From 75d9d6747ed5853e71074397fa984f003791971c Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Tue, 2 Feb 2021 19:24:37 +0100 Subject: [PATCH 001/510] Enrich local data via the OpenAIRE Graph --- .../java/org/dspace/app/nbevent/NBAction.java | 16 + .../app/nbevent/NBEntityMetadataAction.java | 157 ++ .../app/nbevent/NBEventActionService.java | 19 + .../app/nbevent/NBEventActionServiceImpl.java | 116 + .../NBEventsCliScriptConfiguration.java | 23 + .../NBEventsDeleteCascadeConsumer.java | 51 + .../dspace/app/nbevent/NBEventsRunnable.java | 141 ++ .../app/nbevent/NBEventsRunnableCli.java | 48 + .../nbevent/NBEventsScriptConfiguration.java | 63 + .../app/nbevent/NBMetadataMapAction.java | 64 + .../app/nbevent/NBSimpleMetadataAction.java | 55 + .../java/org/dspace/app/nbevent/NBTopic.java | 46 + .../app/nbevent/RawJsonDeserializer.java | 29 + .../dspace/app/nbevent/dao/NBEventsDao.java | 36 + .../app/nbevent/dao/impl/NBEventsDaoImpl.java | 58 + .../app/nbevent/service/NBEventService.java | 39 + .../app/nbevent/service/dto/MessageDto.java | 168 ++ .../service/impl/NBEventServiceImpl.java | 340 +++ .../main/java/org/dspace/content/NBEvent.java | 189 ++ .../org/dspace/content/NBEventProcessed.java | 82 + .../h2/V7.0_2020.10.16__nbevent_processed.sql | 16 + .../V7.0_2020.10.16__nbevent_processed.sql | 19 + .../V7.0_2020.10.16__nbevent_processed.sql | 19 + .../test/data/dspaceFolder/config/local.cfg | 4 +- .../config/spring/api/solr-services.xml | 4 + .../app/nbevent/MockNBEventService.java | 38 + .../org/dspace/builder/AbstractBuilder.java | 7 +- .../org/dspace/builder/NBEventBuilder.java | 125 ++ .../app/rest/NBEventRestController.java | 135 ++ .../app/rest/RestResourceController.java | 70 +- .../app/rest/converter/NBEventConverter.java | 74 + .../app/rest/converter/NBTopicConverter.java | 34 + .../app/rest/model/NBEventMessageRest.java | 88 + .../dspace/app/rest/model/NBEventRest.java | 115 + .../dspace/app/rest/model/NBTopicRest.java | 78 + .../rest/model/hateoas/NBEventResource.java | 21 + .../rest/model/hateoas/NBTopicResource.java | 21 + .../NBEventRelatedLinkRepository.java | 77 + .../repository/NBEventRestRepository.java | 127 ++ .../NBEventTargetLinkRepository.java | 73 + .../NBEventTopicLinkRepository.java | 61 + .../repository/NBTopicRestRepository.java | 54 + .../NBEventStatusReplaceOperation.java | 54 + .../org/dspace/app/rest/utils/RegexUtils.java | 2 +- .../app/rest/NBEventRestRepositoryIT.java | 709 ++++++ .../app/rest/NBTopicRestRepositoryIT.java | 169 ++ .../app/rest/matcher/NBEventMatcher.java | 81 + .../app/rest/matcher/NBTopicMatcher.java | 38 + dspace/config/dspace.cfg | 7 +- dspace/config/hibernate.cfg.xml | 2 + dspace/config/modules/oaire-nbevents.cfg | 14 + dspace/config/spring/api/nbevents.xml | 65 + dspace/config/spring/api/scripts.xml | 6 + dspace/config/spring/api/solr-services.xml | 3 + dspace/config/spring/rest/scripts.xml | 5 + dspace/solr/nbevent/conf/admin-extra.html | 31 + dspace/solr/nbevent/conf/elevate.xml | 36 + dspace/solr/nbevent/conf/protwords.txt | 21 + dspace/solr/nbevent/conf/schema.xml | 544 +++++ dspace/solr/nbevent/conf/scripts.conf | 24 + dspace/solr/nbevent/conf/solrconfig.xml | 1943 +++++++++++++++++ dspace/solr/nbevent/conf/spellings.txt | 2 + dspace/solr/nbevent/conf/stopwords.txt | 57 + dspace/solr/nbevent/conf/synonyms.txt | 31 + dspace/solr/nbevent/core.properties | 0 65 files changed, 6836 insertions(+), 8 deletions(-) create mode 100644 dspace-api/src/main/java/org/dspace/app/nbevent/NBAction.java create mode 100644 dspace-api/src/main/java/org/dspace/app/nbevent/NBEntityMetadataAction.java create mode 100644 dspace-api/src/main/java/org/dspace/app/nbevent/NBEventActionService.java create mode 100644 dspace-api/src/main/java/org/dspace/app/nbevent/NBEventActionServiceImpl.java create mode 100644 dspace-api/src/main/java/org/dspace/app/nbevent/NBEventsCliScriptConfiguration.java create mode 100644 dspace-api/src/main/java/org/dspace/app/nbevent/NBEventsDeleteCascadeConsumer.java create mode 100644 dspace-api/src/main/java/org/dspace/app/nbevent/NBEventsRunnable.java create mode 100644 dspace-api/src/main/java/org/dspace/app/nbevent/NBEventsRunnableCli.java create mode 100644 dspace-api/src/main/java/org/dspace/app/nbevent/NBEventsScriptConfiguration.java create mode 100644 dspace-api/src/main/java/org/dspace/app/nbevent/NBMetadataMapAction.java create mode 100644 dspace-api/src/main/java/org/dspace/app/nbevent/NBSimpleMetadataAction.java create mode 100644 dspace-api/src/main/java/org/dspace/app/nbevent/NBTopic.java create mode 100644 dspace-api/src/main/java/org/dspace/app/nbevent/RawJsonDeserializer.java create mode 100644 dspace-api/src/main/java/org/dspace/app/nbevent/dao/NBEventsDao.java create mode 100644 dspace-api/src/main/java/org/dspace/app/nbevent/dao/impl/NBEventsDaoImpl.java create mode 100644 dspace-api/src/main/java/org/dspace/app/nbevent/service/NBEventService.java create mode 100644 dspace-api/src/main/java/org/dspace/app/nbevent/service/dto/MessageDto.java create mode 100644 dspace-api/src/main/java/org/dspace/app/nbevent/service/impl/NBEventServiceImpl.java create mode 100644 dspace-api/src/main/java/org/dspace/content/NBEvent.java create mode 100644 dspace-api/src/main/java/org/dspace/content/NBEventProcessed.java create mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.0_2020.10.16__nbevent_processed.sql create mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2020.10.16__nbevent_processed.sql create mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.0_2020.10.16__nbevent_processed.sql create mode 100644 dspace-api/src/test/java/org/dspace/app/nbevent/MockNBEventService.java create mode 100644 dspace-api/src/test/java/org/dspace/builder/NBEventBuilder.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/NBEventRestController.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/NBEventConverter.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/NBTopicConverter.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/model/NBEventMessageRest.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/model/NBEventRest.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/model/NBTopicRest.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/NBEventResource.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/NBTopicResource.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NBEventRelatedLinkRepository.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NBEventRestRepository.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NBEventTargetLinkRepository.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NBEventTopicLinkRepository.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NBTopicRestRepository.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/NBEventStatusReplaceOperation.java create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/NBEventRestRepositoryIT.java create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/NBTopicRestRepositoryIT.java create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/NBEventMatcher.java create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/NBTopicMatcher.java create mode 100644 dspace/config/modules/oaire-nbevents.cfg create mode 100644 dspace/config/spring/api/nbevents.xml create mode 100644 dspace/solr/nbevent/conf/admin-extra.html create mode 100644 dspace/solr/nbevent/conf/elevate.xml create mode 100644 dspace/solr/nbevent/conf/protwords.txt create mode 100644 dspace/solr/nbevent/conf/schema.xml create mode 100644 dspace/solr/nbevent/conf/scripts.conf create mode 100644 dspace/solr/nbevent/conf/solrconfig.xml create mode 100644 dspace/solr/nbevent/conf/spellings.txt create mode 100644 dspace/solr/nbevent/conf/stopwords.txt create mode 100644 dspace/solr/nbevent/conf/synonyms.txt create mode 100644 dspace/solr/nbevent/core.properties diff --git a/dspace-api/src/main/java/org/dspace/app/nbevent/NBAction.java b/dspace-api/src/main/java/org/dspace/app/nbevent/NBAction.java new file mode 100644 index 000000000000..782fa53802d3 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/nbevent/NBAction.java @@ -0,0 +1,16 @@ +/** + * 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.nbevent; + +import org.dspace.app.nbevent.service.dto.MessageDto; +import org.dspace.content.Item; +import org.dspace.core.Context; + +public interface NBAction { + public void applyCorrection(Context context, Item item, Item relatedItem, MessageDto message); +} diff --git a/dspace-api/src/main/java/org/dspace/app/nbevent/NBEntityMetadataAction.java b/dspace-api/src/main/java/org/dspace/app/nbevent/NBEntityMetadataAction.java new file mode 100644 index 000000000000..ad575b5281b1 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/nbevent/NBEntityMetadataAction.java @@ -0,0 +1,157 @@ +/** + * 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.nbevent; + +import java.sql.SQLException; +import java.util.Map; + +import org.apache.commons.lang3.StringUtils; +import org.dspace.app.nbevent.service.dto.MessageDto; +import org.dspace.authorize.AuthorizeException; +import org.dspace.content.Collection; +import org.dspace.content.EntityType; +import org.dspace.content.Item; +import org.dspace.content.Relationship; +import org.dspace.content.RelationshipType; +import org.dspace.content.WorkspaceItem; +import org.dspace.content.service.EntityTypeService; +import org.dspace.content.service.InstallItemService; +import org.dspace.content.service.ItemService; +import org.dspace.content.service.RelationshipService; +import org.dspace.content.service.RelationshipTypeService; +import org.dspace.content.service.WorkspaceItemService; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; + +public class NBEntityMetadataAction implements NBAction { + private String relation; + private String entityType; + private Map entityMetadata; + + @Autowired + private InstallItemService installItemService; + + @Autowired + private ItemService itemService; + + @Autowired + private EntityTypeService entityTypeService; + + @Autowired + private RelationshipService relationshipService; + + @Autowired + private RelationshipTypeService relationshipTypeService; + + @Autowired + private WorkspaceItemService workspaceItemService; + + public void setItemService(ItemService itemService) { + this.itemService = itemService; + } + + public String getRelation() { + return relation; + } + + public void setRelation(String relation) { + this.relation = relation; + } + + public String[] splitMetadata(String metadata) { + String[] result = new String[3]; + String[] split = metadata.split("\\."); + result[0] = split[0]; + result[1] = split[1]; + if (split.length == 3) { + result[2] = split[2]; + } + return result; + } + + public String getEntityType() { + return entityType; + } + + public void setEntityType(String entityType) { + this.entityType = entityType; + } + + public Map getEntityMetadata() { + return entityMetadata; + } + + public void setEntityMetadata(Map entityMetadata) { + this.entityMetadata = entityMetadata; + } + + @Override + public void applyCorrection(Context context, Item item, Item relatedItem, MessageDto message) { + try { + if (relatedItem != null) { + link(context, item, relatedItem); + } else { + Collection collection = item.getOwningCollection(); + WorkspaceItem workspaceItem = workspaceItemService.create(context, collection, false); + relatedItem = workspaceItem.getItem(); + if (StringUtils.isNotBlank(entityType)) { + itemService.addMetadata(context, relatedItem, "relationship", "type", null, null, entityType); + } + for (String key : entityMetadata.keySet()) { + String value = getValue(message, key); + if (StringUtils.isNotBlank(value)) { + String[] targetMetadata = splitMetadata(entityMetadata.get(key)); + itemService.addMetadata(context, relatedItem, targetMetadata[0], targetMetadata[1], + targetMetadata[2], null, value); + } + } + installItemService.installItem(context, workspaceItem); + itemService.update(context, relatedItem); + link(context, item, relatedItem); + } + } catch (SQLException | AuthorizeException e) { + throw new RuntimeException(e); + } + } + + private void link(Context context, Item item, Item relatedItem) throws SQLException, AuthorizeException { + EntityType project = entityTypeService.findByEntityType(context, entityType); + RelationshipType relType = relationshipTypeService.findByEntityType(context, project).stream() + .filter(r -> StringUtils.equals(r.getRightwardType(), relation)).findFirst() + .orElseThrow(() -> new IllegalStateException("No relationshipType named " + relation + + " was found for the entity type " + entityType + + ". A proper configuration is required to use the NBEntitiyMetadataAction." + + " If you don't manage funding in your repository please skip this topic in" + + " the oaire-nbevents.cfg")); + // Create the relationship + int leftPlace = relationshipService.findNextLeftPlaceByLeftItem(context, item); + int rightPlace = relationshipService.findNextRightPlaceByRightItem(context, relatedItem); + Relationship persistedRelationship = relationshipService.create(context, item, relatedItem, + relType, leftPlace, rightPlace); + relationshipService.update(context, persistedRelationship); + } + + private String getValue(MessageDto message, String key) { + if (StringUtils.equals(key, "acronym")) { + return message.getAcronym(); + } else if (StringUtils.equals(key, "code")) { + return message.getCode(); + } else if (StringUtils.equals(key, "funder")) { + return message.getFunder(); + } else if (StringUtils.equals(key, "fundingProgram")) { + return message.getFundingProgram(); + } else if (StringUtils.equals(key, "jurisdiction")) { + return message.getJurisdiction(); + } else if (StringUtils.equals(key, "openaireId")) { + return message.getOpenaireId(); + } else if (StringUtils.equals(key, "title")) { + return message.getTitle(); + } + return null; + } +} diff --git a/dspace-api/src/main/java/org/dspace/app/nbevent/NBEventActionService.java b/dspace-api/src/main/java/org/dspace/app/nbevent/NBEventActionService.java new file mode 100644 index 000000000000..0a4de9c7fb56 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/nbevent/NBEventActionService.java @@ -0,0 +1,19 @@ +/** + * 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.nbevent; + +import org.dspace.content.NBEvent; +import org.dspace.core.Context; + +public interface NBEventActionService { + public void accept(Context context, NBEvent nbevent); + + public void discard(Context context, NBEvent nbevent); + + public void reject(Context context, NBEvent nbevent); +} diff --git a/dspace-api/src/main/java/org/dspace/app/nbevent/NBEventActionServiceImpl.java b/dspace-api/src/main/java/org/dspace/app/nbevent/NBEventActionServiceImpl.java new file mode 100644 index 000000000000..b34ac6cd255f --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/nbevent/NBEventActionServiceImpl.java @@ -0,0 +1,116 @@ +/** + * 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.nbevent; + +import java.io.IOException; +import java.sql.SQLException; +import java.util.Map; +import java.util.UUID; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.apache.commons.lang3.StringUtils; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.ContentType; +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.Logger; +import org.dspace.app.nbevent.service.NBEventService; +import org.dspace.app.nbevent.service.dto.MessageDto; +import org.dspace.content.Item; +import org.dspace.content.NBEvent; +import org.dspace.content.service.ItemService; +import org.dspace.core.Context; +import org.dspace.services.ConfigurationService; +import org.springframework.beans.factory.annotation.Autowired; + +public class NBEventActionServiceImpl implements NBEventActionService { + private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(NBEventActionServiceImpl.class); + + private ObjectMapper jsonMapper; + + @Autowired + private NBEventService nbEventService; + + @Autowired + private ItemService itemService; + + @Autowired + private ConfigurationService configurationService; + + private Map topicsToActions; + + public void setTopicsToActions(Map topicsToActions) { + this.topicsToActions = topicsToActions; + } + + public Map getTopicsToActions() { + return topicsToActions; + } + + public NBEventActionServiceImpl() { + jsonMapper = new JsonMapper(); + jsonMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + } + + @Override + public void accept(Context context, NBEvent nbevent) { + Item item = null; + Item related = null; + try { + item = itemService.find(context, UUID.fromString(nbevent.getTarget())); + if (nbevent.getRelated() != null) { + related = itemService.find(context, UUID.fromString(nbevent.getRelated())); + } + topicsToActions.get(nbevent.getTopic()).applyCorrection(context, item, related, + jsonMapper.readValue(nbevent.getMessage(), MessageDto.class)); + nbEventService.deleteEventByEventId(context, nbevent.getEventId()); + makeAcknowledgement(nbevent.getEventId(), NBEvent.ACCEPTED); + } catch (SQLException | JsonProcessingException e) { + throw new RuntimeException(e); + } + } + + @Override + public void discard(Context context, NBEvent nbevent) { + nbEventService.deleteEventByEventId(context, nbevent.getEventId()); + makeAcknowledgement(nbevent.getEventId(), NBEvent.DISCARDED); + } + + @Override + public void reject(Context context, NBEvent nbevent) { + nbEventService.deleteEventByEventId(context, nbevent.getEventId()); + makeAcknowledgement(nbevent.getEventId(), NBEvent.REJECTED); + } + + private void makeAcknowledgement(String eventId, String status) { + String[] ackwnoledgeCallbacks = configurationService.getArrayProperty("oaire-nbevents.acknowledge-url"); + if (ackwnoledgeCallbacks != null) { + for (String ackwnoledgeCallback : ackwnoledgeCallbacks) { + if (StringUtils.isNotBlank(ackwnoledgeCallback)) { + ObjectNode node = jsonMapper.createObjectNode(); + node.put("eventId", eventId); + node.put("status", status); + StringEntity requestEntity = new StringEntity(node.toString(), ContentType.APPLICATION_JSON); + CloseableHttpClient httpclient = HttpClients.createDefault(); + HttpPost postMethod = new HttpPost(ackwnoledgeCallback); + postMethod.setEntity(requestEntity); + try { + httpclient.execute(postMethod); + } catch (IOException e) { + log.error(e.getMessage(), e); + } + } + } + } + } +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/app/nbevent/NBEventsCliScriptConfiguration.java b/dspace-api/src/main/java/org/dspace/app/nbevent/NBEventsCliScriptConfiguration.java new file mode 100644 index 000000000000..d6671676a046 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/nbevent/NBEventsCliScriptConfiguration.java @@ -0,0 +1,23 @@ +/** + * 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.nbevent; + +import org.apache.commons.cli.Options; + +public class NBEventsCliScriptConfiguration extends NBEventsScriptConfiguration { + + @Override + public Options getOptions() { + Options options = super.getOptions(); + options.addOption("h", "help", false, "help"); + options.getOption("h").setType(boolean.class); + super.options = options; + return options; + } + +} diff --git a/dspace-api/src/main/java/org/dspace/app/nbevent/NBEventsDeleteCascadeConsumer.java b/dspace-api/src/main/java/org/dspace/app/nbevent/NBEventsDeleteCascadeConsumer.java new file mode 100644 index 000000000000..0eba13e90bf7 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/nbevent/NBEventsDeleteCascadeConsumer.java @@ -0,0 +1,51 @@ +/** + * 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.nbevent; + +import org.dspace.app.nbevent.service.NBEventService; +import org.dspace.core.Constants; +import org.dspace.core.Context; +import org.dspace.event.Consumer; +import org.dspace.event.Event; +import org.dspace.utils.DSpace; + +/** + * Consumer to delete nbevents once the target item is deleted + * + * @author Andrea Bollini (andrea.bollini at 4science.it) + * + */ +public class NBEventsDeleteCascadeConsumer implements Consumer { + + private NBEventService nbEventService; + + @Override + @SuppressWarnings("unchecked") + public void initialize() throws Exception { + nbEventService = new DSpace().getSingletonService(NBEventService.class); + } + + @Override + public void finish(Context context) throws Exception { + + } + + @Override + public void consume(Context context, Event event) throws Exception { + if (event.getEventType() == Event.DELETE) { + if (event.getSubjectType() == Constants.ITEM && event.getSubjectID() != null) { + nbEventService.deleteEventsByTargetId(context, event.getSubjectID()); + } + } + } + + public void end(Context context) throws Exception { + } + +} diff --git a/dspace-api/src/main/java/org/dspace/app/nbevent/NBEventsRunnable.java b/dspace-api/src/main/java/org/dspace/app/nbevent/NBEventsRunnable.java new file mode 100644 index 000000000000..fc0e7b9dae34 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/nbevent/NBEventsRunnable.java @@ -0,0 +1,141 @@ +/** + * 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.nbevent; + +import java.io.IOException; +import java.io.InputStream; +import java.sql.SQLException; +import java.util.List; +import java.util.UUID; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; +import org.apache.commons.cli.ParseException; +import org.apache.commons.lang3.StringUtils; +import org.dspace.app.nbevent.service.NBEventService; +import org.dspace.content.NBEvent; +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; +import org.dspace.eperson.factory.EPersonServiceFactory; +import org.dspace.scripts.DSpaceRunnable; +import org.dspace.services.ConfigurationService; +import org.dspace.utils.DSpace; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Implementation of {@link DSpaceRunnable} to perfom a NBEvents import from file. + * + * @author Alessandro Martelli (alessandro.martelli at 4science.it) + * + */ +public class NBEventsRunnable extends DSpaceRunnable> { + + private static final Logger LOGGER = LoggerFactory.getLogger(NBEventsRunnable.class); + + protected NBEventService nbEventService; + + protected String[] topicsToImport; + + protected ConfigurationService configurationService; + + protected String fileLocation; + + protected List entries; + + protected Context context; + + @Override + @SuppressWarnings({ "rawtypes" }) + public NBEventsScriptConfiguration getScriptConfiguration() { + NBEventsScriptConfiguration configuration = new DSpace().getServiceManager() + .getServiceByName("import-nbevents", NBEventsScriptConfiguration.class); + return configuration; + } + + @Override + public void setup() throws ParseException { + DSpace dspace = new DSpace(); + + nbEventService = dspace.getSingletonService(NBEventService.class); + if (nbEventService == null) { + LOGGER.error("nbEventService is NULL. Error in spring configuration"); + throw new IllegalStateException(); + } else { + LOGGER.debug("nbEventService correctly loaded"); + } + + configurationService = dspace.getConfigurationService(); + + topicsToImport = configurationService.getArrayProperty("oaire-nbevents.import.topic"); + + fileLocation = commandLine.getOptionValue("f"); + + } + + @Override + public void internalRun() throws Exception { + + if (StringUtils.isEmpty(fileLocation)) { + LOGGER.info("No file location was entered"); + System.exit(1); + } + + context = new Context(); + + ObjectMapper jsonMapper = new JsonMapper(); + jsonMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + try { + this.entries = jsonMapper.readValue(getNBEventsInputStream(), new TypeReference>() { + }); + } catch (IOException e) { + LOGGER.error("File is not found or not readable: " + fileLocation); + e.printStackTrace(); + System.exit(1); + } + + for (NBEvent entry : entries) { + if (!StringUtils.equalsAny(entry.getTopic(), topicsToImport)) { + LOGGER.info("Skip event for topic " + entry.getTopic() + " is not allowed in the oaire-nbevents.cfg"); + continue; + } + try { + nbEventService.store(context, entry); + } catch (RuntimeException e) { + System.out.println("Skip event for originalId " + entry.getOriginalId() + ": " + e.getMessage()); + } + } + + } + + /** + * Obtain an InputStream from the runnable instance. + * @return + * @throws Exception + */ + protected InputStream getNBEventsInputStream() throws Exception { + + this.assignCurrentUserInContext(); + + InputStream inputStream = handler.getFileStream(context, fileLocation) + .orElseThrow(() -> new IllegalArgumentException("Error reading file, the file couldn't be " + + "found for filename: " + fileLocation)); + + return inputStream; + } + + private void assignCurrentUserInContext() throws SQLException { + UUID uuid = getEpersonIdentifier(); + if (uuid != null) { + EPerson ePerson = EPersonServiceFactory.getInstance().getEPersonService().find(context, uuid); + context.setCurrentUser(ePerson); + } + } +} diff --git a/dspace-api/src/main/java/org/dspace/app/nbevent/NBEventsRunnableCli.java b/dspace-api/src/main/java/org/dspace/app/nbevent/NBEventsRunnableCli.java new file mode 100644 index 000000000000..2e33b6bfade7 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/nbevent/NBEventsRunnableCli.java @@ -0,0 +1,48 @@ +/** + * 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.nbevent; + +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; + +import org.apache.commons.cli.HelpFormatter; +import org.apache.commons.cli.ParseException; +import org.dspace.utils.DSpace; + +public class NBEventsRunnableCli extends NBEventsRunnable { + + @Override + @SuppressWarnings({ "rawtypes" }) + public NBEventsCliScriptConfiguration getScriptConfiguration() { + NBEventsCliScriptConfiguration configuration = new DSpace().getServiceManager() + .getServiceByName("import-nbevents", NBEventsCliScriptConfiguration.class); + return configuration; + } + + @Override + public void setup() throws ParseException { + super.setup(); + + // in case of CLI we show the help prompt + if (commandLine.hasOption('h')) { + HelpFormatter formatter = new HelpFormatter(); + formatter.printHelp("Import Notification event json file", getScriptConfiguration().getOptions()); + System.exit(0); + } + } + + /** + * Get the events input stream from a local file. + */ + @Override + protected InputStream getNBEventsInputStream() throws Exception { + return new FileInputStream(new File(fileLocation)); + } + +} diff --git a/dspace-api/src/main/java/org/dspace/app/nbevent/NBEventsScriptConfiguration.java b/dspace-api/src/main/java/org/dspace/app/nbevent/NBEventsScriptConfiguration.java new file mode 100644 index 000000000000..0826e5173d4b --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/nbevent/NBEventsScriptConfiguration.java @@ -0,0 +1,63 @@ +/** + * 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.nbevent; + +import java.io.InputStream; +import java.sql.SQLException; + +import org.apache.commons.cli.Options; +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.core.Context; +import org.dspace.scripts.configuration.ScriptConfiguration; +import org.springframework.beans.factory.annotation.Autowired; + +public class NBEventsScriptConfiguration extends ScriptConfiguration { + + @Autowired + private AuthorizeService authorizeService; + + private Class dspaceRunnableClass; + + @Override + public Class getDspaceRunnableClass() { + return dspaceRunnableClass; + } + + /** + * Generic setter for the dspaceRunnableClass + * @param dspaceRunnableClass The dspaceRunnableClass to be set on this NBEventsScriptConfiguration + */ + @Override + public void setDspaceRunnableClass(Class dspaceRunnableClass) { + this.dspaceRunnableClass = dspaceRunnableClass; + } + + @Override + public boolean isAllowedToExecute(Context context) { + try { + return authorizeService.isAdmin(context); + } catch (SQLException e) { + throw new RuntimeException("SQLException occurred when checking if the current user is an admin", e); + } + } + + @Override + public Options getOptions() { + if (options == null) { + Options options = new Options(); + + options.addOption("f", "file", true, "Import data from OpenAIRE notification broker files"); + options.getOption("f").setType(InputStream.class); + options.getOption("f").setRequired(true); + + super.options = options; + } + return options; + } + +} diff --git a/dspace-api/src/main/java/org/dspace/app/nbevent/NBMetadataMapAction.java b/dspace-api/src/main/java/org/dspace/app/nbevent/NBMetadataMapAction.java new file mode 100644 index 000000000000..ca198abaacf3 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/nbevent/NBMetadataMapAction.java @@ -0,0 +1,64 @@ +/** + * 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.nbevent; + +import java.sql.SQLException; +import java.util.Map; + +import org.dspace.app.nbevent.service.dto.MessageDto; +import org.dspace.authorize.AuthorizeException; +import org.dspace.content.Item; +import org.dspace.content.service.ItemService; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; + +public class NBMetadataMapAction implements NBAction { + public static final String DEFAULT = "default"; + + private Map types; + @Autowired + private ItemService itemService; + + public void setItemService(ItemService itemService) { + this.itemService = itemService; + } + + public Map getTypes() { + return types; + } + + public void setTypes(Map types) { + this.types = types; + } + + @Override + public void applyCorrection(Context context, Item item, Item relatedItem, MessageDto message) { + try { + String targetMetadata = types.get(message.getType()); + if (targetMetadata == null) { + targetMetadata = types.get(DEFAULT); + } + String[] metadata = splitMetadata(targetMetadata); + itemService.addMetadata(context, item, metadata[0], metadata[1], metadata[2], null, message.getValue()); + itemService.update(context, item); + } catch (SQLException | AuthorizeException e) { + throw new RuntimeException(e); + } + } + + public String[] splitMetadata(String metadata) { + String[] result = new String[3]; + String[] split = metadata.split("\\."); + result[0] = split[0]; + result[1] = split[1]; + if (split.length == 3) { + result[2] = split[2]; + } + return result; + } +} diff --git a/dspace-api/src/main/java/org/dspace/app/nbevent/NBSimpleMetadataAction.java b/dspace-api/src/main/java/org/dspace/app/nbevent/NBSimpleMetadataAction.java new file mode 100644 index 000000000000..33eaebcfaab2 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/nbevent/NBSimpleMetadataAction.java @@ -0,0 +1,55 @@ +/** + * 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.nbevent; + +import java.sql.SQLException; + +import org.dspace.app.nbevent.service.dto.MessageDto; +import org.dspace.authorize.AuthorizeException; +import org.dspace.content.Item; +import org.dspace.content.service.ItemService; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; + +public class NBSimpleMetadataAction implements NBAction { + private String metadata; + private String metadataSchema; + private String metadataElement; + private String metadataQualifier; + @Autowired + private ItemService itemService; + + public void setItemService(ItemService itemService) { + this.itemService = itemService; + } + + public String getMetadata() { + return metadata; + } + + public void setMetadata(String metadata) { + this.metadata = metadata; + String[] split = metadata.split("\\."); + this.metadataSchema = split[0]; + this.metadataElement = split[1]; + if (split.length == 3) { + this.metadataQualifier = split[2]; + } + } + + @Override + public void applyCorrection(Context context, Item item, Item relatedItem, MessageDto message) { + try { + itemService.addMetadata(context, item, metadataSchema, metadataElement, metadataQualifier, null, + message.getAbstracts()); + itemService.update(context, item); + } catch (SQLException | AuthorizeException e) { + throw new RuntimeException(e); + } + } +} diff --git a/dspace-api/src/main/java/org/dspace/app/nbevent/NBTopic.java b/dspace-api/src/main/java/org/dspace/app/nbevent/NBTopic.java new file mode 100644 index 000000000000..afa9990d3da3 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/nbevent/NBTopic.java @@ -0,0 +1,46 @@ +/** + * 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.nbevent; + +import java.util.Date; + +/** + * This model class represent the notification broker topic concept + * + * @author Andrea Bollini (andrea.bollini at 4science.it) + * + */ +public class NBTopic { + private String key; + private long totalEvents; + private Date lastEvent; + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public long getTotalEvents() { + return totalEvents; + } + + public void setTotalEvents(long totalEvents) { + this.totalEvents = totalEvents; + } + + public Date getLastEvent() { + return lastEvent; + } + + public void setLastEvent(Date lastEvent) { + this.lastEvent = lastEvent; + } +} diff --git a/dspace-api/src/main/java/org/dspace/app/nbevent/RawJsonDeserializer.java b/dspace-api/src/main/java/org/dspace/app/nbevent/RawJsonDeserializer.java new file mode 100644 index 000000000000..edc744d586a9 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/nbevent/RawJsonDeserializer.java @@ -0,0 +1,29 @@ +/** + * 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.nbevent; + +import java.io.IOException; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +public class RawJsonDeserializer extends JsonDeserializer { + + @Override + public String deserialize(JsonParser jp, DeserializationContext ctxt) + throws IOException, JsonProcessingException { + + ObjectMapper mapper = (ObjectMapper) jp.getCodec(); + JsonNode node = mapper.readTree(jp); + return mapper.writeValueAsString(node); + } +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/app/nbevent/dao/NBEventsDao.java b/dspace-api/src/main/java/org/dspace/app/nbevent/dao/NBEventsDao.java new file mode 100644 index 000000000000..f426ddf6ab44 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/nbevent/dao/NBEventsDao.java @@ -0,0 +1,36 @@ +/** + * 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.nbevent.dao; + +import java.sql.SQLException; +import java.util.List; + +import org.dspace.content.Item; +import org.dspace.content.NBEventProcessed; +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; + +public interface NBEventsDao { + /** + * Search a page of notification broker events by notification ID. + * + * @param c + * @param eventId + * @param start + * @param size + * @return + * @throws SQLException + */ + public List searchByEventId(Context c, String eventId, Integer start, Integer size) + throws SQLException; + + public boolean isEventStored(Context c, String checksum) throws SQLException; + + boolean storeEvent(Context c, String checksum, EPerson eperson, Item item); + +} diff --git a/dspace-api/src/main/java/org/dspace/app/nbevent/dao/impl/NBEventsDaoImpl.java b/dspace-api/src/main/java/org/dspace/app/nbevent/dao/impl/NBEventsDaoImpl.java new file mode 100644 index 000000000000..49894441b2d7 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/nbevent/dao/impl/NBEventsDaoImpl.java @@ -0,0 +1,58 @@ +/** + * 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.nbevent.dao.impl; + +import java.sql.SQLException; +import java.util.Date; +import java.util.List; +import javax.persistence.Query; + +import org.dspace.app.nbevent.dao.NBEventsDao; +import org.dspace.content.Item; +import org.dspace.content.NBEventProcessed; +import org.dspace.core.AbstractHibernateDAO; +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; + +public class NBEventsDaoImpl extends AbstractHibernateDAO implements NBEventsDao { + + @Override + public boolean storeEvent(Context context, String checksum, EPerson eperson, Item item) { + NBEventProcessed nbEvent = new NBEventProcessed(); + nbEvent.setEperson(eperson); + nbEvent.setEventId(checksum); + nbEvent.setItem(item); + nbEvent.setEventTimestamp(new Date()); + try { + create(context, nbEvent); + return true; + } catch (SQLException e) { + return false; + } + } + + @Override + public boolean isEventStored(Context context, String checksum) throws SQLException { + Query query = createQuery(context, + "SELECT count(eventId) FROM NBEventProcessed nbevent WHERE nbevent.eventId = :event_id "); + query.setParameter("event_id", checksum); + return count(query) != 0; + } + + @Override + public List searchByEventId(Context context, String eventId, Integer start, Integer size) + throws SQLException { + Query query = createQuery(context, + "SELECT * " + "FROM NBEventProcessed nbevent WHERE nbevent.nbevent_id = :event_id "); + query.setFirstResult(start); + query.setMaxResults(size); + query.setParameter("event_id", eventId); + return findMany(context, query); + } + +} diff --git a/dspace-api/src/main/java/org/dspace/app/nbevent/service/NBEventService.java b/dspace-api/src/main/java/org/dspace/app/nbevent/service/NBEventService.java new file mode 100644 index 000000000000..146f0d12904f --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/nbevent/service/NBEventService.java @@ -0,0 +1,39 @@ +/** + * 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.nbevent.service; + +import java.util.List; +import java.util.UUID; + +import org.dspace.app.nbevent.NBTopic; +import org.dspace.content.NBEvent; +import org.dspace.core.Context; + +public interface NBEventService { + + public NBTopic findTopicByTopicId(String topicId); + + public List findAllTopics(Context context, long offset, long pageSize); + + public long countTopics(Context context); + + public List findEventsByTopicAndPage(Context context, String topic, + long offset, int pageSize, + String orderField, boolean ascending); + + public long countEventsByTopic(Context context, String topic); + + public NBEvent findEventByEventId(Context context, String id); + + public void store(Context context, NBEvent event); + + public void deleteEventByEventId(Context context, String id); + + public void deleteEventsByTargetId(Context context, UUID targetId); + +} diff --git a/dspace-api/src/main/java/org/dspace/app/nbevent/service/dto/MessageDto.java b/dspace-api/src/main/java/org/dspace/app/nbevent/service/dto/MessageDto.java new file mode 100644 index 000000000000..6b72c58ee433 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/nbevent/service/dto/MessageDto.java @@ -0,0 +1,168 @@ +/** + * 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.nbevent.service.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class MessageDto { + + @JsonProperty("pids[0].value") + private String value; + + @JsonProperty("pids[0].type") + private String type; + + @JsonProperty("instances[0].hostedby") + private String instanceHostedBy; + + @JsonProperty("instances[0].instancetype") + private String instanceInstanceType; + + @JsonProperty("instances[0].license") + private String instanceLicense; + + @JsonProperty("instances[0].url") + private String instanceUrl; + + @JsonProperty("abstracts[0]") + private String abstracts; + + @JsonProperty("projects[0].acronym") + private String acronym; + + @JsonProperty("projects[0].code") + private String code; + + @JsonProperty("projects[0].funder") + private String funder; + + @JsonProperty("projects[0].fundingProgram") + private String fundingProgram; + + @JsonProperty("projects[0].jurisdiction") + private String jurisdiction; + + @JsonProperty("projects[0].openaireId") + private String openaireId; + + @JsonProperty("projects[0].title") + private String title; + + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getInstanceHostedBy() { + return instanceHostedBy; + } + + public void setInstanceHostedBy(String instanceHostedBy) { + this.instanceHostedBy = instanceHostedBy; + } + + public String getInstanceInstanceType() { + return instanceInstanceType; + } + + public void setInstanceInstanceType(String instanceInstanceType) { + this.instanceInstanceType = instanceInstanceType; + } + + public String getInstanceLicense() { + return instanceLicense; + } + + public void setInstanceLicense(String instanceLicense) { + this.instanceLicense = instanceLicense; + } + + public String getInstanceUrl() { + return instanceUrl; + } + + public void setInstanceUrl(String instanceUrl) { + this.instanceUrl = instanceUrl; + } + + public String getAbstracts() { + return abstracts; + } + + public void setAbstracts(String abstracts) { + this.abstracts = abstracts; + } + + public String getAcronym() { + return acronym; + } + + public void setAcronym(String acronym) { + this.acronym = acronym; + } + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + + public String getFunder() { + return funder; + } + + public void setFunder(String funder) { + this.funder = funder; + } + + public String getFundingProgram() { + return fundingProgram; + } + + public void setFundingProgram(String fundingProgram) { + this.fundingProgram = fundingProgram; + } + + public String getJurisdiction() { + return jurisdiction; + } + + public void setJurisdiction(String jurisdiction) { + this.jurisdiction = jurisdiction; + } + + public String getOpenaireId() { + return openaireId; + } + + public void setOpenaireId(String openaireId) { + this.openaireId = openaireId; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } +} diff --git a/dspace-api/src/main/java/org/dspace/app/nbevent/service/impl/NBEventServiceImpl.java b/dspace-api/src/main/java/org/dspace/app/nbevent/service/impl/NBEventServiceImpl.java new file mode 100644 index 000000000000..8901079425ca --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/nbevent/service/impl/NBEventServiceImpl.java @@ -0,0 +1,340 @@ +/** + * 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.nbevent.service.impl; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.UUID; + +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; +import org.apache.log4j.Logger; +import org.apache.solr.client.solrj.SolrClient; +import org.apache.solr.client.solrj.SolrQuery; +import org.apache.solr.client.solrj.SolrQuery.ORDER; +import org.apache.solr.client.solrj.SolrServerException; +import org.apache.solr.client.solrj.impl.HttpSolrClient; +import org.apache.solr.client.solrj.request.UpdateRequest; +import org.apache.solr.client.solrj.response.FacetField; +import org.apache.solr.client.solrj.response.FacetField.Count; +import org.apache.solr.client.solrj.response.QueryResponse; +import org.apache.solr.common.SolrDocument; +import org.apache.solr.common.SolrDocumentList; +import org.apache.solr.common.SolrInputDocument; +import org.dspace.app.nbevent.NBTopic; +import org.dspace.app.nbevent.dao.impl.NBEventsDaoImpl; +import org.dspace.app.nbevent.service.NBEventService; +import org.dspace.content.Item; +import org.dspace.content.NBEvent; +import org.dspace.content.service.ItemService; +import org.dspace.core.Context; +import org.dspace.handle.service.HandleService; +import org.dspace.services.ConfigurationService; +import org.dspace.services.factory.DSpaceServicesFactory; +import org.springframework.beans.factory.annotation.Autowired; + +public class NBEventServiceImpl implements NBEventService { + + private static final Logger log = Logger.getLogger(NBEventServiceImpl.class); + + @Autowired(required = true) + protected ConfigurationService configurationService; + + @Autowired(required = true) + protected ItemService itemService; + + @Autowired + private HandleService handleService; + + @Autowired + private NBEventsDaoImpl nbEventsDao; + + private ObjectMapper jsonMapper; + + public NBEventServiceImpl() { + jsonMapper = new JsonMapper(); + jsonMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + } + + /** + * Non-Static CommonsHttpSolrServer for processing indexing events. + */ + protected SolrClient solr = null; + + public static final String ORIGINAL_ID = "original_id"; + public static final String TITLE = "title"; + public static final String TOPIC = "topic"; + public static final String TRUST = "trust"; + public static final String MESSAGE = "message"; + public static final String EVENT_ID = "event_id"; + public static final String RESOURCE_UUID = "resource_uuid"; + public static final String LAST_UPDATE = "last_update"; + public static final String RELATED_UUID = "related_uuid"; + + protected SolrClient getSolr() { + if (solr == null) { + String solrService = DSpaceServicesFactory.getInstance().getConfigurationService() + .getProperty("oaire-nbevents.solr.server", "http://localhost:8983/solr/nbevent"); + return new HttpSolrClient.Builder(solrService).build(); + } + return solr; + } + + @Override + public long countTopics(Context context) { + SolrQuery solrQuery = new SolrQuery(); + solrQuery.setRows(0); + solrQuery.setQuery("*:*"); + solrQuery.setFacet(true); + // we would like to get eventually topic that has no longer active nb events + solrQuery.setFacetMinCount(0); + solrQuery.addFacetField(TOPIC); + QueryResponse response; + try { + response = getSolr().query(solrQuery); + } catch (SolrServerException | IOException e) { + throw new RuntimeException(e); + } + return response.getFacetField(TOPIC).getValueCount(); + } + + @Override + public void deleteEventByEventId(Context context, String id) { + try { + getSolr().deleteById(id); + getSolr().commit(); + } catch (SolrServerException | IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public void deleteEventsByTargetId(Context context, UUID targetId) { + try { + getSolr().deleteByQuery(RESOURCE_UUID + ":" + targetId.toString()); + getSolr().commit(); + } catch (SolrServerException | IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public NBTopic findTopicByTopicId(String topicId) { + SolrQuery solrQuery = new SolrQuery(); + solrQuery.setRows(0); + solrQuery.setQuery(TOPIC + ":" + topicId.replaceAll("!", "/")); + solrQuery.setFacet(true); + // we would like to get eventually topic that has no longer active nb events + solrQuery.setFacetMinCount(0); + solrQuery.addFacetField(TOPIC); + QueryResponse response; + try { + response = getSolr().query(solrQuery); + FacetField facetField = response.getFacetField(TOPIC); + for (Count c : facetField.getValues()) { + if (c.getName().equals(topicId.replace("!", "/"))) { + NBTopic topic = new NBTopic(); + topic.setKey(c.getName()); +// topic.setName(OpenstarSupportedTopic.sorlToRest(c.getName())); + topic.setTotalEvents(c.getCount()); + topic.setLastEvent(new Date()); + return topic; + } + } + } catch (SolrServerException | IOException e) { + throw new RuntimeException(e); + } + return null; + } + + /** + * Method to get all topics and the number of entries for each topic + * + * @param context DSpace context + * @param offset number of results to skip + * @param count number of result to fetch + * @return list of topics with number of events + * @throws IOException + * @throws SolrServerException + * @throws InvalidEnumeratedDataValueException + * + */ + @Override + public List findAllTopics(Context context, long offset, long count) { + SolrQuery solrQuery = new SolrQuery(); + solrQuery.setRows(0); + solrQuery.setQuery("*:*"); + solrQuery.setFacet(true); + // we would like to get eventually topic that has no longer active nb events + solrQuery.setFacetMinCount(0); + solrQuery.setFacetLimit((int) (offset + count)); + solrQuery.addFacetField(TOPIC); + QueryResponse response; + List nbTopics = null; + try { + response = getSolr().query(solrQuery); + FacetField facetField = response.getFacetField(TOPIC); + nbTopics = new ArrayList<>(); + int idx = 0; + for (Count c : facetField.getValues()) { + if (idx < offset) { + idx++; + continue; + } + NBTopic topic = new NBTopic(); + topic.setKey(c.getName()); + // topic.setName(c.getName().replaceAll("/", "!")); + topic.setTotalEvents(c.getCount()); + topic.setLastEvent(new Date()); + nbTopics.add(topic); + idx++; + } + } catch (SolrServerException | IOException e) { + throw new RuntimeException(e); + } + return nbTopics; + } + + @Override + public void store(Context context, NBEvent dto) { + UpdateRequest updateRequest = new UpdateRequest(); + String topic = dto.getTopic(); + if (topic != null) { + String checksum = dto.getEventId(); + try { + if (!nbEventsDao.isEventStored(context, checksum)) { + SolrInputDocument doc = new SolrInputDocument(); + doc.addField(EVENT_ID, checksum); + doc.addField(ORIGINAL_ID, dto.getOriginalId()); + doc.addField(TITLE, dto.getTitle()); + doc.addField(TOPIC, topic); + doc.addField(TRUST, dto.getTrust()); + doc.addField(MESSAGE, dto.getMessage()); + doc.addField(LAST_UPDATE, new Date()); + final String resourceUUID = getResourceUUID(context, dto.getOriginalId()); + if (resourceUUID == null) { + log.warn("Skipped event " + checksum + " related to the oai record " + dto.getOriginalId() + + " as the record was not found"); + return; + } + doc.addField(RESOURCE_UUID, resourceUUID); + doc.addField(RELATED_UUID, dto.getRelated()); + updateRequest.add(doc); + updateRequest.process(getSolr()); + getSolr().commit(); + } + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } + + @Override + public NBEvent findEventByEventId(Context context, String eventId) { + SolrQuery param = new SolrQuery(EVENT_ID + ":" + eventId); + QueryResponse response; + try { + response = getSolr().query(param); + if (response != null) { + SolrDocumentList list = response.getResults(); + if (list != null && list.size() == 1) { + SolrDocument doc = list.get(0); + return getNBEventFromSOLR(doc); + } + } + } catch (SolrServerException | IOException e) { + throw new RuntimeException("Exception querying Solr", e); + } + return null; + } + + private NBEvent getNBEventFromSOLR(SolrDocument doc) { + NBEvent item = new NBEvent(); + item.setEventId((String) doc.get(EVENT_ID)); + item.setLastUpdate((Date) doc.get(LAST_UPDATE)); + item.setMessage((String) doc.get(MESSAGE)); + item.setOriginalId((String) doc.get(ORIGINAL_ID)); + item.setTarget((String) doc.get(RESOURCE_UUID)); + item.setTitle((String) doc.get(TITLE)); + item.setTopic((String) doc.get(TOPIC)); + item.setTrust((double) doc.get(TRUST)); + item.setRelated((String) doc.get(RELATED_UUID)); + return item; + } + + @Override + public List findEventsByTopicAndPage(Context context, String topic, + long offset, int pageSize, + String orderField, boolean ascending) { + SolrQuery solrQuery = new SolrQuery(); + solrQuery.setStart(((Long) offset).intValue()); + solrQuery.setRows(pageSize); + solrQuery.setSort(orderField, ascending ? ORDER.asc : ORDER.desc); + solrQuery.setQuery(TOPIC + ":" + topic.replaceAll("!", "/")); + QueryResponse response; + try { + response = getSolr().query(solrQuery); + if (response != null) { + SolrDocumentList list = response.getResults(); + List responseItem = new ArrayList<>(); + for (SolrDocument doc : list) { + NBEvent item = getNBEventFromSOLR(doc); + responseItem.add(item); + } + return responseItem; + } + } catch (SolrServerException | IOException e) { + throw new RuntimeException(e); + } + return null; + } + + @Override + public long countEventsByTopic(Context context, String topic) { + SolrQuery solrQuery = new SolrQuery(); + solrQuery.setRows(0); + solrQuery.setQuery(TOPIC + ":" + topic.replace("!", "/")); + QueryResponse response = null; + try { + response = getSolr().query(solrQuery); + return response.getResults().getNumFound(); + } catch (SolrServerException | IOException e) { + throw new RuntimeException(e); + } + } + + private String getResourceUUID(Context context, String originalId) throws Exception { + String id = getHandleFromOriginalId(originalId); + if (id != null) { + Item item = (Item) handleService.resolveToObject(context, id); + if (item != null) { + final String itemUuid = item.getID().toString(); + context.uncacheEntity(item); + return itemUuid; + } else { + return null; + } + } else { + throw new RuntimeException("Malformed originalId " + originalId); + } + } + + // oai:www.openstarts.units.it:10077/21486 + private String getHandleFromOriginalId(String originalId) { + Integer startPosition = originalId.lastIndexOf(':'); + if (startPosition != -1) { + return originalId.substring(startPosition + 1, originalId.length()); + } else { + return null; + } + } + +} diff --git a/dspace-api/src/main/java/org/dspace/content/NBEvent.java b/dspace-api/src/main/java/org/dspace/content/NBEvent.java new file mode 100644 index 000000000000..c92001691724 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/NBEvent.java @@ -0,0 +1,189 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.content; + +import java.io.UnsupportedEncodingException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Date; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import org.apache.solr.client.solrj.beans.Field; +import org.dspace.app.nbevent.RawJsonDeserializer; + +/** + * This class represent the notification broker data as loaded in our solr + * nbevent core + * + */ +public class NBEvent { + public static final char[] HEX_DIGITS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', + 'f' }; + public static final String ACCEPTED = "accepted"; + public static final String REJECTED = "rejected"; + public static final String DISCARDED = "discarded"; + @Field("event_id") + private String eventId; + + @Field("original_id") + private String originalId; + + @Field("resource_uuid") + private String target; + + @Field("related_uuid") + private String related; + + @Field("title") + private String title; + + @Field("topic") + private String topic; + + @Field("trust") + private double trust; + + @Field("message") + @JsonDeserialize(using = RawJsonDeserializer.class) + private String message; + + @Field("last_update") + private Date lastUpdate; + + private String status = "PENDING"; + + public NBEvent() { + } + + public NBEvent(String originalId, String target, String title, String topic, double trust, String message, + Date lastUpdate) { + super(); + this.originalId = originalId; + this.target = target; + this.title = title; + this.topic = topic; + this.trust = trust; + this.message = message; + this.lastUpdate = lastUpdate; + try { + computedEventId(); + } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) { + throw new IllegalStateException(e); + } + + } + + public String getOriginalId() { + return originalId; + } + + public void setOriginalId(String originalId) { + this.originalId = originalId; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getTopic() { + return topic; + } + + public void setTopic(String topic) { + this.topic = topic; + } + + public double getTrust() { + return trust; + } + + public void setTrust(double trust) { + this.trust = trust; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getEventId() { + if (eventId == null) { + try { + computedEventId(); + } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) { + e.printStackTrace(); + } + } + return eventId; + } + + public void setEventId(String eventId) { + this.eventId = eventId; + } + + public String getTarget() { + return target; + } + + public void setTarget(String target) { + this.target = target; + } + + public Date getLastUpdate() { + return lastUpdate; + } + + public void setLastUpdate(Date lastUpdate) { + this.lastUpdate = lastUpdate; + } + + public void setRelated(String related) { + this.related = related; + } + + public String getRelated() { + return related; + } + + public void setStatus(String status) { + this.status = status; + } + + public String getStatus() { + return status; + } + + /* + * DTO constructed via Jackson use empty constructor. In this case, the eventId + * must be compute on the get method + */ + private void computedEventId() throws NoSuchAlgorithmException, UnsupportedEncodingException { + MessageDigest digester = MessageDigest.getInstance("MD5"); + String dataToString = "originalId=" + originalId + ", title=" + title + ", topic=" + topic + ", trust=" + trust + + ", message=" + message; + digester.update(dataToString.getBytes("UTF-8")); + byte[] signature = digester.digest(); + char[] arr = new char[signature.length << 1]; + for (int i = 0; i < signature.length; i++) { + int b = signature[i]; + int idx = i << 1; + arr[idx] = HEX_DIGITS[(b >> 4) & 0xf]; + arr[idx + 1] = HEX_DIGITS[b & 0xf]; + } + eventId = new String(arr); + + } + +} diff --git a/dspace-api/src/main/java/org/dspace/content/NBEventProcessed.java b/dspace-api/src/main/java/org/dspace/content/NBEventProcessed.java new file mode 100644 index 000000000000..62f8222e2478 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/NBEventProcessed.java @@ -0,0 +1,82 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.content; + +import java.io.Serializable; +import java.util.Date; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.OneToOne; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; + +import org.dspace.eperson.EPerson; + +/** + * This class represent the stored information about processed notification + * broker events + * + */ +@Entity +@Table(name = "nbevent_processed") +public class NBEventProcessed implements Serializable { + + private static final long serialVersionUID = 3427340199132007814L; + + @Id + @Column(name = "nbevent_id") + private String eventId; + + @Temporal(TemporalType.TIMESTAMP) + @Column(name = "nbevent_timestamp") + private Date eventTimestamp; + + @JoinColumn(name = "eperson_uuid") + @OneToOne + private EPerson eperson; + + @JoinColumn(name = "item_uuid") + @OneToOne + private Item item; + + public String getEventId() { + return eventId; + } + + public void setEventId(String eventId) { + this.eventId = eventId; + } + + public Date getEventTimestamp() { + return eventTimestamp; + } + + public void setEventTimestamp(Date eventTimestamp) { + this.eventTimestamp = eventTimestamp; + } + + public EPerson getEperson() { + return eperson; + } + + public void setEperson(EPerson eperson) { + this.eperson = eperson; + } + + public Item getItem() { + return item; + } + + public void setItem(Item item) { + this.item = item; + } + +} diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.0_2020.10.16__nbevent_processed.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.0_2020.10.16__nbevent_processed.sql new file mode 100644 index 000000000000..b64c52248b43 --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.0_2020.10.16__nbevent_processed.sql @@ -0,0 +1,16 @@ +-- +-- The contents of this file are subject to the license and copyright +-- detailed in the LICENSE and NOTICE files at the root of the source +-- tree and available online at +-- +-- http://www.dspace.org/license/ +-- + +CREATE TABLE nbevent_processed ( + nbevent_id VARCHAR(255) NOT NULL, + nbevent_timestamp TIMESTAMP NULL, + eperson_uuid UUID NULL REFERENCES eperson(uuid), + item_uuid uuid NOT NULL REFERENCES item(uuid) +); + +CREATE INDEX item_uuid_idx ON nbevent_processed(item_uuid); diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2020.10.16__nbevent_processed.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2020.10.16__nbevent_processed.sql new file mode 100644 index 000000000000..5cf9a0484f8d --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2020.10.16__nbevent_processed.sql @@ -0,0 +1,19 @@ +-- +-- The contents of this file are subject to the license and copyright +-- detailed in the LICENSE and NOTICE files at the root of the source +-- tree and available online at +-- +-- http://www.dspace.org/license/ +-- + +CREATE TABLE nbevent_processed ( + nbevent_id VARCHAR(255) NOT NULL, + nbevent_timestamp TIMESTAMP NULL, + eperson_uuid UUID NULL, + item_uuid UUID NULL, + CONSTRAINT nbevent_pk PRIMARY KEY (nbevent_id), + CONSTRAINT eperson_uuid_fkey FOREIGN KEY (eperson_uuid) REFERENCES eperson (uuid), + CONSTRAINT item_uuid_fkey FOREIGN KEY (item_uuid) REFERENCES item (uuid) +); + +CREATE INDEX item_uuid_idx ON nbevent_processed(item_uuid); diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.0_2020.10.16__nbevent_processed.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.0_2020.10.16__nbevent_processed.sql new file mode 100644 index 000000000000..5cf9a0484f8d --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.0_2020.10.16__nbevent_processed.sql @@ -0,0 +1,19 @@ +-- +-- The contents of this file are subject to the license and copyright +-- detailed in the LICENSE and NOTICE files at the root of the source +-- tree and available online at +-- +-- http://www.dspace.org/license/ +-- + +CREATE TABLE nbevent_processed ( + nbevent_id VARCHAR(255) NOT NULL, + nbevent_timestamp TIMESTAMP NULL, + eperson_uuid UUID NULL, + item_uuid UUID NULL, + CONSTRAINT nbevent_pk PRIMARY KEY (nbevent_id), + CONSTRAINT eperson_uuid_fkey FOREIGN KEY (eperson_uuid) REFERENCES eperson (uuid), + CONSTRAINT item_uuid_fkey FOREIGN KEY (item_uuid) REFERENCES item (uuid) +); + +CREATE INDEX item_uuid_idx ON nbevent_processed(item_uuid); diff --git a/dspace-api/src/test/data/dspaceFolder/config/local.cfg b/dspace-api/src/test/data/dspaceFolder/config/local.cfg index 3c19a68e9fd1..258d53ee2f44 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/local.cfg +++ b/dspace-api/src/test/data/dspaceFolder/config/local.cfg @@ -84,14 +84,14 @@ loglevel.dspace = INFO # IIIF TEST SETTINGS # ######################## iiif.enabled = true -event.dispatcher.default.consumers = versioning, discovery, eperson, iiif +event.dispatcher.default.consumers = versioning, discovery, eperson, iiif, nbeventsdelete ########################################### # CUSTOM UNIT / INTEGRATION TEST SETTINGS # ########################################### # custom dispatcher to be used by dspace-api IT that doesn't need SOLR event.dispatcher.exclude-discovery.class = org.dspace.event.BasicDispatcher -event.dispatcher.exclude-discovery.consumers = versioning, eperson +event.dispatcher.exclude-discovery.consumers = versioning, eperson, nbeventsdelete # Configure authority control for Unit Testing (in DSpaceControlledVocabularyTest) # (This overrides default, commented out settings in dspace.cfg) diff --git a/dspace-api/src/test/data/dspaceFolder/config/spring/api/solr-services.xml b/dspace-api/src/test/data/dspaceFolder/config/spring/api/solr-services.xml index 5f86c7359890..ffffa7f44dbe 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/spring/api/solr-services.xml +++ b/dspace-api/src/test/data/dspaceFolder/config/spring/api/solr-services.xml @@ -47,5 +47,9 @@ + + + diff --git a/dspace-api/src/test/java/org/dspace/app/nbevent/MockNBEventService.java b/dspace-api/src/test/java/org/dspace/app/nbevent/MockNBEventService.java new file mode 100644 index 000000000000..12058fbf7351 --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/app/nbevent/MockNBEventService.java @@ -0,0 +1,38 @@ +/** + * 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.nbevent; + +import org.dspace.app.nbevent.service.impl.NBEventServiceImpl; +import org.dspace.solr.MockSolrServer; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.stereotype.Service; + +/** + * Mock SOLR service for the nbevents Core. + */ +@Service +public class MockNBEventService extends NBEventServiceImpl implements InitializingBean, DisposableBean { + private MockSolrServer mockSolrServer; + + @Override + public void afterPropertiesSet() throws Exception { + mockSolrServer = new MockSolrServer("nbevent"); + solr = mockSolrServer.getSolrServer(); + } + + /** Clear all records from the search core. */ + public void reset() { + mockSolrServer.reset(); + } + + @Override + public void destroy() throws Exception { + mockSolrServer.destroy(); + } +} \ No newline at end of file diff --git a/dspace-api/src/test/java/org/dspace/builder/AbstractBuilder.java b/dspace-api/src/test/java/org/dspace/builder/AbstractBuilder.java index 06deacaca473..2734a116280a 100644 --- a/dspace-api/src/test/java/org/dspace/builder/AbstractBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/AbstractBuilder.java @@ -13,6 +13,7 @@ import org.apache.commons.collections4.CollectionUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.dspace.app.nbevent.service.NBEventService; import org.dspace.app.requestitem.factory.RequestItemServiceFactory; import org.dspace.app.requestitem.service.RequestItemService; import org.dspace.authorize.AuthorizeException; @@ -45,6 +46,7 @@ import org.dspace.scripts.factory.ScriptServiceFactory; import org.dspace.scripts.service.ProcessService; import org.dspace.services.factory.DSpaceServicesFactory; +import org.dspace.utils.DSpace; import org.dspace.versioning.factory.VersionServiceFactory; import org.dspace.versioning.service.VersionHistoryService; import org.dspace.versioning.service.VersioningService; @@ -95,6 +97,7 @@ public abstract class AbstractBuilder { static ProcessService processService; static RequestItemService requestItemService; static VersioningService versioningService; + static NBEventService nbEventService; protected Context context; @@ -151,6 +154,8 @@ public static void init() { inProgressUserService = XmlWorkflowServiceFactory.getInstance().getInProgressUserService(); poolTaskService = XmlWorkflowServiceFactory.getInstance().getPoolTaskService(); workflowItemRoleService = XmlWorkflowServiceFactory.getInstance().getWorkflowItemRoleService(); + + nbEventService = new DSpace().getSingletonService(NBEventService.class); } @@ -183,7 +188,7 @@ public static void destroy() { processService = null; requestItemService = null; versioningService = null; - + nbEventService = null; } public static void cleanupObjects() throws Exception { diff --git a/dspace-api/src/test/java/org/dspace/builder/NBEventBuilder.java b/dspace-api/src/test/java/org/dspace/builder/NBEventBuilder.java new file mode 100644 index 000000000000..9101d3bf5e91 --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/builder/NBEventBuilder.java @@ -0,0 +1,125 @@ +/** + * 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.builder; + +import java.util.Date; + +import org.dspace.app.nbevent.service.NBEventService; +import org.dspace.content.Collection; +import org.dspace.content.Item; +import org.dspace.content.NBEvent; +import org.dspace.core.Context; + +/** + * Builder to construct Notification Broker Event objects + * + * @author Andrea Bollini (andrea.bollini at 4science.it) + */ +public class NBEventBuilder extends AbstractBuilder { + + private Item item; + private NBEvent target; + + private String title; + private String topic; + private String message; + private String relatedItem; + private double trust = 0.5; + private Date lastUpdate = new Date(); + + protected NBEventBuilder(Context context) { + super(context); + } + + public static NBEventBuilder createTarget(final Context context, final Collection col, final String name) { + NBEventBuilder builder = new NBEventBuilder(context); + return builder.create(context, col, name); + } + + public static NBEventBuilder createTarget(final Context context, final Item item) { + NBEventBuilder builder = new NBEventBuilder(context); + return builder.create(context, item); + } + + private NBEventBuilder create(final Context context, final Collection col, final String name) { + this.context = context; + + try { + ItemBuilder itemBuilder = ItemBuilder.createItem(context, col).withTitle(name); + item = itemBuilder.build(); + this.title = name; + context.dispatchEvents(); + indexingService.commit(); + } catch (Exception e) { + return handleException(e); + } + return this; + } + + private NBEventBuilder create(final Context context, final Item item) { + this.context = context; + this.item = item; + return this; + } + + public NBEventBuilder withTopic(final String topic) { + this.topic = topic; + return this; + } + public NBEventBuilder withTitle(final String title) { + this.title = title; + return this; + } + public NBEventBuilder withMessage(final String message) { + this.message = message; + return this; + } + public NBEventBuilder withTrust(final double trust) { + this.trust = trust; + return this; + } + public NBEventBuilder withLastUpdate(final Date lastUpdate) { + this.lastUpdate = lastUpdate; + return this; + } + + public NBEventBuilder withRelatedItem(String relatedItem) { + this.relatedItem = relatedItem; + return this; + } + + @Override + public NBEvent build() { + target = new NBEvent("oai:www.dspace.org:" + item.getHandle(), item.getID().toString(), title, topic, trust, + message, lastUpdate); + target.setRelated(relatedItem); + try { + nbEventService.store(context, target); + } catch (Exception e) { + e.printStackTrace(); + } + return target; + } + + @Override + public void cleanup() throws Exception { + nbEventService.deleteEventByEventId(context, target.getEventId()); + } + + @Override + protected NBEventService getService() { + return nbEventService; + } + + @Override + public void delete(Context c, NBEvent dso) throws Exception { + nbEventService.deleteEventByEventId(context, target.getEventId()); + +// nbEventService.deleteTarget(dso); + } +} \ No newline at end of file diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/NBEventRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/NBEventRestController.java new file mode 100644 index 000000000000..2411f4743d02 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/NBEventRestController.java @@ -0,0 +1,135 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest; + +import static org.dspace.app.rest.utils.RegexUtils.REGEX_REQUESTMAPPING_IDENTIFIER_AS_STRING_VERSION_STRONG; + +import java.io.IOException; +import java.sql.SQLException; +import java.util.UUID; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.lang3.StringUtils; +import org.dspace.app.nbevent.service.NBEventService; +import org.dspace.app.rest.converter.ConverterService; +import org.dspace.app.rest.exception.UnprocessableEntityException; +import org.dspace.app.rest.model.ItemRest; +import org.dspace.app.rest.model.hateoas.ItemResource; +import org.dspace.app.rest.utils.ContextUtil; +import org.dspace.app.rest.utils.Utils; +import org.dspace.authorize.AuthorizeException; +import org.dspace.content.Item; +import org.dspace.content.NBEvent; +import org.dspace.content.service.ItemService; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.rest.webmvc.ControllerUtils; +import org.springframework.data.rest.webmvc.ResourceNotFoundException; +import org.springframework.hateoas.RepresentationModel; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +/** + * This RestController will take care to manipulate the related item eventually associated with a nb event + * "/api/integration/nbevents/{nbeventid}/related" + */ +@RestController +@RequestMapping("/api/integration/nbevents" + REGEX_REQUESTMAPPING_IDENTIFIER_AS_STRING_VERSION_STRONG + "/related") +public class NBEventRestController { + @Autowired + protected Utils utils; + + @Autowired + private ConverterService converterService; + + @Autowired + private ItemService itemService; + + @Autowired + private NBEventService nbEventService; + + /** + * This method associate an item to a nb event + * + * @param nbeventId The nb event id + * @param response The current response + * @param request The current request + * @param relatedItemUUID The uuid of the related item to associate with the nb + * event + * @return The related item + * @throws SQLException If something goes wrong + * @throws AuthorizeException If something goes wrong + */ + @RequestMapping(method = RequestMethod.POST) + @PreAuthorize("hasAuthority('ADMIN')") + public ResponseEntity> postRelatedItem(@PathVariable(name = "id") String nbeventId, + HttpServletResponse response, HttpServletRequest request, + @RequestParam(required = true, name = "item") UUID relatedItemUUID) + throws SQLException, AuthorizeException { + Context context = ContextUtil.obtainContext(request); + NBEvent nbevent = nbEventService.findEventByEventId(context, nbeventId); + if (nbevent == null) { + throw new ResourceNotFoundException("No such nb event: " + nbeventId); + } + if (nbevent.getRelated() != null) { + throw new UnprocessableEntityException("The nb event with ID: " + nbeventId + " already has " + + "a related item"); + } else if (!StringUtils.endsWith(nbevent.getTopic(), "/PROJECT")) { + return ControllerUtils.toEmptyResponse(HttpStatus.BAD_REQUEST); + } + + Item relatedItem = itemService.find(context, relatedItemUUID); + if (relatedItem != null) { + nbevent.setRelated(relatedItemUUID.toString()); + nbEventService.store(context, nbevent); + } else { + throw new UnprocessableEntityException("The proposed related item was not found"); + } + ItemRest relatedItemRest = converterService.toRest(relatedItem, utils.obtainProjection()); + ItemResource itemResource = converterService.toResource(relatedItemRest); + context.complete(); + return ControllerUtils.toResponseEntity(HttpStatus.CREATED, new HttpHeaders(), itemResource); + } + + /** + * This method remove the association to a related item from a nb event + * + * @param nbeventId The nb event id + * @param response The current response + * @param request The current request + * @return The related item + * @throws SQLException If something goes wrong + * @throws AuthorizeException If something goes wrong + */ + @RequestMapping(method = RequestMethod.DELETE) + @PreAuthorize("hasAuthority('ADMIN')") + public ResponseEntity> deleteAdminGroup(@PathVariable(name = "id") String nbeventId, + HttpServletResponse response, HttpServletRequest request) + throws SQLException, AuthorizeException, IOException { + Context context = ContextUtil.obtainContext(request); + NBEvent nbevent = nbEventService.findEventByEventId(context, nbeventId); + if (nbevent == null) { + throw new ResourceNotFoundException("No such nb event: " + nbeventId); + } + if (nbevent.getRelated() != null) { + nbevent.setRelated(null); + nbEventService.store(context, nbevent); + context.complete(); + } + + return ControllerUtils.toEmptyResponse(HttpStatus.NO_CONTENT); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/RestResourceController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/RestResourceController.java index 7c79a857017b..3adc55c4f100 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/RestResourceController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/RestResourceController.java @@ -572,6 +572,37 @@ public ResponseEntity> upload(H return uploadInternal(request, apiCategory, model, uuid, uploadfile); } + /** + * Called in POST, multipart, upload to a specific rest resource the file passed as "file" request parameter + * + * Note that the regular expression in the request mapping accept a String as identifier; + * + * @param request + * the http request + * @param apiCategory + * the api category + * @param model + * the rest model that identify the REST resource collection + * @param id + * the id of the specific rest resource + * @param uploadfile + * the file to upload + * @return the created resource + * @throws HttpRequestMethodNotSupportedException + */ + @RequestMapping(method = RequestMethod.POST, + value = REGEX_REQUESTMAPPING_IDENTIFIER_AS_STRING_VERSION_STRONG, + headers = "content-type=multipart/form-data") + public ResponseEntity> upload(HttpServletRequest request, + @PathVariable String apiCategory, + @PathVariable String model, + @PathVariable String id, + @RequestParam("file") MultipartFile + uploadfile) + throws HttpRequestMethodNotSupportedException { + return uploadInternal(request, apiCategory, model, id, uploadfile); + } + /** * Internal upload method. * @@ -684,6 +715,28 @@ public ResponseEntity> patch(HttpServletRequest request, return patchInternal(request, apiCategory, model, id, jsonNode); } + /** + * PATCH method, using operation on the resources following (JSON) Patch notation (https://tools.ietf + * .org/html/rfc6902) + * + * Note that the regular expression in the request mapping accept a UUID as identifier; + * + * @param request + * @param apiCategory + * @param model + * @param id + * @param jsonNode + * @return + * @throws HttpRequestMethodNotSupportedException + */ + @RequestMapping(method = RequestMethod.PATCH, value = REGEX_REQUESTMAPPING_IDENTIFIER_AS_STRING_VERSION_STRONG) + public ResponseEntity> patch(HttpServletRequest request, @PathVariable String apiCategory, + @PathVariable String model, + @PathVariable String id, + @RequestBody(required = true) JsonNode jsonNode) { + return patchInternal(request, apiCategory, model, id, jsonNode); + } + /** * Internal patch method * @@ -711,9 +764,13 @@ public ResponseEntity> patchInt log.error(e.getMessage(), e); throw e; } - DSpaceResource result = converter.toResource(modelObject); - //TODO manage HTTPHeader - return ControllerUtils.toResponseEntity(HttpStatus.OK, new HttpHeaders(), result); + if (modelObject != null) { + DSpaceResource result = converter.toResource(modelObject); + //TODO manage HTTPHeader + return ControllerUtils.toResponseEntity(HttpStatus.OK, new HttpHeaders(), result); + } else { + return ControllerUtils.toEmptyResponse(HttpStatus.NO_CONTENT); + } } @@ -1050,6 +1107,13 @@ public ResponseEntity> delete(HttpServletRequest request, return deleteInternal(apiCategory, model, uuid); } + @RequestMapping(method = RequestMethod.DELETE, value = REGEX_REQUESTMAPPING_IDENTIFIER_AS_STRING_VERSION_STRONG) + public ResponseEntity> delete(HttpServletRequest request, @PathVariable String apiCategory, + @PathVariable String model, @PathVariable String id) + throws HttpRequestMethodNotSupportedException { + return deleteInternal(apiCategory, model, id); + } + /** * Internal method to delete resource. * diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/NBEventConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/NBEventConverter.java new file mode 100644 index 000000000000..3534e3c31097 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/NBEventConverter.java @@ -0,0 +1,74 @@ +/** + * 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.converter; + +import java.text.DecimalFormat; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; +import org.dspace.app.nbevent.service.dto.MessageDto; +import org.dspace.app.rest.model.NBEventMessageRest; +import org.dspace.app.rest.model.NBEventRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.content.NBEvent; +import org.springframework.stereotype.Component; + +@Component +public class NBEventConverter implements DSpaceConverter { + + private ObjectMapper jsonMapper; + + public NBEventConverter() { + super(); + jsonMapper = new JsonMapper(); + jsonMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + } + + @Override + public NBEventRest convert(NBEvent modelObject, Projection projection) { + NBEventRest rest = new NBEventRest(); + rest.setId(modelObject.getEventId()); + try { + rest.setMessage(convertMessage(jsonMapper.readValue(modelObject.getMessage(), MessageDto.class))); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + rest.setOriginalId(modelObject.getOriginalId()); + rest.setProjection(projection); + rest.setTitle(modelObject.getTitle()); + rest.setTopic(modelObject.getTopic()); + rest.setEventDate(modelObject.getLastUpdate()); + rest.setTrust(new DecimalFormat("0.000").format(modelObject.getTrust())); + // right now only the pending status can be found in persisted nb events + rest.setStatus(modelObject.getStatus()); + return rest; + } + + private NBEventMessageRest convertMessage(MessageDto dto) { + NBEventMessageRest message = new NBEventMessageRest(); + message.setAbstractValue(dto.getAbstracts()); + message.setOpenaireId(dto.getOpenaireId()); + message.setAcronym(dto.getAcronym()); + message.setCode(dto.getCode()); + message.setFunder(dto.getFunder()); + message.setFundingProgram(dto.getFundingProgram()); + message.setJurisdiction(dto.getJurisdiction()); + message.setTitle(dto.getTitle()); + message.setType(dto.getType()); + message.setValue(dto.getValue()); + return message; + } + + @Override + public Class getModelClass() { + return NBEvent.class; + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/NBTopicConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/NBTopicConverter.java new file mode 100644 index 000000000000..8a5a284fe1f6 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/NBTopicConverter.java @@ -0,0 +1,34 @@ +/** + * 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.converter; + +import org.dspace.app.nbevent.NBTopic; +import org.dspace.app.rest.model.NBTopicRest; +import org.dspace.app.rest.projection.Projection; +import org.springframework.stereotype.Component; + +@Component +public class NBTopicConverter implements DSpaceConverter { + + @Override + public Class getModelClass() { + return NBTopic.class; + } + + @Override + public NBTopicRest convert(NBTopic modelObject, Projection projection) { + NBTopicRest rest = new NBTopicRest(); + rest.setProjection(projection); + rest.setId(modelObject.getKey().replace("/", "!")); + rest.setName(modelObject.getKey()); + rest.setLastEvent(modelObject.getLastEvent()); + rest.setTotalEvents(modelObject.getTotalEvents()); + return rest; + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/NBEventMessageRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/NBEventMessageRest.java new file mode 100644 index 000000000000..7d0f24a21d2c --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/NBEventMessageRest.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.model; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class NBEventMessageRest { + // pids + private String type; + private String value; + // abstract + @JsonProperty(value = "abstract") + private String abstractValue; + // project + private String openaireId; + private String acronym; + private String code; + private String funder; + private String fundingProgram; + private String jurisdiction; + private String title; + public String getType() { + return type; + } + public void setType(String type) { + this.type = type; + } + public String getValue() { + return value; + } + public void setValue(String value) { + this.value = value; + } + public String getAbstractValue() { + return abstractValue; + } + public void setAbstractValue(String abstractValue) { + this.abstractValue = abstractValue; + } + public String getOpenaireId() { + return openaireId; + } + public void setOpenaireId(String openaireId) { + this.openaireId = openaireId; + } + public String getAcronym() { + return acronym; + } + public void setAcronym(String acronym) { + this.acronym = acronym; + } + public String getCode() { + return code; + } + public void setCode(String code) { + this.code = code; + } + public String getFunder() { + return funder; + } + public void setFunder(String funder) { + this.funder = funder; + } + public String getFundingProgram() { + return fundingProgram; + } + public void setFundingProgram(String fundingProgram) { + this.fundingProgram = fundingProgram; + } + public String getJurisdiction() { + return jurisdiction; + } + public void setJurisdiction(String jurisdiction) { + this.jurisdiction = jurisdiction; + } + public String getTitle() { + return title; + } + public void setTitle(String title) { + this.title = title; + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/NBEventRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/NBEventRest.java new file mode 100644 index 000000000000..de2cea32fa6c --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/NBEventRest.java @@ -0,0 +1,115 @@ +/** + * 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.model; + +import java.util.Date; + +import org.dspace.app.rest.RestResourceController; + +@LinksRest( + links = { + @LinkRest(name = "topic", method = "getTopic"), + @LinkRest(name = "target", method = "getTarget"), + @LinkRest(name = "related", method = "getRelated") + }) +public class NBEventRest extends BaseObjectRest { + + private static final long serialVersionUID = -5001130073350654793L; + public static final String NAME = "nbevent"; + public static final String CATEGORY = RestAddressableModel.INTEGRATION; + + public static final String TOPIC = "topic"; + public static final String TARGET = "target"; + public static final String RELATED = "related"; + private String originalId; + private String title; + private String topic; + private String trust; + private Date eventDate; + private NBEventMessageRest message; + private String status; + + @Override + public String getType() { + return NAME; + } + + @Override + public String getCategory() { + return CATEGORY; + } + + @Override + public Class getController() { + return RestResourceController.class; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getOriginalId() { + return originalId; + } + + public void setOriginalId(String originalId) { + this.originalId = originalId; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getTopic() { + return topic; + } + + public void setTopic(String topic) { + this.topic = topic; + } + + public String getTrust() { + return trust; + } + + public void setTrust(String trust) { + this.trust = trust; + } + + public Date getEventDate() { + return eventDate; + } + + public void setEventDate(Date eventDate) { + this.eventDate = eventDate; + } + + public NBEventMessageRest getMessage() { + return message; + } + + public void setMessage(NBEventMessageRest message) { + this.message = message; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/NBTopicRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/NBTopicRest.java new file mode 100644 index 000000000000..4bebce27e803 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/NBTopicRest.java @@ -0,0 +1,78 @@ +/** + * 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.model; + +import java.util.Date; + +import org.dspace.app.rest.RestResourceController; + +/** + * REST Representation of a notification broker topic + * + * @author Andrea Bollini (andrea.bollini at 4science.it) + * + */ +public class NBTopicRest extends BaseObjectRest { + + private static final long serialVersionUID = -7455358581579629244L; + + public static final String NAME = "nbtopic"; + public static final String CATEGORY = RestAddressableModel.INTEGRATION; + + private String id; + private String name; + private Date lastEvent; + private long totalEvents; + + @Override + public String getType() { + return NAME; + } + + @Override + public String getCategory() { + return CATEGORY; + } + + @Override + public Class getController() { + return RestResourceController.class; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Date getLastEvent() { + return lastEvent; + } + + public void setLastEvent(Date lastEvent) { + this.lastEvent = lastEvent; + } + + public long getTotalEvents() { + return totalEvents; + } + + public void setTotalEvents(long totalEvents) { + this.totalEvents = totalEvents; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/NBEventResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/NBEventResource.java new file mode 100644 index 000000000000..bd3c266f1eb6 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/NBEventResource.java @@ -0,0 +1,21 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.model.hateoas; + +import org.dspace.app.rest.model.NBEventRest; +import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource; +import org.dspace.app.rest.utils.Utils; + +@RelNameDSpaceResource(NBEventRest.NAME) +public class NBEventResource extends DSpaceResource { + + public NBEventResource(NBEventRest data, Utils utils) { + super(data, utils); + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/NBTopicResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/NBTopicResource.java new file mode 100644 index 000000000000..a2fed4ffc6ec --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/NBTopicResource.java @@ -0,0 +1,21 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.model.hateoas; + +import org.dspace.app.rest.model.NBTopicRest; +import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource; +import org.dspace.app.rest.utils.Utils; + +@RelNameDSpaceResource(NBTopicRest.NAME) +public class NBTopicResource extends DSpaceResource { + + public NBTopicResource(NBTopicRest data, Utils utils) { + super(data, utils); + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NBEventRelatedLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NBEventRelatedLinkRepository.java new file mode 100644 index 000000000000..3ec4660c4abf --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NBEventRelatedLinkRepository.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.repository; + +import java.sql.SQLException; +import java.util.UUID; +import javax.annotation.Nullable; +import javax.servlet.http.HttpServletRequest; + +import org.dspace.app.nbevent.service.NBEventService; +import org.dspace.app.rest.model.ItemRest; +import org.dspace.app.rest.model.NBEventRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.content.Item; +import org.dspace.content.NBEvent; +import org.dspace.content.service.ItemService; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Pageable; +import org.springframework.data.rest.webmvc.ResourceNotFoundException; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Component; + +/** + * Link repository for "related" subresource of a nb event. + * + * @author Andrea Bollini (andrea.bollini at 4science.it) + * + */ +@Component(NBEventRest.CATEGORY + "." + NBEventRest.NAME + "." + NBEventRest.RELATED) +public class NBEventRelatedLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { + + @Autowired + private NBEventService nbEventService; + + @Autowired + private ItemService itemService; + + /** + * Returns the item related to the nb event with the given id. This is another + * item that should be linked to the target item as part of the correction + * + * @param request the http servlet request + * @param id the nb event id + * @param pageable the optional pageable + * @param projection the projection object + * @return the item rest representation of the secondary item related to nb event + */ + @PreAuthorize("hasAuthority('ADMIN')") + public ItemRest getRelated(@Nullable HttpServletRequest request, String id, @Nullable Pageable pageable, + Projection projection) { + Context context = obtainContext(); + NBEvent nbEvent = nbEventService.findEventByEventId(context, id); + if (nbEvent == null) { + throw new ResourceNotFoundException("No nb event with ID: " + id); + } + if (nbEvent.getRelated() == null) { + return null; + } + UUID itemUuid = UUID.fromString(nbEvent.getRelated()); + Item item; + try { + item = itemService.find(context, itemUuid); + if (item == null) { + throw new ResourceNotFoundException("No related item found with id : " + id); + } + return converter.toRest(item, projection); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NBEventRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NBEventRestRepository.java new file mode 100644 index 000000000000..b00688a6ea95 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NBEventRestRepository.java @@ -0,0 +1,127 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.repository; + +import java.sql.SQLException; +import java.util.List; +import java.util.UUID; +import javax.servlet.http.HttpServletRequest; + +import org.dspace.app.nbevent.dao.NBEventsDao; +import org.dspace.app.nbevent.service.NBEventService; +import org.dspace.app.rest.Parameter; +import org.dspace.app.rest.SearchRestMethod; +import org.dspace.app.rest.exception.RepositoryMethodNotImplementedException; +import org.dspace.app.rest.model.NBEventRest; +import org.dspace.app.rest.model.patch.Patch; +import org.dspace.app.rest.repository.patch.ResourcePatch; +import org.dspace.authorize.AuthorizeException; +import org.dspace.content.Item; +import org.dspace.content.NBEvent; +import org.dspace.content.service.ItemService; +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; +import org.dspace.eperson.service.EPersonService; +import org.slf4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort.Direction; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Component; + +@Component(NBEventRest.CATEGORY + "." + NBEventRest.NAME) +public class NBEventRestRepository extends DSpaceRestRepository { + + final static String ORDER_FIELD = "trust"; + + @Autowired + private NBEventService nbEventService; + + @Autowired + private NBEventsDao nbEventDao; + + @Autowired + private ItemService itemService; + + @Autowired + private EPersonService ePersonService; + + @Autowired + private ResourcePatch resourcePatch; + + private Logger log = org.slf4j.LoggerFactory.getLogger(NBEventRestRepository.class); + + @Override + @PreAuthorize("hasAuthority('ADMIN')") + public NBEventRest findOne(Context context, String id) { + NBEvent nbEvent = nbEventService.findEventByEventId(context, id); + if (nbEvent == null) { + // HACK check if this request is part of a patch flow + nbEvent = (NBEvent) requestService.getCurrentRequest().getAttribute("patchedNotificationEvent"); + if (nbEvent != null && nbEvent.getEventId().contentEquals(id)) { + return converter.toRest(nbEvent, utils.obtainProjection()); + } else { + return null; + } + } + return converter.toRest(nbEvent, utils.obtainProjection()); + } + + @SearchRestMethod(name = "findByTopic") + @PreAuthorize("hasAuthority('ADMIN')") + public Page findByTopic(Context context, @Parameter(value = "topic", required = true) String topic, + Pageable pageable) { + List nbEvents = null; + Long count = 0L; + boolean ascending = false; + if (pageable.getSort() != null && pageable.getSort().getOrderFor(ORDER_FIELD) != null) { + ascending = pageable.getSort().getOrderFor(ORDER_FIELD).getDirection() == Direction.ASC; + } + nbEvents = nbEventService.findEventsByTopicAndPage(context, topic, + pageable.getOffset(), pageable.getPageSize(), + ORDER_FIELD, ascending); + count = nbEventService.countEventsByTopic(context, topic); + if (nbEvents == null) { + return null; + } + return converter.toRestPage(nbEvents, pageable, count, utils.obtainProjection()); + } + + @Override + protected void delete(Context context, String id) throws AuthorizeException { + Item item; + try { + item = itemService.find(context, UUID.fromString(id)); + EPerson eperson = context.getCurrentUser(); + nbEventService.deleteEventByEventId(context, id); + nbEventDao.storeEvent(context, id, eperson, item); + } catch (SQLException e) { + throw new RuntimeException("Unable to delete NBEvent " + id, e); + } + } + + @Override + public Page findAll(Context context, Pageable pageable) { + throw new RepositoryMethodNotImplementedException(NBEventRest.NAME, "findAll"); + } + + @Override + @PreAuthorize("hasAuthority('ADMIN')") + protected void patch(Context context, HttpServletRequest request, String apiCategory, String model, + String id, Patch patch) throws SQLException, AuthorizeException { + NBEvent nbEvent = nbEventService.findEventByEventId(context, id); + resourcePatch.patch(context, nbEvent, patch.getOperations()); + } + + @Override + public Class getDomainClass() { + return NBEventRest.class; + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NBEventTargetLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NBEventTargetLinkRepository.java new file mode 100644 index 000000000000..5c73f4d244c9 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NBEventTargetLinkRepository.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.rest.repository; + +import java.sql.SQLException; +import java.util.UUID; +import javax.annotation.Nullable; +import javax.servlet.http.HttpServletRequest; + +import org.dspace.app.nbevent.service.NBEventService; +import org.dspace.app.rest.model.ItemRest; +import org.dspace.app.rest.model.NBEventRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.content.Item; +import org.dspace.content.NBEvent; +import org.dspace.content.service.ItemService; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Pageable; +import org.springframework.data.rest.webmvc.ResourceNotFoundException; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Component; + +/** + * Link repository for "target" subresource of a nb event. + * + * @author Andrea Bollini (andrea.bollini at 4science.it) + * + */ +@Component(NBEventRest.CATEGORY + "." + NBEventRest.NAME + "." + NBEventRest.TARGET) +public class NBEventTargetLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { + + @Autowired + private NBEventService nbEventService; + + @Autowired + private ItemService itemService; + + /** + * Returns the item target of the nb event with the given id. + * + * @param request the http servlet request + * @param id the nb event id + * @param pageable the optional pageable + * @param projection the projection object + * @return the item rest representation of the nb event target + */ + @PreAuthorize("hasAuthority('ADMIN')") + public ItemRest getTarget(@Nullable HttpServletRequest request, String id, @Nullable Pageable pageable, + Projection projection) { + Context context = obtainContext(); + NBEvent nbEvent = nbEventService.findEventByEventId(context, id); + if (nbEvent == null) { + throw new ResourceNotFoundException("No nb event with ID: " + id); + } + UUID itemUuid = UUID.fromString(nbEvent.getTarget()); + Item item; + try { + item = itemService.find(context, itemUuid); + if (item == null) { + throw new ResourceNotFoundException("No target item found with id : " + id); + } + return converter.toRest(item, projection); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NBEventTopicLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NBEventTopicLinkRepository.java new file mode 100644 index 000000000000..94da3b8a507a --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NBEventTopicLinkRepository.java @@ -0,0 +1,61 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.repository; + +import javax.annotation.Nullable; +import javax.servlet.http.HttpServletRequest; + +import org.dspace.app.nbevent.NBTopic; +import org.dspace.app.nbevent.service.NBEventService; +import org.dspace.app.rest.model.NBEventRest; +import org.dspace.app.rest.model.NBTopicRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.content.NBEvent; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Pageable; +import org.springframework.data.rest.webmvc.ResourceNotFoundException; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Component; + +/** + * Link repository for "topic" subresource of a nb event. + * + * @author Andrea Bollini (andrea.bollini at 4science.it) + * + */ +@Component(NBEventRest.CATEGORY + "." + NBEventRest.NAME + "." + NBEventRest.TOPIC) +public class NBEventTopicLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { + + @Autowired + private NBEventService nbEventService; + + /** + * Returns the topic of the nb event with the given id. + * + * @param request the http servlet request + * @param id the nb event id + * @param pageable the optional pageable + * @param projection the projection object + * @return the nb topic rest representation + */ + @PreAuthorize("hasAuthority('ADMIN')") + public NBTopicRest getTopic(@Nullable HttpServletRequest request, String id, @Nullable Pageable pageable, + Projection projection) { + Context context = obtainContext(); + NBEvent nbEvent = nbEventService.findEventByEventId(context, id); + if (nbEvent == null) { + throw new ResourceNotFoundException("No nb event with ID: " + id); + } + NBTopic topic = nbEventService.findTopicByTopicId(nbEvent.getTopic().replace("/", "!")); + if (topic == null) { + throw new ResourceNotFoundException("No topic found with id : " + id); + } + return converter.toRest(topic, projection); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NBTopicRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NBTopicRestRepository.java new file mode 100644 index 000000000000..afaf3c7346ef --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NBTopicRestRepository.java @@ -0,0 +1,54 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.repository; + +import java.util.List; + +import org.dspace.app.nbevent.NBTopic; +import org.dspace.app.nbevent.service.NBEventService; +import org.dspace.app.rest.model.NBTopicRest; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Component; + +@Component(NBTopicRest.CATEGORY + "." + NBTopicRest.NAME) +public class NBTopicRestRepository extends DSpaceRestRepository { + + @Autowired + private NBEventService nbEventService; + + @Override + @PreAuthorize("hasAuthority('ADMIN')") + public NBTopicRest findOne(Context context, String id) { + NBTopic nbTopic = nbEventService.findTopicByTopicId(id); + if (nbTopic == null) { + return null; + } + return converter.toRest(nbTopic, utils.obtainProjection()); + } + + @Override + @PreAuthorize("hasAuthority('ADMIN')") + public Page findAll(Context context, Pageable pageable) { + List nbTopics = nbEventService.findAllTopics(context, pageable.getOffset(), pageable.getPageSize()); + long count = nbEventService.countTopics(context); + if (nbTopics == null) { + return null; + } + return converter.toRestPage(nbTopics, pageable, count, utils.obtainProjection()); + } + + @Override + public Class getDomainClass() { + return NBTopicRest.class; + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/NBEventStatusReplaceOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/NBEventStatusReplaceOperation.java new file mode 100644 index 000000000000..bd690ee683ec --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/NBEventStatusReplaceOperation.java @@ -0,0 +1,54 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.repository.patch.operation; + +import java.sql.SQLException; + +import org.apache.commons.lang3.StringUtils; +import org.dspace.app.nbevent.NBEventActionService; +import org.dspace.app.rest.model.patch.Operation; +import org.dspace.content.NBEvent; +import org.dspace.core.Context; +import org.dspace.services.RequestService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +public class NBEventStatusReplaceOperation extends PatchOperation { + @Autowired + private RequestService requestService; + + @Autowired + private NBEventActionService nbEventActionService; + + @Override + public NBEvent perform(Context context, NBEvent nbevent, Operation operation) throws SQLException { + String value = (String) operation.getValue(); + if (StringUtils.equalsIgnoreCase(value, NBEvent.ACCEPTED)) { + nbEventActionService.accept(context, nbevent); + } else if (StringUtils.equalsIgnoreCase(value, NBEvent.REJECTED)) { + nbEventActionService.reject(context, nbevent); + } else if (StringUtils.equalsIgnoreCase(value, NBEvent.DISCARDED)) { + nbEventActionService.discard(context, nbevent); + } else { + throw new IllegalArgumentException( + "The received operation is not valid: " + operation.getPath() + " - " + value); + } + nbevent.setStatus(value.toUpperCase()); + // HACK, we need to store the temporary object in the request so that a subsequent find would get it + requestService.getCurrentRequest().setAttribute("patchedNotificationEvent", nbevent); + return nbevent; + } + + @Override + public boolean supports(Object objectToMatch, Operation operation) { + return StringUtils.equals(operation.getOp(), "replace") && objectToMatch instanceof NBEvent && StringUtils + .containsAny(operation.getValue().toString().toLowerCase(), NBEvent.ACCEPTED, NBEvent.DISCARDED, + NBEvent.REJECTED); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/RegexUtils.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/RegexUtils.java index 8739f6b8d57f..8db5a74eefba 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/RegexUtils.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/RegexUtils.java @@ -33,7 +33,7 @@ private RegexUtils(){} */ public static final String REGEX_REQUESTMAPPING_IDENTIFIER_AS_STRING_VERSION_STRONG = "/{id:^(?!^\\d+$)" + "(?!^[0-9a-fxA-FX]{8}-[0-9a-fxA-FX]{4}-[0-9a-fxA-FX]{4}-[0-9a-fxA-FX]{4}-[0-9a-fxA-FX]{12}$)" - + "[\\w+\\-\\.:]+$+}"; + + "[\\w+\\-\\.:!]+$+}"; /** * Regular expression in the request mapping to accept number as identifier diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/NBEventRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/NBEventRestRepositoryIT.java new file mode 100644 index 000000000000..ef9abfe9782e --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/NBEventRestRepositoryIT.java @@ -0,0 +1,709 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest; + +import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; +import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasNoJsonPath; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.util.ArrayList; +import java.util.List; +import javax.ws.rs.core.MediaType; + +import org.dspace.app.rest.matcher.ItemMatcher; +import org.dspace.app.rest.matcher.NBEventMatcher; +import org.dspace.app.rest.model.patch.Operation; +import org.dspace.app.rest.model.patch.ReplaceOperation; +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.EntityTypeBuilder; +import org.dspace.builder.ItemBuilder; +import org.dspace.builder.NBEventBuilder; +import org.dspace.builder.RelationshipTypeBuilder; +import org.dspace.content.Collection; +import org.dspace.content.EntityType; +import org.dspace.content.Item; +import org.dspace.content.NBEvent; +import org.hamcrest.Matchers; +import org.junit.Test; + +public class NBEventRestRepositoryIT extends AbstractControllerIntegrationTest { + + @Test + public void findAllNotImplementedTest() throws Exception { + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(get("/api/integration/nbevents")).andExpect(status().isMethodNotAllowed()); + String epersonToken = getAuthToken(admin.getEmail(), password); + getClient(epersonToken).perform(get("/api/integration/nbevents")).andExpect(status().isMethodNotAllowed()); + getClient().perform(get("/api/integration/nbevents")).andExpect(status().isMethodNotAllowed()); + } + + @Test + public void findOneTest() throws Exception { + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context).withName("Parent Community").build(); + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build(); + NBEvent event1 = NBEventBuilder.createTarget(context, col1, "Science and Freedom") + .withTopic("ENRICH/MISSING/PID") + .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144300\"}").build(); + NBEvent event4 = NBEventBuilder.createTarget(context, col1, "Science and Freedom 4") + .withTopic("ENRICH/MISSING/ABSTRACT") + .withMessage("{\"abstracts[0]\": \"Descrizione delle caratteristiche...\"}").build(); + context.restoreAuthSystemState(); + String authToken = getAuthToken(admin.getEmail(), password); + getClient(authToken).perform(get("/api/integration/nbevents/" + event1.getEventId())).andExpect(status().isOk()) + .andExpect(jsonPath("$", NBEventMatcher.matchNBEventEntry(event1))); + getClient(authToken).perform(get("/api/integration/nbevents/" + event4.getEventId())).andExpect(status().isOk()) + .andExpect(jsonPath("$", NBEventMatcher.matchNBEventEntry(event4))); + } + + @Test + public void findOneWithProjectionTest() throws Exception { + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context).withName("Parent Community").build(); + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build(); + NBEvent event1 = NBEventBuilder.createTarget(context, col1, "Science and Freedom") + .withTopic("ENRICH/MISSING/PID") + .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144300\"}").build(); + NBEvent event5 = NBEventBuilder.createTarget(context, col1, "Science and Freedom 5") + .withTopic("ENRICH/MISSING/PROJECT") + .withMessage( + "{\"projects[0].acronym\":\"PAThs\"," + + "\"projects[0].code\":\"687567\"," + + "\"projects[0].funder\":\"EC\"," + + "\"projects[0].fundingProgram\":\"H2020\"," + + "\"projects[0].jurisdiction\":\"EU\"," + + "\"projects[0].openaireId\":\"40|corda__h2020::6e32f5eb912688f2424c68b851483ea4\"," + + "\"projects[0].title\":\"Tracking Papyrus and Parchment Paths: " + + "An Archaeological Atlas of Coptic Literature." + + "\\nLiterary Texts in their Geographical Context: Production, Copying, Usage, " + + "Dissemination and Storage\"}") + .build(); + context.restoreAuthSystemState(); + String authToken = getAuthToken(admin.getEmail(), password); + getClient(authToken) + .perform(get("/api/integration/nbevents/" + event1.getEventId()).param("projection", "full")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", NBEventMatcher.matchNBEventFullEntry(event1))); + getClient(authToken) + .perform(get("/api/integration/nbevents/" + event5.getEventId()).param("projection", "full")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", NBEventMatcher.matchNBEventFullEntry(event5))); + } + + @Test + public void findOneUnauthorizedTest() throws Exception { + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context).withName("Parent Community").build(); + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build(); + NBEvent event1 = NBEventBuilder.createTarget(context, col1, "Science and Freedom") + .withTopic("ENRICH/MISSING/PID") + .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144300\"}").build(); + context.restoreAuthSystemState(); + getClient().perform(get("/api/integration/nbevents/" + event1.getEventId())) + .andExpect(status().isUnauthorized()); + } + + @Test + public void findOneForbiddenTest() throws Exception { + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context).withName("Parent Community").build(); + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build(); + NBEvent event1 = NBEventBuilder.createTarget(context, col1, "Science and Freedom") + .withTopic("ENRICH/MISSING/PID") + .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144300\"}").build(); + context.restoreAuthSystemState(); + String authToken = getAuthToken(eperson.getEmail(), password); + getClient(authToken).perform(get("/api/integration/nbevents/" + event1.getEventId())) + .andExpect(status().isForbidden()); + } + + @Test + public void findByTopicTest() throws Exception { + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context).withName("Parent Community").build(); + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build(); + NBEvent event1 = NBEventBuilder.createTarget(context, col1, "Science and Freedom") + .withTopic("ENRICH/MISSING/PID") + .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144300\"}").build(); + NBEvent event2 = NBEventBuilder.createTarget(context, col1, "Science and Freedom 2") + .withTopic("ENRICH/MISSING/PID") + .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144301\"}").build(); + NBEvent event3 = NBEventBuilder.createTarget(context, col1, "Science and Freedom 3") + .withTopic("ENRICH/MORE/PID") + .withMessage("{\"pids[0].type\":\"pmid\",\"pids[0].value\":\"10.2307/2144302\"}").build(); + NBEvent event4 = NBEventBuilder.createTarget(context, col1, "Science and Freedom 4") + .withTopic("ENRICH/MISSING/ABSTRACT") + .withMessage("{\"abstracts[0]\": \"Descrizione delle caratteristiche...\"}").build(); + context.restoreAuthSystemState(); + String authToken = getAuthToken(admin.getEmail(), password); + getClient(authToken) + .perform(get("/api/integration/nbevents/search/findByTopic").param("topic", "ENRICH!MISSING!PID")) + .andExpect(status().isOk()).andExpect(jsonPath("$._embedded.nbevents", Matchers.hasSize(2))) + .andExpect(jsonPath("$._embedded.nbevents", + Matchers.containsInAnyOrder(NBEventMatcher.matchNBEventEntry(event1), + NBEventMatcher.matchNBEventEntry(event2)))) + .andExpect(jsonPath("$.page.size", is(20))).andExpect(jsonPath("$.page.totalElements", is(2))); + getClient(authToken) + .perform(get("/api/integration/nbevents/search/findByTopic").param("topic", "ENRICH!MISSING!ABSTRACT")) + .andExpect(status().isOk()).andExpect(jsonPath("$._embedded.nbevents", Matchers.hasSize(1))) + .andExpect(jsonPath("$._embedded.nbevents", + Matchers.containsInAnyOrder(NBEventMatcher.matchNBEventEntry(event4)))) + .andExpect(jsonPath("$.page.size", is(20))).andExpect(jsonPath("$.page.totalElements", is(1))); + getClient(authToken).perform(get("/api/integration/nbevents/search/findByTopic").param("topic", "not-existing")) + .andExpect(status().isOk()).andExpect(jsonPath("$.page.size", is(20))) + .andExpect(jsonPath("$.page.totalElements", is(0))); + } + + @Test + public void findByTopicPaginatedTest() throws Exception { + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context).withName("Parent Community").build(); + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build(); + NBEvent event1 = NBEventBuilder.createTarget(context, col1, "Science and Freedom") + .withTopic("ENRICH/MISSING/PID") + .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144300\"}").build(); + NBEvent event2 = NBEventBuilder.createTarget(context, col1, "Science and Freedom 2") + .withTopic("ENRICH/MISSING/PID") + .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144301\"}").build(); + NBEvent event3 = NBEventBuilder.createTarget(context, col1, "Science and Freedom 3") + .withTopic("ENRICH/MISSING/PID") + .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144302\"}").build(); + NBEvent event4 = NBEventBuilder.createTarget(context, col1, "Science and Freedom 4") + .withTopic("ENRICH/MISSING/PID") + .withMessage("{\"pids[0].type\":\"pmc\",\"pids[0].value\":\"2144303\"}").build(); + NBEvent event5 = NBEventBuilder.createTarget(context, col1, "Science and Freedom 5") + .withTopic("ENRICH/MISSING/PID") + .withMessage("{\"pids[0].type\":\"pmid\",\"pids[0].value\":\"2144304\"}").build(); + context.restoreAuthSystemState(); + String authToken = getAuthToken(admin.getEmail(), password); + getClient(authToken) + .perform(get("/api/integration/nbevents/search/findByTopic").param("topic", "ENRICH!MISSING!PID") + .param("size", "2")) + .andExpect(status().isOk()).andExpect(jsonPath("$._embedded.nbevents", Matchers.hasSize(2))) + .andExpect(jsonPath("$._embedded.nbevents", + Matchers.containsInAnyOrder( + NBEventMatcher.matchNBEventEntry(event1), + NBEventMatcher.matchNBEventEntry(event2)))) + .andExpect(jsonPath("$._links.self.href", + Matchers.allOf( + Matchers.containsString("/api/integration/nbevents/search/findByTopic?"), + Matchers.containsString("topic=ENRICH!MISSING!PID"), + Matchers.containsString("size=2")))) + .andExpect(jsonPath("$._links.next.href", + Matchers.allOf( + Matchers.containsString("/api/integration/nbevents/search/findByTopic?"), + Matchers.containsString("topic=ENRICH!MISSING!PID"), Matchers.containsString("page=1"), + Matchers.containsString("size=2")))) + .andExpect(jsonPath("$._links.last.href", + Matchers.allOf( + Matchers.containsString("/api/integration/nbevents/search/findByTopic?"), + Matchers.containsString("topic=ENRICH!MISSING!PID"), Matchers.containsString("page=2"), + Matchers.containsString("size=2")))) + .andExpect(jsonPath("$._links.first.href", + Matchers.allOf( + Matchers.containsString("/api/integration/nbevents/search/findByTopic?"), + Matchers.containsString("topic=ENRICH!MISSING!PID"), Matchers.containsString("page=0"), + Matchers.containsString("size=2")))) + .andExpect(jsonPath("$._links.prev.href").doesNotExist()) + .andExpect(jsonPath("$.page.size", is(2))) + .andExpect(jsonPath("$.page.totalPages", is(3))) + .andExpect(jsonPath("$.page.totalElements", is(5))); + + getClient(authToken) + .perform(get("/api/integration/nbevents/search/findByTopic").param("topic", "ENRICH!MISSING!PID") + .param("size", "2").param("page", "1")) + .andExpect(status().isOk()).andExpect(jsonPath("$._embedded.nbevents", Matchers.hasSize(2))) + .andExpect(jsonPath("$._embedded.nbevents", + Matchers.containsInAnyOrder( + NBEventMatcher.matchNBEventEntry(event3), + NBEventMatcher.matchNBEventEntry(event4)))) + .andExpect(jsonPath("$._links.self.href", + Matchers.allOf( + Matchers.containsString("/api/integration/nbevents/search/findByTopic?"), + Matchers.containsString("topic=ENRICH!MISSING!PID"), Matchers.containsString("page=1"), + Matchers.containsString("size=2")))) + .andExpect(jsonPath("$._links.next.href", + Matchers.allOf( + Matchers.containsString("/api/integration/nbevents/search/findByTopic?"), + Matchers.containsString("topic=ENRICH!MISSING!PID"), Matchers.containsString("page=2"), + Matchers.containsString("size=2")))) + .andExpect(jsonPath("$._links.last.href", + Matchers.allOf( + Matchers.containsString("/api/integration/nbevents/search/findByTopic?"), + Matchers.containsString("topic=ENRICH!MISSING!PID"), Matchers.containsString("page=2"), + Matchers.containsString("size=2")))) + .andExpect(jsonPath("$._links.first.href", + Matchers.allOf( + Matchers.containsString("/api/integration/nbevents/search/findByTopic?"), + Matchers.containsString("topic=ENRICH!MISSING!PID"), Matchers.containsString("page=0"), + Matchers.containsString("size=2")))) + .andExpect(jsonPath("$._links.prev.href", + Matchers.allOf( + Matchers.containsString("/api/integration/nbevents/search/findByTopic?"), + Matchers.containsString("topic=ENRICH!MISSING!PID"), Matchers.containsString("page=0"), + Matchers.containsString("size=2")))) + .andExpect(jsonPath("$.page.size", is(2))) + .andExpect(jsonPath("$.page.totalPages", is(3))) + .andExpect(jsonPath("$.page.totalElements", is(5))); + + getClient(authToken) + .perform(get("/api/integration/nbevents/search/findByTopic").param("topic", "ENRICH!MISSING!PID") + .param("size", "2").param("page", "2")) + .andExpect(status().isOk()).andExpect(jsonPath("$._embedded.nbevents", Matchers.hasSize(1))) + .andExpect(jsonPath("$._embedded.nbevents", + Matchers.containsInAnyOrder( + NBEventMatcher.matchNBEventEntry(event5)))) + .andExpect(jsonPath("$._links.self.href", + Matchers.allOf( + Matchers.containsString("/api/integration/nbevents/search/findByTopic?"), + Matchers.containsString("topic=ENRICH!MISSING!PID"), Matchers.containsString("page=2"), + Matchers.containsString("size=2")))) + .andExpect(jsonPath("$._links.next.href").doesNotExist()) + .andExpect(jsonPath("$._links.last.href", + Matchers.allOf( + Matchers.containsString("/api/integration/nbevents/search/findByTopic?"), + Matchers.containsString("topic=ENRICH!MISSING!PID"), Matchers.containsString("page=2"), + Matchers.containsString("size=2")))) + .andExpect(jsonPath("$._links.first.href", + Matchers.allOf( + Matchers.containsString("/api/integration/nbevents/search/findByTopic?"), + Matchers.containsString("topic=ENRICH!MISSING!PID"), Matchers.containsString("page=0"), + Matchers.containsString("size=2")))) + .andExpect(jsonPath("$._links.prev.href", + Matchers.allOf( + Matchers.containsString("/api/integration/nbevents/search/findByTopic?"), + Matchers.containsString("topic=ENRICH!MISSING!PID"), Matchers.containsString("page=1"), + Matchers.containsString("size=2")))) + .andExpect(jsonPath("$.page.size", is(2))) + .andExpect(jsonPath("$.page.totalPages", is(3))) + .andExpect(jsonPath("$.page.totalElements", is(5))); + + } + + @Test + public void findByTopicUnauthorizedTest() throws Exception { + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context).withName("Parent Community").build(); + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build(); + NBEvent event1 = NBEventBuilder.createTarget(context, col1, "Science and Freedom") + .withTopic("ENRICH/MISSING/PID") + .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144300\"}").build(); + NBEvent event2 = NBEventBuilder.createTarget(context, col1, "Science and Freedom 2") + .withTopic("ENRICH/MISSING/PID") + .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144301\"}").build(); + NBEvent event3 = NBEventBuilder.createTarget(context, col1, "Science and Freedom 3") + .withTopic("ENRICH/MORE/PID") + .withMessage("{\"pids[0].type\":\"pmid\",\"pids[0].value\":\"10.2307/2144302\"}").build(); + NBEvent event4 = NBEventBuilder.createTarget(context, col1, "Science and Freedom 4") + .withTopic("ENRICH/MISSING/ABSTRACT") + .withMessage("{\"abstracts[0]\": \"Descrizione delle caratteristiche...\"}").build(); + context.restoreAuthSystemState(); + getClient().perform(get("/api/integration/nbevents/search/findByTopic").param("topic", "ENRICH!MISSING!PID")) + .andExpect(status().isUnauthorized()); + } + + @Test + public void findByTopicForbiddenTest() throws Exception { + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context).withName("Parent Community").build(); + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build(); + NBEvent event1 = NBEventBuilder.createTarget(context, col1, "Science and Freedom") + .withTopic("ENRICH/MISSING/PID") + .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144300\"}").build(); + NBEvent event2 = NBEventBuilder.createTarget(context, col1, "Science and Freedom 2") + .withTopic("ENRICH/MISSING/PID") + .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144301\"}").build(); + NBEvent event3 = NBEventBuilder.createTarget(context, col1, "Science and Freedom 3") + .withTopic("ENRICH/MORE/PID") + .withMessage("{\"pids[0].type\":\"pmid\",\"pids[0].value\":\"10.2307/2144302\"}").build(); + NBEvent event4 = NBEventBuilder.createTarget(context, col1, "Science and Freedom 4") + .withTopic("ENRICH/MISSING/ABSTRACT") + .withMessage("{\"abstracts[0]\": \"Descrizione delle caratteristiche...\"}").build(); + context.restoreAuthSystemState(); + String epersonToken = getAuthToken(eperson.getEmail(), password); + getClient(epersonToken) + .perform(get("/api/integration/nbevents/search/findByTopic").param("topic", "ENRICH!MISSING!PID")) + .andExpect(status().isForbidden()); + } + + @Test + public void findByTopicBadRequestTest() throws Exception { + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context).withName("Parent Community").build(); + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build(); + NBEvent event1 = NBEventBuilder.createTarget(context, col1, "Science and Freedom") + .withTopic("ENRICH/MISSING/PID") + .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144300\"}").build(); + NBEvent event2 = NBEventBuilder.createTarget(context, col1, "Science and Freedom 2") + .withTopic("ENRICH/MISSING/PID") + .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144301\"}").build(); + NBEvent event3 = NBEventBuilder.createTarget(context, col1, "Science and Freedom 3") + .withTopic("ENRICH/MORE/PID") + .withMessage("{\"pids[0].type\":\"pmid\",\"pids[0].value\":\"10.2307/2144302\"}").build(); + NBEvent event4 = NBEventBuilder.createTarget(context, col1, "Science and Freedom 4") + .withTopic("ENRICH/MISSING/ABSTRACT") + .withMessage("{\"abstracts[0]\": \"Descrizione delle caratteristiche...\"}").build(); + context.restoreAuthSystemState(); + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(get("/api/integration/nbevents/search/findByTopic")) + .andExpect(status().isBadRequest()); + } + + @Test + public void recordDecisionTest() throws Exception { + context.turnOffAuthorisationSystem(); + EntityType publication = EntityTypeBuilder.createEntityTypeBuilder(context, "Publication").build(); + EntityType project = EntityTypeBuilder.createEntityTypeBuilder(context, "Project").build(); + RelationshipTypeBuilder.createRelationshipTypeBuilder(context, publication, project, "isProjectOfPublication", + "isPublicationOfProject", 0, null, 0, + null).withCopyToRight(true).build(); + parentCommunity = CommunityBuilder.createCommunity(context).withName("Parent Community").build(); + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity) + .withEntityType("Publication") + .withName("Collection 1").build(); + Collection colFunding = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection Fundings") + .withEntityType("Project").build(); + Item funding = ItemBuilder.createItem(context, colFunding).withTitle("Tracking Papyrus and Parchment Paths") + .build(); + NBEvent eventProjectBound = NBEventBuilder.createTarget(context, col1, "Science and Freedom with project") + .withTopic("ENRICH/MISSING/PROJECT") + .withMessage( + "{\"projects[0].acronym\":\"PAThs\"," + + "\"projects[0].code\":\"687567\"," + + "\"projects[0].funder\":\"EC\"," + + "\"projects[0].fundingProgram\":\"H2020\"," + + "\"projects[0].jurisdiction\":\"EU\"," + + "\"projects[0].openaireId\":\"40|corda__h2020::6e32f5eb912688f2424c68b851483ea4\"," + + "\"projects[0].title\":\"Tracking Papyrus and Parchment Paths: " + + "An Archaeological Atlas of Coptic Literature." + + "\\nLiterary Texts in their Geographical Context: Production, Copying, Usage, " + + "Dissemination and Storage\"}") + .withRelatedItem(funding.getID().toString()) + .build(); + NBEvent eventProjectNoBound = NBEventBuilder + .createTarget(context, col1, "Science and Freedom with unrelated project") + .withTopic("ENRICH/MISSING/PROJECT") + .withMessage( + "{\"projects[0].acronym\":\"NEW\"," + + "\"projects[0].code\":\"123456\"," + + "\"projects[0].funder\":\"EC\"," + + "\"projects[0].fundingProgram\":\"H2020\"," + + "\"projects[0].jurisdiction\":\"EU\"," + + "\"projects[0].openaireId\":\"newProjectID\"," + + "\"projects[0].title\":\"A new project\"}") + .build(); + NBEvent eventMissingPID1 = NBEventBuilder.createTarget(context, col1, "Science and Freedom") + .withTopic("ENRICH/MISSING/PID") + .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144300\"}").build(); + NBEvent eventMissingPID2 = NBEventBuilder.createTarget(context, col1, "Science and Freedom 2") + .withTopic("ENRICH/MISSING/PID") + .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144301\"}").build(); + NBEvent eventMissingUnknownPID = NBEventBuilder.createTarget(context, col1, "Science and Freedom URN PID") + .withTopic("ENRICH/MISSING/PID") + .withMessage( + "{\"pids[0].type\":\"urn\",\"pids[0].value\":\"http://thesis2.sba.units.it/store/handle/item/12937\"}") + .build(); + NBEvent eventMorePID = NBEventBuilder.createTarget(context, col1, "Science and Freedom 3") + .withTopic("ENRICH/MORE/PID") + .withMessage("{\"pids[0].type\":\"pmid\",\"pids[0].value\":\"2144302\"}").build(); + NBEvent eventAbstract = NBEventBuilder.createTarget(context, col1, "Science and Freedom 4") + .withTopic("ENRICH/MISSING/ABSTRACT") + .withMessage("{\"abstracts[0]\": \"An abstract to add...\"}").build(); + NBEvent eventAbstractToDiscard = NBEventBuilder.createTarget(context, col1, "Science and Freedom 7") + .withTopic("ENRICH/MISSING/ABSTRACT") + .withMessage("{\"abstracts[0]\": \"Abstract to discard...\"}").build(); + context.restoreAuthSystemState(); + // prepare the different patches for our decisions + List acceptOp = new ArrayList(); + acceptOp.add(new ReplaceOperation("/status", NBEvent.ACCEPTED)); + List acceptOpUppercase = new ArrayList(); + acceptOpUppercase.add(new ReplaceOperation("/status", NBEvent.ACCEPTED)); + List discardOp = new ArrayList(); + discardOp.add(new ReplaceOperation("/status", NBEvent.DISCARDED)); + List rejectOp = new ArrayList(); + rejectOp.add(new ReplaceOperation("/status", NBEvent.REJECTED)); + String patchAccept = getPatchContent(acceptOp); + String patchAcceptUppercase = getPatchContent(acceptOpUppercase); + String patchDiscard = getPatchContent(discardOp); + String patchReject = getPatchContent(rejectOp); + + String authToken = getAuthToken(admin.getEmail(), password); + // accept pid1, unknownPID, morePID, the two projects and abstract + eventMissingPID1.setStatus(NBEvent.ACCEPTED); + eventMorePID.setStatus(NBEvent.ACCEPTED); + eventMissingUnknownPID.setStatus(NBEvent.ACCEPTED); + eventMissingUnknownPID.setStatus(NBEvent.ACCEPTED); + eventProjectBound.setStatus(NBEvent.ACCEPTED); + eventProjectNoBound.setStatus(NBEvent.ACCEPTED); + eventAbstract.setStatus(NBEvent.ACCEPTED); + + getClient(authToken).perform(patch("/api/integration/nbevents/" + eventMissingPID1.getEventId()) + .content(patchAccept) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", NBEventMatcher.matchNBEventEntry(eventMissingPID1))); + getClient(authToken).perform(patch("/api/integration/nbevents/" + eventMorePID.getEventId()) + .content(patchAcceptUppercase) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", NBEventMatcher.matchNBEventEntry(eventMorePID))); + getClient(authToken).perform(patch("/api/integration/nbevents/" + eventMissingUnknownPID.getEventId()) + .content(patchAccept) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", NBEventMatcher.matchNBEventEntry(eventMissingUnknownPID))); + getClient(authToken).perform(patch("/api/integration/nbevents/" + eventProjectBound.getEventId()) + .content(patchAccept) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", NBEventMatcher.matchNBEventEntry(eventProjectBound))); + getClient(authToken).perform(patch("/api/integration/nbevents/" + eventProjectNoBound.getEventId()) + .content(patchAccept) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", NBEventMatcher.matchNBEventEntry(eventProjectNoBound))); + getClient(authToken).perform(patch("/api/integration/nbevents/" + eventAbstract.getEventId()) + .content(patchAccept) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", NBEventMatcher.matchNBEventEntry(eventAbstract))); + // check if the item has been updated + getClient(authToken).perform(get("/api/core/items/" + eventMissingPID1.getTarget()) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()) + .andExpect( + jsonPath("$", + hasJsonPath("$.metadata['dc.identifier.other'][0].value", is("10.2307/2144300")))); + getClient(authToken).perform(get("/api/core/items/" + eventMorePID.getTarget()) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", hasJsonPath("$.metadata['dc.identifier.other'][0].value", is("2144302")))); + getClient(authToken).perform(get("/api/core/items/" + eventMissingUnknownPID.getTarget()) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", hasJsonPath("$.metadata['dc.identifier.other'][0].value", + is("http://thesis2.sba.units.it/store/handle/item/12937")))); + getClient(authToken).perform(get("/api/core/items/" + eventProjectBound.getTarget()) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", + hasJsonPath("$.metadata['relation.isProjectOfPublication'][0].value", + is(funding.getID().toString())))); + getClient(authToken).perform(get("/api/core/items/" + eventProjectNoBound.getTarget()) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", + hasJsonPath("$.metadata['relation.isProjectOfPublication'][0].value", + is(not(empty()))))); + getClient(authToken).perform(get("/api/core/items/" + eventAbstract.getTarget()) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", + hasJsonPath("$.metadata['dc.description.abstract'][0].value", is("An abstract to add...")))); + // reject pid2 + eventMissingPID2.setStatus(NBEvent.REJECTED); + getClient(authToken).perform(patch("/api/integration/nbevents/" + eventMissingPID2.getEventId()) + .content(patchReject) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", NBEventMatcher.matchNBEventEntry(eventMissingPID2))); + getClient(authToken).perform(get("/api/core/items/" + eventMissingPID2.getTarget()) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", + hasNoJsonPath("$.metadata['dc.identifier.other']"))); + // discard abstractToDiscard + eventAbstractToDiscard.setStatus(NBEvent.DISCARDED); + getClient(authToken).perform(patch("/api/integration/nbevents/" + eventAbstractToDiscard.getEventId()) + .content(patchDiscard) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", NBEventMatcher.matchNBEventEntry(eventAbstractToDiscard))); + getClient(authToken).perform(get("/api/core/items/" + eventMissingPID2.getTarget()) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", + hasNoJsonPath("$.metadata['dc.description.abstract']"))); + // no pending nb events should be longer available + getClient(authToken).perform(get("/api/integration/nbtopics")).andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.size", is(20))).andExpect(jsonPath("$.page.totalElements", is(0))); + // we should have stored the decision into the database as well + } + + @Test + public void setRelatedTest() throws Exception { + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context).withName("Parent Community").build(); + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build(); + Collection colFunding = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection Fundings").build(); + NBEvent event = NBEventBuilder.createTarget(context, col1, "Science and Freedom 5") + .withTopic("ENRICH/MISSING/PROJECT") + .withMessage( + "{\"projects[0].acronym\":\"PAThs\"," + + "\"projects[0].code\":\"687567\"," + + "\"projects[0].funder\":\"EC\"," + + "\"projects[0].fundingProgram\":\"H2020\"," + + "\"projects[0].jurisdiction\":\"EU\"," + + "\"projects[0].openaireId\":\"40|corda__h2020::6e32f5eb912688f2424c68b851483ea4\"," + + "\"projects[0].title\":\"Tracking Papyrus and Parchment Paths: " + + "An Archaeological Atlas of Coptic Literature." + + "\\nLiterary Texts in their Geographical Context: Production, Copying, Usage, " + + "Dissemination and Storage\"}") + .build(); + Item funding = ItemBuilder.createItem(context, colFunding).withTitle("Tracking Papyrus and Parchment Paths") + .build(); + context.restoreAuthSystemState(); + String authToken = getAuthToken(admin.getEmail(), password); + getClient(authToken) + .perform(get("/api/integration/nbevents/" + event.getEventId()).param("projection", "full")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", NBEventMatcher.matchNBEventFullEntry(event))); + + getClient(authToken) + .perform(post("/api/integration/nbevents/" + event.getEventId() + "/related").param("item", + funding.getID().toString())) + .andExpect(status().isCreated()) + .andExpect(jsonPath("$", ItemMatcher.matchItemProperties(funding))); + // update our local event copy to reflect the association with the related item + event.setRelated(funding.getID().toString()); + getClient(authToken) + .perform(get("/api/integration/nbevents/" + event.getEventId()).param("projection", "full")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", NBEventMatcher.matchNBEventFullEntry(event))); + getClient(authToken) + .perform(get("/api/integration/nbevents/" + event.getEventId() + "/related")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", ItemMatcher.matchItemProperties(funding))); + } + + @Test + public void unsetRelatedTest() throws Exception { + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context).withName("Parent Community").build(); + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build(); + Collection colFunding = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection Fundings").build(); + Item funding = ItemBuilder.createItem(context, colFunding).withTitle("Tracking Papyrus and Parchment Paths") + .build(); + NBEvent event = NBEventBuilder.createTarget(context, col1, "Science and Freedom 5") + .withTopic("ENRICH/MISSING/PROJECT") + .withMessage( + "{\"projects[0].acronym\":\"PAThs\"," + + "\"projects[0].code\":\"687567\"," + + "\"projects[0].funder\":\"EC\"," + + "\"projects[0].fundingProgram\":\"H2020\"," + + "\"projects[0].jurisdiction\":\"EU\"," + + "\"projects[0].openaireId\":\"40|corda__h2020::6e32f5eb912688f2424c68b851483ea4\"," + + "\"projects[0].title\":\"Tracking Papyrus and Parchment Paths: " + + "An Archaeological Atlas of Coptic Literature." + + "\\nLiterary Texts in their Geographical Context: Production, Copying, Usage, " + + "Dissemination and Storage\"}") + .withRelatedItem(funding.getID().toString()) + .build(); + context.restoreAuthSystemState(); + String authToken = getAuthToken(admin.getEmail(), password); + getClient(authToken) + .perform(get("/api/integration/nbevents/" + event.getEventId()).param("projection", "full")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", NBEventMatcher.matchNBEventFullEntry(event))); + getClient(authToken) + .perform(delete("/api/integration/nbevents/" + event.getEventId() + "/related")) + .andExpect(status().isNoContent()); + + // update our local event copy to reflect the association with the related item + event.setRelated(null); + getClient(authToken) + .perform(get("/api/integration/nbevents/" + event.getEventId()).param("projection", "full")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", NBEventMatcher.matchNBEventFullEntry(event))); + getClient(authToken) + .perform(get("/api/integration/nbevents/" + event.getEventId() + "/related")) + .andExpect(status().isNoContent()); + } + + @Test + public void setInvalidRelatedTest() throws Exception { + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context).withName("Parent Community").build(); + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build(); + Collection colFunding = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection Fundings").build(); + NBEvent event = NBEventBuilder.createTarget(context, col1, "Science and Freedom") + .withTopic("ENRICH/MISSING/PID") + .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144300\"}").build(); + Item funding = ItemBuilder.createItem(context, colFunding).withTitle("Tracking Papyrus and Parchment Paths") + .build(); + context.restoreAuthSystemState(); + String authToken = getAuthToken(admin.getEmail(), password); + getClient(authToken) + .perform(get("/api/integration/nbevents/" + event.getEventId()).param("projection", "full")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", NBEventMatcher.matchNBEventFullEntry(event))); + + getClient(authToken) + .perform(post("/api/integration/nbevents/" + event.getEventId() + "/related").param("item", + funding.getID().toString())) + .andExpect(status().isBadRequest()); + // check that no related item has been added to our event + getClient(authToken) + .perform(get("/api/integration/nbevents/" + event.getEventId()).param("projection", "full")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", NBEventMatcher.matchNBEventFullEntry(event))); + } + + @Test + public void deleteItemWithEventTest() throws Exception { + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context).withName("Parent Community").build(); + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build(); + NBEvent event1 = NBEventBuilder.createTarget(context, col1, "Science and Freedom") + .withTopic("ENRICH/MISSING/PID") + .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144300\"}").build(); + NBEvent event2 = NBEventBuilder.createTarget(context, col1, "Science and Freedom 2") + .withTopic("ENRICH/MISSING/PID") + .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144301\"}").build(); + context.restoreAuthSystemState(); + String authToken = getAuthToken(admin.getEmail(), password); + getClient(authToken) + .perform(get("/api/integration/nbevents/search/findByTopic").param("topic", "ENRICH!MISSING!PID")) + .andExpect(status().isOk()).andExpect(jsonPath("$._embedded.nbevents", Matchers.hasSize(2))) + .andExpect(jsonPath("$._embedded.nbevents", + Matchers.containsInAnyOrder(NBEventMatcher.matchNBEventEntry(event1), + NBEventMatcher.matchNBEventEntry(event2)))) + .andExpect(jsonPath("$.page.size", is(20))).andExpect(jsonPath("$.page.totalElements", is(2))); + + getClient(authToken).perform(delete("/api/core/items/" + event1.getTarget())) + .andExpect(status().is(204)); + + getClient(authToken).perform(get("/api/core/items/" + event1.getTarget())) + .andExpect(status().is(404)); + + getClient(authToken) + .perform(get("/api/integration/nbevents/search/findByTopic").param("topic", "ENRICH!MISSING!PID")) + .andExpect(status().isOk()).andExpect(jsonPath("$._embedded.nbevents", Matchers.hasSize(1))) + .andExpect(jsonPath("$._embedded.nbevents", + Matchers.containsInAnyOrder( + NBEventMatcher.matchNBEventEntry(event2)))) + .andExpect(jsonPath("$.page.size", is(20))).andExpect(jsonPath("$.page.totalElements", is(1))); + } +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/NBTopicRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/NBTopicRestRepositoryIT.java new file mode 100644 index 000000000000..2d0095bd8fd1 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/NBTopicRestRepositoryIT.java @@ -0,0 +1,169 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest; + +import static org.hamcrest.Matchers.is; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import org.dspace.app.rest.matcher.NBTopicMatcher; +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.NBEventBuilder; +import org.dspace.content.Collection; +import org.dspace.content.NBEvent; +import org.hamcrest.Matchers; +import org.junit.Test; + +public class NBTopicRestRepositoryIT extends AbstractControllerIntegrationTest { + + @Test + public void findAllTest() throws Exception { + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build(); + NBEvent event1 = NBEventBuilder.createTarget(context, col1, "Science and Freedom") + .withTopic("ENRICH/MISSING/PID") + .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144300\"}").build(); + NBEvent event2 = NBEventBuilder.createTarget(context, col1, "Science and Freedom 2") + .withTopic("ENRICH/MISSING/PID") + .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144301\"}").build(); + NBEvent event3 = NBEventBuilder.createTarget(context, col1, "Science and Freedom 3") + .withTopic("ENRICH/MORE/PID") + .withMessage("{\"pids[0].type\":\"pmid\",\"pids[0].value\":\"10.2307/2144302\"}").build(); + NBEvent event4 = NBEventBuilder.createTarget(context, col1, "Science and Freedom 4") + .withTopic("ENRICH/MISSING/ABSTRACT") + .withMessage( + "{\"abstracts[0]\": \"Descrizione delle caratteristiche...\"}") + .build(); + context.restoreAuthSystemState(); + String authToken = getAuthToken(admin.getEmail(), password); + getClient(authToken).perform(get("/api/integration/nbtopics")).andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.nbtopics", + Matchers.containsInAnyOrder(NBTopicMatcher.matchNBTopicEntry("ENRICH/MISSING/PID", 2), + NBTopicMatcher.matchNBTopicEntry("ENRICH/MISSING/ABSTRACT", 1), + NBTopicMatcher.matchNBTopicEntry("ENRICH/MORE/PID", 1)))) + .andExpect(jsonPath("$.page.size", is(20))).andExpect(jsonPath("$.page.totalElements", is(3))); + + } + + @Test + public void findAllUnauthorizedTest() throws Exception { + getClient().perform(get("/api/integration/nbtopics")).andExpect(status().isUnauthorized()); + } + + @Test + public void findAllForbiddenTest() throws Exception { + String authToken = getAuthToken(eperson.getEmail(), password); + getClient(authToken).perform(get("/api/integration/nbtopics")).andExpect(status().isForbidden()); + } + + @Test + public void findAllPaginationTest() throws Exception { + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + //create collection + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build(); + NBEvent event1 = NBEventBuilder.createTarget(context, col1, "Science and Freedom") + .withTopic("ENRICH/MISSING/PID") + .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144300\"}").build(); + NBEvent event2 = NBEventBuilder.createTarget(context, col1, "Science and Freedom 2") + .withTopic("ENRICH/MISSING/PID") + .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144301\"}").build(); + NBEvent event3 = NBEventBuilder.createTarget(context, col1, "Science and Freedom 3") + .withTopic("ENRICH/MORE/PID") + .withMessage("{\"pids[0].type\":\"pmid\",\"pids[0].value\":\"10.2307/2144302\"}").build(); + NBEvent event4 = NBEventBuilder.createTarget(context, col1, "Science and Freedom 4") + .withTopic("ENRICH/MISSING/ABSTRACT") + .withMessage( + "{\"abstracts[0]\": \"Descrizione delle caratteristiche...\"}") + .build(); + context.restoreAuthSystemState(); + String authToken = getAuthToken(admin.getEmail(), password); + getClient(authToken).perform(get("/api/integration/nbtopics").param("size", "2")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.nbtopics", Matchers.hasSize(2))) + .andExpect(jsonPath("$.page.size", is(2))).andExpect(jsonPath("$.page.totalElements", is(3))); + getClient(authToken).perform(get("/api/integration/nbtopics").param("size", "2").param("page", "1")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.nbtopics", Matchers.hasSize(1))) + .andExpect(jsonPath("$.page.size", is(2))).andExpect(jsonPath("$.page.totalElements", is(3))); + } + + @Test + public void findOneTest() throws Exception { + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build(); + NBEvent event1 = NBEventBuilder.createTarget(context, col1, "Science and Freedom") + .withTopic("ENRICH/MISSING/PID") + .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144300\"}").build(); + NBEvent event2 = NBEventBuilder.createTarget(context, col1, "Science and Freedom 2") + .withTopic("ENRICH/MISSING/PID") + .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144301\"}").build(); + NBEvent event3 = NBEventBuilder.createTarget(context, col1, "Science and Freedom 3") + .withTopic("ENRICH/MORE/PID") + .withMessage("{\"pids[0].type\":\"pmid\",\"pids[0].value\":\"10.2307/2144302\"}").build(); + NBEvent event4 = NBEventBuilder.createTarget(context, col1, "Science and Freedom 4") + .withTopic("ENRICH/MISSING/ABSTRACT") + .withMessage( + "{\"abstracts[0]\": \"Descrizione delle caratteristiche...\"}") + .build(); + context.restoreAuthSystemState(); + String authToken = getAuthToken(admin.getEmail(), password); + getClient(authToken).perform(get("/api/integration/nbtopics/ENRICH!MISSING!PID")) + .andExpect(jsonPath("$", NBTopicMatcher.matchNBTopicEntry("ENRICH/MISSING/PID", 2))); + getClient(authToken).perform(get("/api/integration/nbtopics/ENRICH!MISSING!ABSTRACT")) + .andExpect(jsonPath("$", NBTopicMatcher.matchNBTopicEntry("ENRICH/MISSING/ABSTRACT", 1))); + } + + @Test + public void findOneUnauthorizedTest() throws Exception { + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build(); + NBEvent event1 = NBEventBuilder.createTarget(context, col1, "Science and Freedom") + .withTopic("ENRICH/MISSING/PID").build(); + context.restoreAuthSystemState(); + getClient().perform(get("/api/integration/nbtopics/ENRICH!MISSING!PID")).andExpect(status().isUnauthorized()); + getClient().perform(get("/api/integration/nbtopics/ENRICH!MISSING!ABSTRACT")) + .andExpect(status().isUnauthorized()); + } + + @Test + public void findOneForbiddenTest() throws Exception { + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build(); + NBEvent event1 = NBEventBuilder.createTarget(context, col1, "Science and Freedom") + .withTopic("ENRICH/MISSING/PID").build(); + context.restoreAuthSystemState(); + String authToken = getAuthToken(eperson.getEmail(), password); + getClient(authToken).perform(get("/api/integration/nbtopics/ENRICH!MISSING!PID")) + .andExpect(status().isForbidden()); + getClient(authToken).perform(get("/api/integration/nbtopics/ENRICH!MISSING!ABSTRACT")) + .andExpect(status().isForbidden()); + } + +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/NBEventMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/NBEventMatcher.java new file mode 100644 index 000000000000..afb364bb0e3a --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/NBEventMatcher.java @@ -0,0 +1,81 @@ +/** + * 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.matcher; + +import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.emptyOrNullString; +import static org.hamcrest.Matchers.is; + +import java.text.DecimalFormat; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; +import org.apache.commons.lang3.StringUtils; +import org.dspace.app.nbevent.service.dto.MessageDto; +import org.dspace.content.NBEvent; +import org.hamcrest.Matcher; +import org.hamcrest.Matchers; +import org.hamcrest.core.IsAnything; + +public class NBEventMatcher { + + private NBEventMatcher() { + } + + public static Matcher matchNBEventFullEntry(NBEvent event) { + return allOf( + matchNBEventEntry(event), + hasJsonPath("$._embedded.topic.name", is(event.getTopic())), + hasJsonPath("$._embedded.target.id", is(event.getTarget())), + event.getRelated() != null ? + hasJsonPath("$._embedded.related.id", is(event.getRelated())) : + hasJsonPath("$._embedded.related", is(emptyOrNullString())) + ); + } + + public static Matcher matchNBEventEntry(NBEvent event) { + try { + ObjectMapper jsonMapper = new JsonMapper(); + return allOf(hasJsonPath("$.id", is(event.getEventId())), + hasJsonPath("$.originalId", is(event.getOriginalId())), + hasJsonPath("$.title", is(event.getTitle())), + hasJsonPath("$.trust", is(new DecimalFormat("0.000").format(event.getTrust()))), + hasJsonPath("$.status", Matchers.equalToIgnoringCase(event.getStatus())), + hasJsonPath("$.message", + matchMessage(event.getTopic(), jsonMapper.readValue(event.getMessage(), MessageDto.class))), + hasJsonPath("$._links.target.href", Matchers.endsWith(event.getEventId() + "/target")), + hasJsonPath("$._links.related.href", Matchers.endsWith(event.getEventId() + "/related")), + hasJsonPath("$._links.topic.href", Matchers.endsWith(event.getEventId() + "/topic")), + hasJsonPath("$.type", is("nbevent"))); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } + + private static Matcher matchMessage(String topic, MessageDto message) { + if (StringUtils.endsWith(topic, "/ABSTRACT")) { + return allOf(hasJsonPath("$.abstract", is(message.getAbstracts()))); + } else if (StringUtils.endsWith(topic, "/PID")) { + return allOf( + hasJsonPath("$.value", is(message.getValue())), + hasJsonPath("$.type", is(message.getType()))); + } else if (StringUtils.endsWith(topic, "/PROJECT")) { + return allOf( + hasJsonPath("$.openaireId", is(message.getOpenaireId())), + hasJsonPath("$.acronym", is(message.getAcronym())), + hasJsonPath("$.code", is(message.getCode())), + hasJsonPath("$.funder", is(message.getFunder())), + hasJsonPath("$.fundingProgram", is(message.getFundingProgram())), + hasJsonPath("$.jurisdiction", is(message.getJurisdiction())), + hasJsonPath("$.title", is(message.getTitle()))); + } + return IsAnything.anything(); + } +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/NBTopicMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/NBTopicMatcher.java new file mode 100644 index 000000000000..644feeeec48d --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/NBTopicMatcher.java @@ -0,0 +1,38 @@ +/** + * 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.matcher; + +import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.is; + +import org.hamcrest.Matcher; + +public class NBTopicMatcher { + + private NBTopicMatcher() { } + + public static Matcher matchNBTopicEntry(String key, int totalEvents) { + return allOf( + hasJsonPath("$.type", is("nbtopic")), + hasJsonPath("$.name", is(key)), + hasJsonPath("$.id", is(key.replace("/", "!"))), + hasJsonPath("$.totalEvents", is(totalEvents)) + ); + } + + + public static Matcher matchNBTopicEntry(String key) { + return allOf( + hasJsonPath("$.type", is("nbtopic")), + hasJsonPath("$.name", is(key)), + hasJsonPath("$.id", is(key.replace("/", "/"))) + ); + } + +} diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 02c618abf420..369d15c9c157 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -714,7 +714,7 @@ event.dispatcher.default.class = org.dspace.event.BasicDispatcher # Adding doi here makes DSpace send metadata updates to your doi registration agency. # Add rdf here, if you are using dspace-rdf to export your repository content as RDF. # Add iiif here, if you are using dspace-iiif. -event.dispatcher.default.consumers = versioning, discovery, eperson +event.dispatcher.default.consumers = versioning, discovery, eperson, nbeventsdelete # The noindex dispatcher will not create search or browse indexes (useful for batch item imports) event.dispatcher.noindex.class = org.dspace.event.BasicDispatcher @@ -740,6 +740,10 @@ event.consumer.rdf.filters = Community|Collection|Item|Bundle|Bitstream|Site+Add #event.consumer.test.class = org.dspace.event.TestConsumer #event.consumer.test.filters = All+All +# nbevents consumer to delete events related to deleted items +event.consumer.nbeventsdelete.class = org.dspace.app.nbevent.NBEventsDeleteCascadeConsumer +event.consumer.nbeventsdelete.filters = Item+Delete + # consumer to maintain versions event.consumer.versioning.class = org.dspace.versioning.VersioningConsumer event.consumer.versioning.filters = Item+Install @@ -1595,6 +1599,7 @@ include = ${module_dir}/healthcheck.cfg include = ${module_dir}/irus-statistics.cfg include = ${module_dir}/oai.cfg include = ${module_dir}/openaire-client.cfg +include = ${module_dir}/oaire-nbevents.cfg include = ${module_dir}/rdf.cfg include = ${module_dir}/rest.cfg include = ${module_dir}/iiif.cfg diff --git a/dspace/config/hibernate.cfg.xml b/dspace/config/hibernate.cfg.xml index 39f5a113788f..b686a5672b01 100644 --- a/dspace/config/hibernate.cfg.xml +++ b/dspace/config/hibernate.cfg.xml @@ -56,6 +56,8 @@ + + diff --git a/dspace/config/modules/oaire-nbevents.cfg b/dspace/config/modules/oaire-nbevents.cfg new file mode 100644 index 000000000000..68baec3d1de0 --- /dev/null +++ b/dspace/config/modules/oaire-nbevents.cfg @@ -0,0 +1,14 @@ +#---------------------------------------------------------------# +#-------OAIRE Notification Broker Events CONFIGURATIONS---------# +#---------------------------------------------------------------# +# Configuration properties used by data correction service # +#---------------------------------------------------------------# +oaire-nbevents.solr.server = ${solr.server}/${solr.multicorePrefix}nbevent +# A POST to these url(s) will be done to notify oaire of decision taken for each nbevents +oaire-nbevents.acknowledge-url = https://beta.api-broker.openaire.eu/feedback/events +#oaire-nbevents.acknowledge-url = +oaire-nbevents.import.topic = ENRICH/MISSING/ABSTRACT +oaire-nbevents.import.topic = ENRICH/MISSING/PID +oaire-nbevents.import.topic = ENRICH/MORE/PID +oaire-nbevents.import.topic = ENRICH/MISSING/PROJECT +oaire-nbevents.import.topic = ENRICH/MORE/PROJECT \ No newline at end of file diff --git a/dspace/config/spring/api/nbevents.xml b/dspace/config/spring/api/nbevents.xml new file mode 100644 index 000000000000..34dca93ebbb3 --- /dev/null +++ b/dspace/config/spring/api/nbevents.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dspace/config/spring/api/scripts.xml b/dspace/config/spring/api/scripts.xml index e7c55549c748..61b06c2aa9ee 100644 --- a/dspace/config/spring/api/scripts.xml +++ b/dspace/config/spring/api/scripts.xml @@ -3,6 +3,12 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"> + + + + + + diff --git a/dspace/config/spring/api/solr-services.xml b/dspace/config/spring/api/solr-services.xml index 698a82418422..164a4dd8351b 100644 --- a/dspace/config/spring/api/solr-services.xml +++ b/dspace/config/spring/api/solr-services.xml @@ -31,5 +31,8 @@ + + + diff --git a/dspace/config/spring/rest/scripts.xml b/dspace/config/spring/rest/scripts.xml index 563c639a10ee..518d81009b4c 100644 --- a/dspace/config/spring/rest/scripts.xml +++ b/dspace/config/spring/rest/scripts.xml @@ -8,6 +8,11 @@ + + + + + diff --git a/dspace/solr/nbevent/conf/admin-extra.html b/dspace/solr/nbevent/conf/admin-extra.html new file mode 100644 index 000000000000..aa739da862cf --- /dev/null +++ b/dspace/solr/nbevent/conf/admin-extra.html @@ -0,0 +1,31 @@ + + + diff --git a/dspace/solr/nbevent/conf/elevate.xml b/dspace/solr/nbevent/conf/elevate.xml new file mode 100644 index 000000000000..7630ebe20fb9 --- /dev/null +++ b/dspace/solr/nbevent/conf/elevate.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + diff --git a/dspace/solr/nbevent/conf/protwords.txt b/dspace/solr/nbevent/conf/protwords.txt new file mode 100644 index 000000000000..1dfc0abecbf8 --- /dev/null +++ b/dspace/solr/nbevent/conf/protwords.txt @@ -0,0 +1,21 @@ +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +#----------------------------------------------------------------------- +# Use a protected word file to protect against the stemmer reducing two +# unrelated words to the same base word. + +# Some non-words that normally won't be encountered, +# just to test that they won't be stemmed. +dontstems +zwhacky + diff --git a/dspace/solr/nbevent/conf/schema.xml b/dspace/solr/nbevent/conf/schema.xml new file mode 100644 index 000000000000..8ed9b4d5ae73 --- /dev/null +++ b/dspace/solr/nbevent/conf/schema.xml @@ -0,0 +1,544 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + event_id + + + + + diff --git a/dspace/solr/nbevent/conf/scripts.conf b/dspace/solr/nbevent/conf/scripts.conf new file mode 100644 index 000000000000..f58b262ae0c8 --- /dev/null +++ b/dspace/solr/nbevent/conf/scripts.conf @@ -0,0 +1,24 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +user= +solr_hostname=localhost +solr_port=8983 +rsyncd_port=18983 +data_dir= +webapp_name=solr +master_host= +master_data_dir= +master_status_dir= diff --git a/dspace/solr/nbevent/conf/solrconfig.xml b/dspace/solr/nbevent/conf/solrconfig.xml new file mode 100644 index 000000000000..a4cfbed4a989 --- /dev/null +++ b/dspace/solr/nbevent/conf/solrconfig.xml @@ -0,0 +1,1943 @@ + + + + + + + + + 7.7.2 + + + + + + + + + + + + + + + ${solr.data.dir:} + + + + + + + + + + + + + + + + + + + + + + + + + 32 + 1000 + + + + + + + + + + + + ${solr.lock.type:native} + + + + + + + + + + + + + + + + + + + + + + true + + + + + + + + + + + + + + + + ${solr.ulog.dir:} + + + + + 10000 + ${solr.autoCommit.maxTime:10000} + false + + + + + + ${solr.autoSoftCommit.maxTime:1000} + + + + + + + + + + + + + + + + + + + + 1024 + + + + + + + + + + + + + + + + + + + + + + true + + + + + + 20 + + + 200 + + + + + + + + + + + + static firstSearcher warming in solrconfig.xml + + + + + + false + + + 2 + + + + + + + + + + + + + + + + + + + + + + + explicit + 10 + event_id + + + + + + + + + + + + + + explicit + json + true + event_id + + + + + + + + true + json + true + + + + + + + + explicit + + + velocity + browse + layout + Solritas + + + edismax + + text^0.5 features^1.0 name^1.2 sku^1.5 event_id^10.0 manu^1.1 cat^1.4 + title^10.0 description^5.0 keywords^5.0 author^2.0 resourcename^1.0 + + event_id + 100% + *:* + 10 + *,score + + + text^0.5 features^1.0 name^1.2 sku^1.5 event_id^10.0 manu^1.1 cat^1.4 + title^10.0 description^5.0 keywords^5.0 author^2.0 resourcename^1.0 + + text,features,name,sku,event_id,manu,cat,title,description,keywords,author,resourcename + 3 + + + on + cat + manu_exact + content_type + author_s + ipod + GB + 1 + cat,inStock + after + price + 0 + 600 + 50 + popularity + 0 + 10 + 3 + manufacturedate_dt + NOW/YEAR-10YEARS + NOW + +1YEAR + before + after + + + on + content features title name + html + <b> + </b> + 0 + title + 0 + name + 3 + 200 + content + 750 + + + on + false + 5 + 2 + 5 + true + true + 5 + 3 + + + + + spellcheck + + + + + + + + + + + + + + + application/json + + + + + + + application/csv + + + + + + + + true + ignored_ + + + true + links + ignored_ + + + + + + + + + + + + + + + + + + + + + + solrpingquery + + + all + + + + + + + + + explicit + true + + + + + + + + + + + + + + + + textSpell + + + default + name + ./spellchecker + + + + + + + + + + + + false + + false + + 1 + + + spellcheck + + + + + + + + true + + + tvComponent + + + + + + + + + text_general + + + + + + default + event_id + solr.DirectSolrSpellChecker + + internal + + 0.5 + + 2 + + 1 + + 5 + + 4 + + 0.01 + + + + + + wordbreak + solr.WordBreakSolrSpellChecker + name + true + true + 10 + + + + + + + + + + + + + + + + event_id + + default + wordbreak + on + true + 10 + 5 + 5 + true + true + 10 + 5 + + + spellcheck + + + + + + + + + + event_id + true + + + tvComponent + + + + + + + + + default + + + org.carrot2.clustering.lingo.LingoClusteringAlgorithm + + + 20 + + + clustering/carrot2 + + + ENGLISH + + + stc + org.carrot2.clustering.stc.STCClusteringAlgorithm + + + + + + + true + default + true + + name + event_id + + features + + true + + + + false + + edismax + + text^0.5 features^1.0 name^1.2 sku^1.5 event_id^10.0 manu^1.1 cat^1.4 + + *:* + 10 + *,score + + + clustering + + + + + + + + + + true + false + + + terms + + + + + + + + string + elevate.xml + + + + + + explicit + event_id + + + elevator + + + + + + + + + + + 100 + + + + + + + + 70 + + 0.5 + + [-\w ,/\n\"']{20,200} + + + + + + + ]]> + ]]> + + + + + + + + + + + + + + + + + + + + + + + + ,, + ,, + ,, + ,, + ,]]> + ]]> + + + + + + 10 + .,!? + + + + + + + WORD + + + en + US + + + + + + + + + + + + + + + + + + + + + + + + text/plain; charset=UTF-8 + + + + + + + + + 5 + + + + + + + + + + + + + + + + + + *:* + + diff --git a/dspace/solr/nbevent/conf/spellings.txt b/dspace/solr/nbevent/conf/spellings.txt new file mode 100644 index 000000000000..d7ede6f5611d --- /dev/null +++ b/dspace/solr/nbevent/conf/spellings.txt @@ -0,0 +1,2 @@ +pizza +history \ No newline at end of file diff --git a/dspace/solr/nbevent/conf/stopwords.txt b/dspace/solr/nbevent/conf/stopwords.txt new file mode 100644 index 000000000000..8433c832d2cf --- /dev/null +++ b/dspace/solr/nbevent/conf/stopwords.txt @@ -0,0 +1,57 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +#----------------------------------------------------------------------- +# a couple of test stopwords to test that the words are really being +# configured from this file: +stopworda +stopwordb + +#Standard english stop words taken from Lucene's StopAnalyzer +an +and +are +as +at +be +but +by +for +if +in +into +is +it +no +not +of +on +or +s +such +t +that +the +their +then +there +these +they +this +to +was +will +with + diff --git a/dspace/solr/nbevent/conf/synonyms.txt b/dspace/solr/nbevent/conf/synonyms.txt new file mode 100644 index 000000000000..b0e31cb7ec83 --- /dev/null +++ b/dspace/solr/nbevent/conf/synonyms.txt @@ -0,0 +1,31 @@ +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +#----------------------------------------------------------------------- +#some test synonym mappings unlikely to appear in real input text +aaa => aaaa +bbb => bbbb1 bbbb2 +ccc => cccc1,cccc2 +a\=>a => b\=>b +a\,a => b\,b +fooaaa,baraaa,bazaaa + +# Some synonym groups specific to this example +GB,gib,gigabyte,gigabytes +MB,mib,megabyte,megabytes +Television, Televisions, TV, TVs +#notice we use "gib" instead of "GiB" so any WordDelimiterFilter coming +#after us won't split it into two words. + +# Synonym mappings can be used for spelling correction too +pixima => pixma + diff --git a/dspace/solr/nbevent/core.properties b/dspace/solr/nbevent/core.properties new file mode 100644 index 000000000000..e69de29bb2d1 From 2340a44e96ebde2398877ae3a3df839a8c92cefa Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Wed, 16 Feb 2022 15:48:28 +0100 Subject: [PATCH 002/510] [CST-5294] FIxed NBEventRestRepositoryIT test --- .../app/nbevent/NBEntityMetadataAction.java | 8 ++- .../dspace/content/CollectionServiceImpl.java | 52 +++++++++++++++++++ .../content/service/CollectionService.java | 27 ++++++++++ 3 files changed, 85 insertions(+), 2 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/nbevent/NBEntityMetadataAction.java b/dspace-api/src/main/java/org/dspace/app/nbevent/NBEntityMetadataAction.java index ad575b5281b1..2e5622cf4b82 100644 --- a/dspace-api/src/main/java/org/dspace/app/nbevent/NBEntityMetadataAction.java +++ b/dspace-api/src/main/java/org/dspace/app/nbevent/NBEntityMetadataAction.java @@ -19,6 +19,7 @@ import org.dspace.content.Relationship; import org.dspace.content.RelationshipType; import org.dspace.content.WorkspaceItem; +import org.dspace.content.service.CollectionService; import org.dspace.content.service.EntityTypeService; import org.dspace.content.service.InstallItemService; import org.dspace.content.service.ItemService; @@ -51,6 +52,9 @@ public class NBEntityMetadataAction implements NBAction { @Autowired private WorkspaceItemService workspaceItemService; + @Autowired + private CollectionService collectionService; + public void setItemService(ItemService itemService) { this.itemService = itemService; } @@ -96,11 +100,11 @@ public void applyCorrection(Context context, Item item, Item relatedItem, Messag if (relatedItem != null) { link(context, item, relatedItem); } else { - Collection collection = item.getOwningCollection(); + Collection collection = collectionService.retrieveCollectionByEntityType(context, item, entityType); WorkspaceItem workspaceItem = workspaceItemService.create(context, collection, false); relatedItem = workspaceItem.getItem(); if (StringUtils.isNotBlank(entityType)) { - itemService.addMetadata(context, relatedItem, "relationship", "type", null, null, entityType); + itemService.addMetadata(context, relatedItem, "dspace", "entity", "type", null, entityType); } for (String key : entityMetadata.keySet()) { String value = getValue(message, key); 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 e54f609389d8..7a12c1c5455a 100644 --- a/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java @@ -1017,6 +1017,58 @@ private DiscoverResult retrieveCollectionsWithSubmit(Context context, DiscoverQu return resp; } + @Override + public Collection retrieveCollectionByEntityType(Context context, Item item, String entityType) + throws SQLException { + Collection ownCollection = item.getOwningCollection(); + return retrieveCollectionByEntityType(context, ownCollection.getCommunities(), entityType); + } + + private Collection retrieveCollectionByEntityType(Context context, List communities, String entityType) { + + for (Community community : communities) { + Collection collection = retrieveCollectionByCommunityAndEntityType(context, community, entityType); + if (collection != null) { + return collection; + } + } + + for (Community community : communities) { + List parentCommunities = community.getParentCommunities(); + Collection collection = retrieveCollectionByEntityType(context, parentCommunities, entityType); + if (collection != null) { + return collection; + } + } + + return retrieveCollectionByCommunityAndEntityType(context, null, entityType); + } + + @Override + public Collection retrieveCollectionByCommunityAndEntityType(Context context, Community community, + String entityType) { + context.turnOffAuthorisationSystem(); + List collections; + try { + collections = findCollectionsWithSubmit(null, context, community, entityType, 0, 1); + } catch (SQLException | SearchServiceException e) { + throw new RuntimeException(e); + } + context.restoreAuthSystemState(); + if (collections != null && collections.size() > 0) { + return collections.get(0); + } + if (community != null) { + for (Community subCommunity : community.getSubcommunities()) { + Collection collection = retrieveCollectionByCommunityAndEntityType(context, subCommunity, entityType); + if (collection != null) { + return collection; + } + } + } + return null; + } + @Override public List findCollectionsWithSubmit(String q, Context context, Community community, String entityType, int offset, int limit) throws SQLException, SearchServiceException { diff --git a/dspace-api/src/main/java/org/dspace/content/service/CollectionService.java b/dspace-api/src/main/java/org/dspace/content/service/CollectionService.java index 522bdac22480..07d4d113b704 100644 --- a/dspace-api/src/main/java/org/dspace/content/service/CollectionService.java +++ b/dspace-api/src/main/java/org/dspace/content/service/CollectionService.java @@ -412,6 +412,33 @@ public List findCollectionsWithSubmit(String q, Context context, Com public List findCollectionsWithSubmit(String q, Context context, Community community, int offset, int limit) throws SQLException, SearchServiceException; + /** + * Retrieve the first collection in the community or its descending that support + * the provided entityType + * + * @param context the DSpace context + * @param community the root from where the search start + * @param entityType the requested entity type + * @return the first collection in the community or its descending + * that support the provided entityType + */ + public Collection retrieveCollectionByCommunityAndEntityType(Context context, Community community, + String entityType); + + /** + * Retrieve the close collection to the item that support the provided + * entityType. Close mean the collection that can be reach with the minimum + * steps starting from the item (owningCollection, brothers collections, etc) + * + * @param context the DSpace context + * @param item the item from where the search start + * @param entityType the requested entity type + * @return the first collection in the community or its descending + * that support the provided entityType + */ + public Collection retrieveCollectionByEntityType(Context context, Item item, String entityType) + throws SQLException; + /** * Counts the number of Collection for which the current user has 'submit' privileges. * NOTE: for better performance, this method retrieves its results from an index (cache) From 8952fa7cf1c493dff1112b9f577b41a84ce130fc Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Wed, 16 Feb 2022 18:50:55 +0100 Subject: [PATCH 003/510] [CST-5246] Added support for multiple providers --- .../app/nbevent/NBEntityMetadataAction.java | 22 ++- .../app/nbevent/NBEventActionServiceImpl.java | 12 +- .../dspace/app/nbevent/NBEventsRunnable.java | 2 + .../app/nbevent/NBMetadataMapAction.java | 13 +- .../app/nbevent/NBSimpleMetadataAction.java | 3 +- .../java/org/dspace/app/nbevent/NBSource.java | 46 +++++ .../app/nbevent/service/NBEventService.java | 14 +- .../app/nbevent/service/dto/MessageDto.java | 157 +--------------- .../service/dto/OpenaireMessageDto.java | 167 ++++++++++++++++++ .../service/impl/NBEventServiceImpl.java | 75 ++++++++ .../main/java/org/dspace/content/NBEvent.java | 30 ++-- .../java/org/dspace/content/NBSourceName.java | 13 ++ .../org/dspace/builder/NBEventBuilder.java | 7 +- .../app/rest/converter/NBEventConverter.java | 43 +++-- .../app/rest/converter/NBSourceConverter.java | 33 ++++ .../app/rest/model/NBEventMessageRest.java | 78 +------- .../dspace/app/rest/model/NBEventRest.java | 15 ++ .../dspace/app/rest/model/NBSourceRest.java | 69 ++++++++ .../model/OpenaireNBEventMessageRest.java | 88 +++++++++ .../rest/model/hateoas/NBSourceResource.java | 21 +++ .../repository/NBSourceRestRepository.java | 53 ++++++ .../repository/NBTopicRestRepository.java | 14 ++ .../app/rest/matcher/NBEventMatcher.java | 7 +- dspace/solr/nbevent/conf/schema.xml | 1 + 24 files changed, 704 insertions(+), 279 deletions(-) create mode 100644 dspace-api/src/main/java/org/dspace/app/nbevent/NBSource.java create mode 100644 dspace-api/src/main/java/org/dspace/app/nbevent/service/dto/OpenaireMessageDto.java create mode 100644 dspace-api/src/main/java/org/dspace/content/NBSourceName.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/NBSourceConverter.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/model/NBSourceRest.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/model/OpenaireNBEventMessageRest.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/NBSourceResource.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NBSourceRestRepository.java diff --git a/dspace-api/src/main/java/org/dspace/app/nbevent/NBEntityMetadataAction.java b/dspace-api/src/main/java/org/dspace/app/nbevent/NBEntityMetadataAction.java index 2e5622cf4b82..5e6b96c0b440 100644 --- a/dspace-api/src/main/java/org/dspace/app/nbevent/NBEntityMetadataAction.java +++ b/dspace-api/src/main/java/org/dspace/app/nbevent/NBEntityMetadataAction.java @@ -12,6 +12,7 @@ import org.apache.commons.lang3.StringUtils; import org.dspace.app.nbevent.service.dto.MessageDto; +import org.dspace.app.nbevent.service.dto.OpenaireMessageDto; import org.dspace.authorize.AuthorizeException; import org.dspace.content.Collection; import org.dspace.content.EntityType; @@ -141,21 +142,28 @@ private void link(Context context, Item item, Item relatedItem) throws SQLExcept } private String getValue(MessageDto message, String key) { + if (!(message instanceof OpenaireMessageDto)) { + return null; + } + + OpenaireMessageDto openaireMessage = (OpenaireMessageDto) message; + if (StringUtils.equals(key, "acronym")) { - return message.getAcronym(); + return openaireMessage.getAcronym(); } else if (StringUtils.equals(key, "code")) { - return message.getCode(); + return openaireMessage.getCode(); } else if (StringUtils.equals(key, "funder")) { - return message.getFunder(); + return openaireMessage.getFunder(); } else if (StringUtils.equals(key, "fundingProgram")) { - return message.getFundingProgram(); + return openaireMessage.getFundingProgram(); } else if (StringUtils.equals(key, "jurisdiction")) { - return message.getJurisdiction(); + return openaireMessage.getJurisdiction(); } else if (StringUtils.equals(key, "openaireId")) { - return message.getOpenaireId(); + return openaireMessage.getOpenaireId(); } else if (StringUtils.equals(key, "title")) { - return message.getTitle(); + return openaireMessage.getTitle(); } + return null; } } diff --git a/dspace-api/src/main/java/org/dspace/app/nbevent/NBEventActionServiceImpl.java b/dspace-api/src/main/java/org/dspace/app/nbevent/NBEventActionServiceImpl.java index b34ac6cd255f..2d84e8f9ba94 100644 --- a/dspace-api/src/main/java/org/dspace/app/nbevent/NBEventActionServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/app/nbevent/NBEventActionServiceImpl.java @@ -26,6 +26,7 @@ import org.apache.logging.log4j.Logger; import org.dspace.app.nbevent.service.NBEventService; import org.dspace.app.nbevent.service.dto.MessageDto; +import org.dspace.app.nbevent.service.dto.OpenaireMessageDto; import org.dspace.content.Item; import org.dspace.content.NBEvent; import org.dspace.content.service.ItemService; @@ -72,7 +73,7 @@ public void accept(Context context, NBEvent nbevent) { related = itemService.find(context, UUID.fromString(nbevent.getRelated())); } topicsToActions.get(nbevent.getTopic()).applyCorrection(context, item, related, - jsonMapper.readValue(nbevent.getMessage(), MessageDto.class)); + jsonMapper.readValue(nbevent.getMessage(), getMessageDtoClass(nbevent))); nbEventService.deleteEventByEventId(context, nbevent.getEventId()); makeAcknowledgement(nbevent.getEventId(), NBEvent.ACCEPTED); } catch (SQLException | JsonProcessingException e) { @@ -80,6 +81,15 @@ public void accept(Context context, NBEvent nbevent) { } } + private Class getMessageDtoClass(NBEvent modelObject) { + switch (modelObject.getSource()) { + case OPENAIRE: + return OpenaireMessageDto.class; + default: + throw new IllegalArgumentException("Unknown event's source: " + modelObject.getSource()); + } + } + @Override public void discard(Context context, NBEvent nbevent) { nbEventService.deleteEventByEventId(context, nbevent.getEventId()); diff --git a/dspace-api/src/main/java/org/dspace/app/nbevent/NBEventsRunnable.java b/dspace-api/src/main/java/org/dspace/app/nbevent/NBEventsRunnable.java index fc0e7b9dae34..80f7f5dabb5d 100644 --- a/dspace-api/src/main/java/org/dspace/app/nbevent/NBEventsRunnable.java +++ b/dspace-api/src/main/java/org/dspace/app/nbevent/NBEventsRunnable.java @@ -21,6 +21,7 @@ import org.apache.commons.lang3.StringUtils; import org.dspace.app.nbevent.service.NBEventService; import org.dspace.content.NBEvent; +import org.dspace.content.NBSourceName; import org.dspace.core.Context; import org.dspace.eperson.EPerson; import org.dspace.eperson.factory.EPersonServiceFactory; @@ -102,6 +103,7 @@ public void internalRun() throws Exception { } for (NBEvent entry : entries) { + entry.setSource(NBSourceName.OPENAIRE); if (!StringUtils.equalsAny(entry.getTopic(), topicsToImport)) { LOGGER.info("Skip event for topic " + entry.getTopic() + " is not allowed in the oaire-nbevents.cfg"); continue; diff --git a/dspace-api/src/main/java/org/dspace/app/nbevent/NBMetadataMapAction.java b/dspace-api/src/main/java/org/dspace/app/nbevent/NBMetadataMapAction.java index ca198abaacf3..7e9de849b3a2 100644 --- a/dspace-api/src/main/java/org/dspace/app/nbevent/NBMetadataMapAction.java +++ b/dspace-api/src/main/java/org/dspace/app/nbevent/NBMetadataMapAction.java @@ -11,6 +11,7 @@ import java.util.Map; import org.dspace.app.nbevent.service.dto.MessageDto; +import org.dspace.app.nbevent.service.dto.OpenaireMessageDto; import org.dspace.authorize.AuthorizeException; import org.dspace.content.Item; import org.dspace.content.service.ItemService; @@ -38,13 +39,21 @@ public void setTypes(Map types) { @Override public void applyCorrection(Context context, Item item, Item relatedItem, MessageDto message) { + + if (!(message instanceof OpenaireMessageDto)) { + throw new IllegalArgumentException("Unsupported message type: " + message.getClass()); + } + + OpenaireMessageDto openaireMessage = (OpenaireMessageDto) message; + try { - String targetMetadata = types.get(message.getType()); + String targetMetadata = types.get(openaireMessage.getType()); if (targetMetadata == null) { targetMetadata = types.get(DEFAULT); } String[] metadata = splitMetadata(targetMetadata); - itemService.addMetadata(context, item, metadata[0], metadata[1], metadata[2], null, message.getValue()); + itemService.addMetadata(context, item, metadata[0], metadata[1], metadata[2], null, + openaireMessage.getValue()); itemService.update(context, item); } catch (SQLException | AuthorizeException e) { throw new RuntimeException(e); diff --git a/dspace-api/src/main/java/org/dspace/app/nbevent/NBSimpleMetadataAction.java b/dspace-api/src/main/java/org/dspace/app/nbevent/NBSimpleMetadataAction.java index 33eaebcfaab2..910f799ad886 100644 --- a/dspace-api/src/main/java/org/dspace/app/nbevent/NBSimpleMetadataAction.java +++ b/dspace-api/src/main/java/org/dspace/app/nbevent/NBSimpleMetadataAction.java @@ -10,6 +10,7 @@ import java.sql.SQLException; import org.dspace.app.nbevent.service.dto.MessageDto; +import org.dspace.app.nbevent.service.dto.OpenaireMessageDto; import org.dspace.authorize.AuthorizeException; import org.dspace.content.Item; import org.dspace.content.service.ItemService; @@ -46,7 +47,7 @@ public void setMetadata(String metadata) { public void applyCorrection(Context context, Item item, Item relatedItem, MessageDto message) { try { itemService.addMetadata(context, item, metadataSchema, metadataElement, metadataQualifier, null, - message.getAbstracts()); + ((OpenaireMessageDto) message).getAbstracts()); itemService.update(context, item); } catch (SQLException | AuthorizeException e) { throw new RuntimeException(e); diff --git a/dspace-api/src/main/java/org/dspace/app/nbevent/NBSource.java b/dspace-api/src/main/java/org/dspace/app/nbevent/NBSource.java new file mode 100644 index 000000000000..e74547d531f6 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/nbevent/NBSource.java @@ -0,0 +1,46 @@ +/** + * 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.nbevent; + +import java.util.Date; + +/** + * This model class represent the notification broker source concept + * + * @author Luca Giamminonni (luca.giamminonni at 4Science) + * + */ +public class NBSource { + private String name; + private long totalEvents; + private Date lastEvent; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public long getTotalEvents() { + return totalEvents; + } + + public void setTotalEvents(long totalEvents) { + this.totalEvents = totalEvents; + } + + public Date getLastEvent() { + return lastEvent; + } + + public void setLastEvent(Date lastEvent) { + this.lastEvent = lastEvent; + } +} diff --git a/dspace-api/src/main/java/org/dspace/app/nbevent/service/NBEventService.java b/dspace-api/src/main/java/org/dspace/app/nbevent/service/NBEventService.java index 146f0d12904f..bb3b5bbc4978 100644 --- a/dspace-api/src/main/java/org/dspace/app/nbevent/service/NBEventService.java +++ b/dspace-api/src/main/java/org/dspace/app/nbevent/service/NBEventService.java @@ -10,18 +10,22 @@ import java.util.List; import java.util.UUID; +import org.dspace.app.nbevent.NBSource; import org.dspace.app.nbevent.NBTopic; import org.dspace.content.NBEvent; +import org.dspace.content.NBSourceName; import org.dspace.core.Context; public interface NBEventService { - public NBTopic findTopicByTopicId(String topicId); - public List findAllTopics(Context context, long offset, long pageSize); + public List findAllTopicsBySource(Context context, NBSourceName source, long offset, long count); + public long countTopics(Context context); + public long countTopicsBySource(Context context, NBSourceName source); + public List findEventsByTopicAndPage(Context context, String topic, long offset, int pageSize, String orderField, boolean ascending); @@ -36,4 +40,10 @@ public List findEventsByTopicAndPage(Context context, String topic, public void deleteEventsByTargetId(Context context, UUID targetId); + public NBTopic findTopicByTopicId(String topicId); + + public NBSource findSource(NBSourceName source); + + public List findAllSources(Context context, long offset, int pageSize); + } diff --git a/dspace-api/src/main/java/org/dspace/app/nbevent/service/dto/MessageDto.java b/dspace-api/src/main/java/org/dspace/app/nbevent/service/dto/MessageDto.java index 6b72c58ee433..55c1722de8a5 100644 --- a/dspace-api/src/main/java/org/dspace/app/nbevent/service/dto/MessageDto.java +++ b/dspace-api/src/main/java/org/dspace/app/nbevent/service/dto/MessageDto.java @@ -7,162 +7,7 @@ */ package org.dspace.app.nbevent.service.dto; -import com.fasterxml.jackson.annotation.JsonProperty; +public interface MessageDto { -public class MessageDto { - @JsonProperty("pids[0].value") - private String value; - - @JsonProperty("pids[0].type") - private String type; - - @JsonProperty("instances[0].hostedby") - private String instanceHostedBy; - - @JsonProperty("instances[0].instancetype") - private String instanceInstanceType; - - @JsonProperty("instances[0].license") - private String instanceLicense; - - @JsonProperty("instances[0].url") - private String instanceUrl; - - @JsonProperty("abstracts[0]") - private String abstracts; - - @JsonProperty("projects[0].acronym") - private String acronym; - - @JsonProperty("projects[0].code") - private String code; - - @JsonProperty("projects[0].funder") - private String funder; - - @JsonProperty("projects[0].fundingProgram") - private String fundingProgram; - - @JsonProperty("projects[0].jurisdiction") - private String jurisdiction; - - @JsonProperty("projects[0].openaireId") - private String openaireId; - - @JsonProperty("projects[0].title") - private String title; - - - public String getValue() { - return value; - } - - public void setValue(String value) { - this.value = value; - } - - public String getType() { - return type; - } - - public void setType(String type) { - this.type = type; - } - - public String getInstanceHostedBy() { - return instanceHostedBy; - } - - public void setInstanceHostedBy(String instanceHostedBy) { - this.instanceHostedBy = instanceHostedBy; - } - - public String getInstanceInstanceType() { - return instanceInstanceType; - } - - public void setInstanceInstanceType(String instanceInstanceType) { - this.instanceInstanceType = instanceInstanceType; - } - - public String getInstanceLicense() { - return instanceLicense; - } - - public void setInstanceLicense(String instanceLicense) { - this.instanceLicense = instanceLicense; - } - - public String getInstanceUrl() { - return instanceUrl; - } - - public void setInstanceUrl(String instanceUrl) { - this.instanceUrl = instanceUrl; - } - - public String getAbstracts() { - return abstracts; - } - - public void setAbstracts(String abstracts) { - this.abstracts = abstracts; - } - - public String getAcronym() { - return acronym; - } - - public void setAcronym(String acronym) { - this.acronym = acronym; - } - - public String getCode() { - return code; - } - - public void setCode(String code) { - this.code = code; - } - - public String getFunder() { - return funder; - } - - public void setFunder(String funder) { - this.funder = funder; - } - - public String getFundingProgram() { - return fundingProgram; - } - - public void setFundingProgram(String fundingProgram) { - this.fundingProgram = fundingProgram; - } - - public String getJurisdiction() { - return jurisdiction; - } - - public void setJurisdiction(String jurisdiction) { - this.jurisdiction = jurisdiction; - } - - public String getOpenaireId() { - return openaireId; - } - - public void setOpenaireId(String openaireId) { - this.openaireId = openaireId; - } - - public String getTitle() { - return title; - } - - public void setTitle(String title) { - this.title = title; - } } diff --git a/dspace-api/src/main/java/org/dspace/app/nbevent/service/dto/OpenaireMessageDto.java b/dspace-api/src/main/java/org/dspace/app/nbevent/service/dto/OpenaireMessageDto.java new file mode 100644 index 000000000000..5ae6b29c3abb --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/nbevent/service/dto/OpenaireMessageDto.java @@ -0,0 +1,167 @@ +/** + * 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.nbevent.service.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class OpenaireMessageDto implements MessageDto { + + @JsonProperty("pids[0].value") + private String value; + + @JsonProperty("pids[0].type") + private String type; + + @JsonProperty("instances[0].hostedby") + private String instanceHostedBy; + + @JsonProperty("instances[0].instancetype") + private String instanceInstanceType; + + @JsonProperty("instances[0].license") + private String instanceLicense; + + @JsonProperty("instances[0].url") + private String instanceUrl; + + @JsonProperty("abstracts[0]") + private String abstracts; + + @JsonProperty("projects[0].acronym") + private String acronym; + + @JsonProperty("projects[0].code") + private String code; + + @JsonProperty("projects[0].funder") + private String funder; + + @JsonProperty("projects[0].fundingProgram") + private String fundingProgram; + + @JsonProperty("projects[0].jurisdiction") + private String jurisdiction; + + @JsonProperty("projects[0].openaireId") + private String openaireId; + + @JsonProperty("projects[0].title") + private String title; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getInstanceHostedBy() { + return instanceHostedBy; + } + + public void setInstanceHostedBy(String instanceHostedBy) { + this.instanceHostedBy = instanceHostedBy; + } + + public String getInstanceInstanceType() { + return instanceInstanceType; + } + + public void setInstanceInstanceType(String instanceInstanceType) { + this.instanceInstanceType = instanceInstanceType; + } + + public String getInstanceLicense() { + return instanceLicense; + } + + public void setInstanceLicense(String instanceLicense) { + this.instanceLicense = instanceLicense; + } + + public String getInstanceUrl() { + return instanceUrl; + } + + public void setInstanceUrl(String instanceUrl) { + this.instanceUrl = instanceUrl; + } + + public String getAbstracts() { + return abstracts; + } + + public void setAbstracts(String abstracts) { + this.abstracts = abstracts; + } + + public String getAcronym() { + return acronym; + } + + public void setAcronym(String acronym) { + this.acronym = acronym; + } + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + + public String getFunder() { + return funder; + } + + public void setFunder(String funder) { + this.funder = funder; + } + + public String getFundingProgram() { + return fundingProgram; + } + + public void setFundingProgram(String fundingProgram) { + this.fundingProgram = fundingProgram; + } + + public String getJurisdiction() { + return jurisdiction; + } + + public void setJurisdiction(String jurisdiction) { + this.jurisdiction = jurisdiction; + } + + public String getOpenaireId() { + return openaireId; + } + + public void setOpenaireId(String openaireId) { + this.openaireId = openaireId; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } +} diff --git a/dspace-api/src/main/java/org/dspace/app/nbevent/service/impl/NBEventServiceImpl.java b/dspace-api/src/main/java/org/dspace/app/nbevent/service/impl/NBEventServiceImpl.java index 8901079425ca..6f745d080085 100644 --- a/dspace-api/src/main/java/org/dspace/app/nbevent/service/impl/NBEventServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/app/nbevent/service/impl/NBEventServiceImpl.java @@ -9,9 +9,11 @@ import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Date; import java.util.List; import java.util.UUID; +import java.util.stream.Collectors; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; @@ -29,11 +31,13 @@ import org.apache.solr.common.SolrDocument; import org.apache.solr.common.SolrDocumentList; import org.apache.solr.common.SolrInputDocument; +import org.dspace.app.nbevent.NBSource; import org.dspace.app.nbevent.NBTopic; import org.dspace.app.nbevent.dao.impl.NBEventsDaoImpl; import org.dspace.app.nbevent.service.NBEventService; import org.dspace.content.Item; import org.dspace.content.NBEvent; +import org.dspace.content.NBSourceName; import org.dspace.content.service.ItemService; import org.dspace.core.Context; import org.dspace.handle.service.HandleService; @@ -69,6 +73,7 @@ public NBEventServiceImpl() { */ protected SolrClient solr = null; + public static final String SOURCE = "source"; public static final String ORIGINAL_ID = "original_id"; public static final String TITLE = "title"; public static final String TOPIC = "topic"; @@ -106,6 +111,25 @@ public long countTopics(Context context) { return response.getFacetField(TOPIC).getValueCount(); } + @Override + public long countTopicsBySource(Context context, NBSourceName source) { + SolrQuery solrQuery = new SolrQuery(); + solrQuery.setRows(0); + solrQuery.setQuery("*:*"); + solrQuery.setFacet(true); + // we would like to get eventually topic that has no longer active nb events + solrQuery.setFacetMinCount(0); + solrQuery.addFacetField(TOPIC); + solrQuery.addFilterQuery("source:" + source); + QueryResponse response; + try { + response = getSolr().query(solrQuery); + } catch (SolrServerException | IOException e) { + throw new RuntimeException(e); + } + return response.getFacetField(TOPIC).getValueCount(); + } + @Override public void deleteEventByEventId(Context context, String id) { try { @@ -169,6 +193,11 @@ public NBTopic findTopicByTopicId(String topicId) { */ @Override public List findAllTopics(Context context, long offset, long count) { + return findAllTopicsBySource(context, null, offset, count); + } + + @Override + public List findAllTopicsBySource(Context context, NBSourceName source, long offset, long count) { SolrQuery solrQuery = new SolrQuery(); solrQuery.setRows(0); solrQuery.setQuery("*:*"); @@ -177,6 +206,9 @@ public List findAllTopics(Context context, long offset, long count) { solrQuery.setFacetMinCount(0); solrQuery.setFacetLimit((int) (offset + count)); solrQuery.addFacetField(TOPIC); + if (source != null) { + solrQuery.addFilterQuery("source:" + source); + } QueryResponse response; List nbTopics = null; try { @@ -212,6 +244,7 @@ public void store(Context context, NBEvent dto) { try { if (!nbEventsDao.isEventStored(context, checksum)) { SolrInputDocument doc = new SolrInputDocument(); + doc.addField(SOURCE, dto.getSource().name()); doc.addField(EVENT_ID, checksum); doc.addField(ORIGINAL_ID, dto.getOriginalId()); doc.addField(TITLE, dto.getTitle()); @@ -258,6 +291,7 @@ public NBEvent findEventByEventId(Context context, String eventId) { private NBEvent getNBEventFromSOLR(SolrDocument doc) { NBEvent item = new NBEvent(); + item.setSource(NBSourceName.valueOf((String) doc.get(SOURCE))); item.setEventId((String) doc.get(EVENT_ID)); item.setLastUpdate((Date) doc.get(LAST_UPDATE)); item.setMessage((String) doc.get(MESSAGE)); @@ -337,4 +371,45 @@ private String getHandleFromOriginalId(String originalId) { } } + @Override + public NBSource findSource(NBSourceName sourceName) { + SolrQuery solrQuery = new SolrQuery(); + solrQuery.setRows(0); + solrQuery.setQuery(SOURCE + ":" + sourceName); + solrQuery.setFacet(true); + // we would like to get eventually topic that has no longer active nb events + solrQuery.setFacetMinCount(0); + solrQuery.addFacetField(SOURCE); + QueryResponse response; + try { + response = getSolr().query(solrQuery); + FacetField facetField = response.getFacetField(SOURCE); + for (Count c : facetField.getValues()) { + if (c.getName().equalsIgnoreCase(sourceName.name())) { + NBSource source = new NBSource(); + source.setName(c.getName()); + source.setTotalEvents(c.getCount()); + source.setLastEvent(new Date()); + return source; + } + } + } catch (SolrServerException | IOException e) { + throw new RuntimeException(e); + } + + NBSource source = new NBSource(); + source.setName(sourceName.name()); + source.setTotalEvents(0L); + return source; + } + + @Override + public List findAllSources(Context context, long offset, int pageSize) { + return Arrays.stream(NBSourceName.values()).sorted() + .map((sourceName) -> findSource(sourceName)) + .skip(offset) + .limit(pageSize) + .collect(Collectors.toList()); + } + } diff --git a/dspace-api/src/main/java/org/dspace/content/NBEvent.java b/dspace-api/src/main/java/org/dspace/content/NBEvent.java index c92001691724..950fc37d01a7 100644 --- a/dspace-api/src/main/java/org/dspace/content/NBEvent.java +++ b/dspace-api/src/main/java/org/dspace/content/NBEvent.java @@ -13,7 +13,6 @@ import java.util.Date; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import org.apache.solr.client.solrj.beans.Field; import org.dspace.app.nbevent.RawJsonDeserializer; /** @@ -27,32 +26,26 @@ public class NBEvent { public static final String ACCEPTED = "accepted"; public static final String REJECTED = "rejected"; public static final String DISCARDED = "discarded"; - @Field("event_id") + + private NBSourceName source; + private String eventId; - @Field("original_id") private String originalId; - @Field("resource_uuid") private String target; - @Field("related_uuid") private String related; - @Field("title") private String title; - @Field("topic") private String topic; - @Field("trust") private double trust; - @Field("message") @JsonDeserialize(using = RawJsonDeserializer.class) private String message; - @Field("last_update") private Date lastUpdate; private String status = "PENDING"; @@ -60,9 +53,10 @@ public class NBEvent { public NBEvent() { } - public NBEvent(String originalId, String target, String title, String topic, double trust, String message, - Date lastUpdate) { + public NBEvent(NBSourceName source, String originalId, String target, String title, + String topic, double trust, String message, Date lastUpdate) { super(); + this.source = source; this.originalId = originalId; this.target = target; this.title = title; @@ -165,14 +159,22 @@ public String getStatus() { return status; } + public NBSourceName getSource() { + return source != null ? source : NBSourceName.OPENAIRE; + } + + public void setSource(NBSourceName source) { + this.source = source; + } + /* * DTO constructed via Jackson use empty constructor. In this case, the eventId * must be compute on the get method */ private void computedEventId() throws NoSuchAlgorithmException, UnsupportedEncodingException { MessageDigest digester = MessageDigest.getInstance("MD5"); - String dataToString = "originalId=" + originalId + ", title=" + title + ", topic=" + topic + ", trust=" + trust - + ", message=" + message; + String dataToString = "source=" + source + ",originalId=" + originalId + ", title=" + title + ", topic=" + + topic + ", trust=" + trust + ", message=" + message; digester.update(dataToString.getBytes("UTF-8")); byte[] signature = digester.digest(); char[] arr = new char[signature.length << 1]; diff --git a/dspace-api/src/main/java/org/dspace/content/NBSourceName.java b/dspace-api/src/main/java/org/dspace/content/NBSourceName.java new file mode 100644 index 000000000000..705cc2605834 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/NBSourceName.java @@ -0,0 +1,13 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.content; + +public enum NBSourceName { + + OPENAIRE; +} diff --git a/dspace-api/src/test/java/org/dspace/builder/NBEventBuilder.java b/dspace-api/src/test/java/org/dspace/builder/NBEventBuilder.java index 9101d3bf5e91..57e5c2a2fe76 100644 --- a/dspace-api/src/test/java/org/dspace/builder/NBEventBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/NBEventBuilder.java @@ -13,6 +13,7 @@ import org.dspace.content.Collection; import org.dspace.content.Item; import org.dspace.content.NBEvent; +import org.dspace.content.NBSourceName; import org.dspace.core.Context; /** @@ -24,7 +25,7 @@ public class NBEventBuilder extends AbstractBuilder { private Item item; private NBEvent target; - + private NBSourceName source = NBSourceName.OPENAIRE; private String title; private String topic; private String message; @@ -95,8 +96,8 @@ public NBEventBuilder withRelatedItem(String relatedItem) { @Override public NBEvent build() { - target = new NBEvent("oai:www.dspace.org:" + item.getHandle(), item.getID().toString(), title, topic, trust, - message, lastUpdate); + target = new NBEvent(source, "oai:www.dspace.org:" + item.getHandle(), item.getID().toString(), title, topic, + trust, message, lastUpdate); target.setRelated(relatedItem); try { nbEventService.store(context, target); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/NBEventConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/NBEventConverter.java index 3534e3c31097..82230e8eeeb9 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/NBEventConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/NBEventConverter.java @@ -14,8 +14,10 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.json.JsonMapper; import org.dspace.app.nbevent.service.dto.MessageDto; +import org.dspace.app.nbevent.service.dto.OpenaireMessageDto; import org.dspace.app.rest.model.NBEventMessageRest; import org.dspace.app.rest.model.NBEventRest; +import org.dspace.app.rest.model.OpenaireNBEventMessageRest; import org.dspace.app.rest.projection.Projection; import org.dspace.content.NBEvent; import org.springframework.stereotype.Component; @@ -36,7 +38,8 @@ public NBEventRest convert(NBEvent modelObject, Projection projection) { NBEventRest rest = new NBEventRest(); rest.setId(modelObject.getEventId()); try { - rest.setMessage(convertMessage(jsonMapper.readValue(modelObject.getMessage(), MessageDto.class))); + rest.setMessage(convertMessage(jsonMapper.readValue(modelObject.getMessage(), + getMessageDtoClass(modelObject)))); } catch (JsonProcessingException e) { throw new RuntimeException(e); } @@ -51,19 +54,33 @@ public NBEventRest convert(NBEvent modelObject, Projection projection) { return rest; } + private Class getMessageDtoClass(NBEvent modelObject) { + switch (modelObject.getSource()) { + case OPENAIRE: + return OpenaireMessageDto.class; + default: + throw new IllegalArgumentException("Unknown event's source: " + modelObject.getSource()); + } + } + private NBEventMessageRest convertMessage(MessageDto dto) { - NBEventMessageRest message = new NBEventMessageRest(); - message.setAbstractValue(dto.getAbstracts()); - message.setOpenaireId(dto.getOpenaireId()); - message.setAcronym(dto.getAcronym()); - message.setCode(dto.getCode()); - message.setFunder(dto.getFunder()); - message.setFundingProgram(dto.getFundingProgram()); - message.setJurisdiction(dto.getJurisdiction()); - message.setTitle(dto.getTitle()); - message.setType(dto.getType()); - message.setValue(dto.getValue()); - return message; + if (dto instanceof OpenaireMessageDto) { + OpenaireMessageDto openaireDto = (OpenaireMessageDto) dto; + OpenaireNBEventMessageRest message = new OpenaireNBEventMessageRest(); + message.setAbstractValue(openaireDto.getAbstracts()); + message.setOpenaireId(openaireDto.getOpenaireId()); + message.setAcronym(openaireDto.getAcronym()); + message.setCode(openaireDto.getCode()); + message.setFunder(openaireDto.getFunder()); + message.setFundingProgram(openaireDto.getFundingProgram()); + message.setJurisdiction(openaireDto.getJurisdiction()); + message.setTitle(openaireDto.getTitle()); + message.setType(openaireDto.getType()); + message.setValue(openaireDto.getValue()); + return message; + } + + throw new IllegalArgumentException("Unknown message type: " + dto.getClass()); } @Override diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/NBSourceConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/NBSourceConverter.java new file mode 100644 index 000000000000..7524cc797559 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/NBSourceConverter.java @@ -0,0 +1,33 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.converter; + +import org.dspace.app.nbevent.NBSource; +import org.dspace.app.rest.model.NBSourceRest; +import org.dspace.app.rest.projection.Projection; +import org.springframework.stereotype.Component; + +@Component +public class NBSourceConverter implements DSpaceConverter { + + @Override + public Class getModelClass() { + return NBSource.class; + } + + @Override + public NBSourceRest convert(NBSource modelObject, Projection projection) { + NBSourceRest rest = new NBSourceRest(); + rest.setProjection(projection); + rest.setId(modelObject.getName()); + rest.setLastEvent(modelObject.getLastEvent()); + rest.setTotalEvents(modelObject.getTotalEvents()); + return rest; + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/NBEventMessageRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/NBEventMessageRest.java index 7d0f24a21d2c..7c2e03ac3428 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/NBEventMessageRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/NBEventMessageRest.java @@ -7,82 +7,6 @@ */ package org.dspace.app.rest.model; -import com.fasterxml.jackson.annotation.JsonProperty; - -public class NBEventMessageRest { - // pids - private String type; - private String value; - // abstract - @JsonProperty(value = "abstract") - private String abstractValue; - // project - private String openaireId; - private String acronym; - private String code; - private String funder; - private String fundingProgram; - private String jurisdiction; - private String title; - public String getType() { - return type; - } - public void setType(String type) { - this.type = type; - } - public String getValue() { - return value; - } - public void setValue(String value) { - this.value = value; - } - public String getAbstractValue() { - return abstractValue; - } - public void setAbstractValue(String abstractValue) { - this.abstractValue = abstractValue; - } - public String getOpenaireId() { - return openaireId; - } - public void setOpenaireId(String openaireId) { - this.openaireId = openaireId; - } - public String getAcronym() { - return acronym; - } - public void setAcronym(String acronym) { - this.acronym = acronym; - } - public String getCode() { - return code; - } - public void setCode(String code) { - this.code = code; - } - public String getFunder() { - return funder; - } - public void setFunder(String funder) { - this.funder = funder; - } - public String getFundingProgram() { - return fundingProgram; - } - public void setFundingProgram(String fundingProgram) { - this.fundingProgram = fundingProgram; - } - public String getJurisdiction() { - return jurisdiction; - } - public void setJurisdiction(String jurisdiction) { - this.jurisdiction = jurisdiction; - } - public String getTitle() { - return title; - } - public void setTitle(String title) { - this.title = title; - } +public interface NBEventMessageRest { } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/NBEventRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/NBEventRest.java index de2cea32fa6c..60dbce6d9b8e 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/NBEventRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/NBEventRest.java @@ -26,6 +26,7 @@ public class NBEventRest extends BaseObjectRest { public static final String TOPIC = "topic"; public static final String TARGET = "target"; public static final String RELATED = "related"; + private String source; private String originalId; private String title; private String topic; @@ -112,4 +113,18 @@ public String getStatus() { public void setStatus(String status) { this.status = status; } + + /** + * @return the source + */ + public String getSource() { + return source; + } + + /** + * @param source the source to set + */ + public void setSource(String source) { + this.source = source; + } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/NBSourceRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/NBSourceRest.java new file mode 100644 index 000000000000..69e230f37835 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/NBSourceRest.java @@ -0,0 +1,69 @@ +/** + * 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.model; + +import java.util.Date; + +import org.dspace.app.rest.RestResourceController; + +/** + * REST Representation of a notification broker source + * + * @author Luca Giamminonni (luca.giamminonni at 4Science) + * + */ +public class NBSourceRest extends BaseObjectRest { + + private static final long serialVersionUID = -7455358581579629244L; + + public static final String NAME = "nbsource"; + public static final String CATEGORY = RestAddressableModel.INTEGRATION; + + private String id; + private Date lastEvent; + private long totalEvents; + + @Override + public String getType() { + return NAME; + } + + @Override + public String getCategory() { + return CATEGORY; + } + + @Override + public Class getController() { + return RestResourceController.class; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public Date getLastEvent() { + return lastEvent; + } + + public void setLastEvent(Date lastEvent) { + this.lastEvent = lastEvent; + } + + public long getTotalEvents() { + return totalEvents; + } + + public void setTotalEvents(long totalEvents) { + this.totalEvents = totalEvents; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/OpenaireNBEventMessageRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/OpenaireNBEventMessageRest.java new file mode 100644 index 000000000000..84021abb6edd --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/OpenaireNBEventMessageRest.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.model; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class OpenaireNBEventMessageRest implements NBEventMessageRest { + // pids + private String type; + private String value; + // abstract + @JsonProperty(value = "abstract") + private String abstractValue; + // project + private String openaireId; + private String acronym; + private String code; + private String funder; + private String fundingProgram; + private String jurisdiction; + private String title; + public String getType() { + return type; + } + public void setType(String type) { + this.type = type; + } + public String getValue() { + return value; + } + public void setValue(String value) { + this.value = value; + } + public String getAbstractValue() { + return abstractValue; + } + public void setAbstractValue(String abstractValue) { + this.abstractValue = abstractValue; + } + public String getOpenaireId() { + return openaireId; + } + public void setOpenaireId(String openaireId) { + this.openaireId = openaireId; + } + public String getAcronym() { + return acronym; + } + public void setAcronym(String acronym) { + this.acronym = acronym; + } + public String getCode() { + return code; + } + public void setCode(String code) { + this.code = code; + } + public String getFunder() { + return funder; + } + public void setFunder(String funder) { + this.funder = funder; + } + public String getFundingProgram() { + return fundingProgram; + } + public void setFundingProgram(String fundingProgram) { + this.fundingProgram = fundingProgram; + } + public String getJurisdiction() { + return jurisdiction; + } + public void setJurisdiction(String jurisdiction) { + this.jurisdiction = jurisdiction; + } + public String getTitle() { + return title; + } + public void setTitle(String title) { + this.title = title; + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/NBSourceResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/NBSourceResource.java new file mode 100644 index 000000000000..899b684199c1 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/NBSourceResource.java @@ -0,0 +1,21 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.model.hateoas; + +import org.dspace.app.rest.model.NBSourceRest; +import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource; +import org.dspace.app.rest.utils.Utils; + +@RelNameDSpaceResource(NBSourceRest.NAME) +public class NBSourceResource extends DSpaceResource { + + public NBSourceResource(NBSourceRest data, Utils utils) { + super(data, utils); + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NBSourceRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NBSourceRestRepository.java new file mode 100644 index 000000000000..95dfa6b61adc --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NBSourceRestRepository.java @@ -0,0 +1,53 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.repository; + +import java.util.List; + +import org.dspace.app.nbevent.NBSource; +import org.dspace.app.nbevent.service.NBEventService; +import org.dspace.app.rest.model.NBSourceRest; +import org.dspace.content.NBSourceName; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Component; + +@Component(NBSourceRest.CATEGORY + "." + NBSourceRest.NAME) +public class NBSourceRestRepository extends DSpaceRestRepository { + + @Autowired + private NBEventService nbEventService; + + @Override + @PreAuthorize("hasAuthority('ADMIN')") + public NBSourceRest findOne(Context context, String id) { + NBSource nbSource = nbEventService.findSource(NBSourceName.valueOf(id)); + return converter.toRest(nbSource, utils.obtainProjection()); + } + + @Override + @PreAuthorize("hasAuthority('ADMIN')") + public Page findAll(Context context, Pageable pageable) { + List nbTopics = nbEventService.findAllSources(context, pageable.getOffset(), pageable.getPageSize()); + long count = nbEventService.countTopics(context); + if (nbTopics == null) { + return null; + } + return converter.toRestPage(nbTopics, pageable, count, utils.obtainProjection()); + } + + + @Override + public Class getDomainClass() { + return NBSourceRest.class; + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NBTopicRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NBTopicRestRepository.java index afaf3c7346ef..c738da7bd62f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NBTopicRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NBTopicRestRepository.java @@ -11,7 +11,9 @@ import org.dspace.app.nbevent.NBTopic; import org.dspace.app.nbevent.service.NBEventService; +import org.dspace.app.rest.SearchRestMethod; import org.dspace.app.rest.model.NBTopicRest; +import org.dspace.content.NBSourceName; import org.dspace.core.Context; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; @@ -46,6 +48,18 @@ public Page findAll(Context context, Pageable pageable) { return converter.toRestPage(nbTopics, pageable, count, utils.obtainProjection()); } + @SearchRestMethod(name = "bySource") + @PreAuthorize("hasAuthority('ADMIN')") + public Page findBySource(Context context, String source, Pageable pageable) { + List nbTopics = nbEventService.findAllTopicsBySource(context, NBSourceName.valueOf(source), + pageable.getOffset(), pageable.getPageSize()); + long count = nbEventService.countTopicsBySource(context, NBSourceName.valueOf(source)); + if (nbTopics == null) { + return null; + } + return converter.toRestPage(nbTopics, pageable, count, utils.obtainProjection()); + } + @Override public Class getDomainClass() { return NBTopicRest.class; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/NBEventMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/NBEventMatcher.java index afb364bb0e3a..f8dca7e466b0 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/NBEventMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/NBEventMatcher.java @@ -18,7 +18,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.json.JsonMapper; import org.apache.commons.lang3.StringUtils; -import org.dspace.app.nbevent.service.dto.MessageDto; +import org.dspace.app.nbevent.service.dto.OpenaireMessageDto; import org.dspace.content.NBEvent; import org.hamcrest.Matcher; import org.hamcrest.Matchers; @@ -49,7 +49,8 @@ public static Matcher matchNBEventEntry(NBEvent event) { hasJsonPath("$.trust", is(new DecimalFormat("0.000").format(event.getTrust()))), hasJsonPath("$.status", Matchers.equalToIgnoringCase(event.getStatus())), hasJsonPath("$.message", - matchMessage(event.getTopic(), jsonMapper.readValue(event.getMessage(), MessageDto.class))), + matchMessage(event.getTopic(), jsonMapper.readValue(event.getMessage(), + OpenaireMessageDto.class))), hasJsonPath("$._links.target.href", Matchers.endsWith(event.getEventId() + "/target")), hasJsonPath("$._links.related.href", Matchers.endsWith(event.getEventId() + "/related")), hasJsonPath("$._links.topic.href", Matchers.endsWith(event.getEventId() + "/topic")), @@ -59,7 +60,7 @@ public static Matcher matchNBEventEntry(NBEvent event) { } } - private static Matcher matchMessage(String topic, MessageDto message) { + private static Matcher matchMessage(String topic, OpenaireMessageDto message) { if (StringUtils.endsWith(topic, "/ABSTRACT")) { return allOf(hasJsonPath("$.abstract", is(message.getAbstracts()))); } else if (StringUtils.endsWith(topic, "/PID")) { diff --git a/dspace/solr/nbevent/conf/schema.xml b/dspace/solr/nbevent/conf/schema.xml index 8ed9b4d5ae73..338fbdcdcd6b 100644 --- a/dspace/solr/nbevent/conf/schema.xml +++ b/dspace/solr/nbevent/conf/schema.xml @@ -509,6 +509,7 @@ when adding a document. --> + From 90e93a3e4075f40c08c105c500b585d156f8275b Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Thu, 17 Feb 2022 11:34:27 +0100 Subject: [PATCH 004/510] [CST-5246] Added missing java docs --- .../java/org/dspace/app/nbevent/NBAction.java | 19 ++- .../app/nbevent/NBEntityMetadataAction.java | 19 ++- .../app/nbevent/NBEventActionService.java | 26 ++++ .../app/nbevent/NBEventActionServiceImpl.java | 19 ++- .../app/nbevent/NBMetadataMapAction.java | 17 ++- .../app/nbevent/NBSimpleMetadataAction.java | 15 ++- ...OpenaireEventsCliScriptConfiguration.java} | 10 +- ...nable.java => OpenaireEventsRunnable.java} | 13 +- ...li.java => OpenaireEventsRunnableCli.java} | 14 ++- ...=> OpenaireEventsScriptConfiguration.java} | 11 +- .../app/nbevent/RawJsonDeserializer.java | 6 + .../dspace/app/nbevent/dao/NBEventsDao.java | 44 +++++-- .../app/nbevent/dao/impl/NBEventsDaoImpl.java | 7 ++ .../app/nbevent/service/NBEventService.java | 112 ++++++++++++++++-- .../dto/{MessageDto.java => NBMessage.java} | 10 +- ...reMessageDto.java => OpenaireMessage.java} | 8 +- .../service/impl/NBEventServiceImpl.java | 33 ++++-- .../main/java/org/dspace/content/NBEvent.java | 23 +++- .../java/org/dspace/content/NBSourceName.java | 13 -- ...=> V7.3_2022.02.17__nbevent_processed.sql} | 0 ...=> V7.3_2022.02.17__nbevent_processed.sql} | 0 ...=> V7.3_2022.02.17__nbevent_processed.sql} | 0 .../org/dspace/builder/NBEventBuilder.java | 3 +- .../app/rest/converter/NBEventConverter.java | 28 ++--- .../app/rest/converter/NBSourceConverter.java | 7 ++ .../app/rest/converter/NBTopicConverter.java | 7 ++ .../app/rest/model/NBEventMessageRest.java | 6 + .../dspace/app/rest/model/NBEventRest.java | 6 + .../model/OpenaireNBEventMessageRest.java | 6 + .../rest/model/hateoas/NBEventResource.java | 6 + .../rest/model/hateoas/NBSourceResource.java | 6 + .../rest/model/hateoas/NBTopicResource.java | 6 + .../repository/NBEventRestRepository.java | 6 + .../repository/NBSourceRestRepository.java | 15 ++- .../repository/NBTopicRestRepository.java | 11 +- .../NBEventStatusReplaceOperation.java | 6 + .../app/rest/NBEventRestRepositoryIT.java | 6 + .../app/rest/NBTopicRestRepositoryIT.java | 7 ++ .../app/rest/matcher/NBEventMatcher.java | 12 +- .../app/rest/matcher/NBSourceMatcher.java | 43 +++++++ .../app/rest/matcher/NBTopicMatcher.java | 7 ++ dspace/config/spring/api/scripts.xml | 4 +- dspace/config/spring/rest/scripts.xml | 4 +- 43 files changed, 501 insertions(+), 120 deletions(-) rename dspace-api/src/main/java/org/dspace/app/nbevent/{NBEventsCliScriptConfiguration.java => OpenaireEventsCliScriptConfiguration.java} (64%) rename dspace-api/src/main/java/org/dspace/app/nbevent/{NBEventsRunnable.java => OpenaireEventsRunnable.java} (90%) rename dspace-api/src/main/java/org/dspace/app/nbevent/{NBEventsRunnableCli.java => OpenaireEventsRunnableCli.java} (69%) rename dspace-api/src/main/java/org/dspace/app/nbevent/{NBEventsScriptConfiguration.java => OpenaireEventsScriptConfiguration.java} (84%) rename dspace-api/src/main/java/org/dspace/app/nbevent/service/dto/{MessageDto.java => NBMessage.java} (54%) rename dspace-api/src/main/java/org/dspace/app/nbevent/service/dto/{OpenaireMessageDto.java => OpenaireMessage.java} (94%) delete mode 100644 dspace-api/src/main/java/org/dspace/content/NBSourceName.java rename dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/{V7.0_2020.10.16__nbevent_processed.sql => V7.3_2022.02.17__nbevent_processed.sql} (100%) rename dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/{V7.0_2020.10.16__nbevent_processed.sql => V7.3_2022.02.17__nbevent_processed.sql} (100%) rename dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/{V7.0_2020.10.16__nbevent_processed.sql => V7.3_2022.02.17__nbevent_processed.sql} (100%) create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/NBSourceMatcher.java diff --git a/dspace-api/src/main/java/org/dspace/app/nbevent/NBAction.java b/dspace-api/src/main/java/org/dspace/app/nbevent/NBAction.java index 782fa53802d3..70e762419733 100644 --- a/dspace-api/src/main/java/org/dspace/app/nbevent/NBAction.java +++ b/dspace-api/src/main/java/org/dspace/app/nbevent/NBAction.java @@ -7,10 +7,25 @@ */ package org.dspace.app.nbevent; -import org.dspace.app.nbevent.service.dto.MessageDto; +import org.dspace.app.nbevent.service.dto.NBMessage; import org.dspace.content.Item; import org.dspace.core.Context; +/** + * Interface for classes that perform a correction on the given item. + * + * @author Andrea Bollini (andrea.bollini at 4science.it) + * + */ public interface NBAction { - public void applyCorrection(Context context, Item item, Item relatedItem, MessageDto message); + + /** + * Perform a correction on the given item. + * + * @param context the DSpace context + * @param item the item to correct + * @param relatedItem the related item, if any + * @param message the message with the correction details + */ + public void applyCorrection(Context context, Item item, Item relatedItem, NBMessage message); } diff --git a/dspace-api/src/main/java/org/dspace/app/nbevent/NBEntityMetadataAction.java b/dspace-api/src/main/java/org/dspace/app/nbevent/NBEntityMetadataAction.java index 5e6b96c0b440..f2322fe6b703 100644 --- a/dspace-api/src/main/java/org/dspace/app/nbevent/NBEntityMetadataAction.java +++ b/dspace-api/src/main/java/org/dspace/app/nbevent/NBEntityMetadataAction.java @@ -11,8 +11,8 @@ import java.util.Map; import org.apache.commons.lang3.StringUtils; -import org.dspace.app.nbevent.service.dto.MessageDto; -import org.dspace.app.nbevent.service.dto.OpenaireMessageDto; +import org.dspace.app.nbevent.service.dto.NBMessage; +import org.dspace.app.nbevent.service.dto.OpenaireMessage; import org.dspace.authorize.AuthorizeException; import org.dspace.content.Collection; import org.dspace.content.EntityType; @@ -30,6 +30,13 @@ import org.dspace.core.Context; import org.springframework.beans.factory.annotation.Autowired; +/** + * Implementation of {@link NBAction} that handle the relationship between the + * item to correct and a related item. + * + * @author Andrea Bollini (andrea.bollini at 4science.it) + * + */ public class NBEntityMetadataAction implements NBAction { private String relation; private String entityType; @@ -96,7 +103,7 @@ public void setEntityMetadata(Map entityMetadata) { } @Override - public void applyCorrection(Context context, Item item, Item relatedItem, MessageDto message) { + public void applyCorrection(Context context, Item item, Item relatedItem, NBMessage message) { try { if (relatedItem != null) { link(context, item, relatedItem); @@ -141,12 +148,12 @@ private void link(Context context, Item item, Item relatedItem) throws SQLExcept relationshipService.update(context, persistedRelationship); } - private String getValue(MessageDto message, String key) { - if (!(message instanceof OpenaireMessageDto)) { + private String getValue(NBMessage message, String key) { + if (!(message instanceof OpenaireMessage)) { return null; } - OpenaireMessageDto openaireMessage = (OpenaireMessageDto) message; + OpenaireMessage openaireMessage = (OpenaireMessage) message; if (StringUtils.equals(key, "acronym")) { return openaireMessage.getAcronym(); diff --git a/dspace-api/src/main/java/org/dspace/app/nbevent/NBEventActionService.java b/dspace-api/src/main/java/org/dspace/app/nbevent/NBEventActionService.java index 0a4de9c7fb56..e6a29173849e 100644 --- a/dspace-api/src/main/java/org/dspace/app/nbevent/NBEventActionService.java +++ b/dspace-api/src/main/java/org/dspace/app/nbevent/NBEventActionService.java @@ -10,10 +10,36 @@ import org.dspace.content.NBEvent; import org.dspace.core.Context; +/** + * Service that handle the actions that can be done related to an + * {@link NBEvent}. + * + * @author Andrea Bollini (andrea.bollini at 4science.it) + * + */ public interface NBEventActionService { + + /** + * Accept the given event. + * + * @param context the DSpace context + * @param nbevent the event to be accepted + */ public void accept(Context context, NBEvent nbevent); + /** + * Discard the given event. + * + * @param context the DSpace context + * @param nbevent the event to be discarded + */ public void discard(Context context, NBEvent nbevent); + /** + * Reject the given event. + * + * @param context the DSpace context + * @param nbevent the event to be rejected + */ public void reject(Context context, NBEvent nbevent); } diff --git a/dspace-api/src/main/java/org/dspace/app/nbevent/NBEventActionServiceImpl.java b/dspace-api/src/main/java/org/dspace/app/nbevent/NBEventActionServiceImpl.java index 2d84e8f9ba94..970858218b87 100644 --- a/dspace-api/src/main/java/org/dspace/app/nbevent/NBEventActionServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/app/nbevent/NBEventActionServiceImpl.java @@ -25,8 +25,6 @@ import org.apache.http.impl.client.HttpClients; import org.apache.logging.log4j.Logger; import org.dspace.app.nbevent.service.NBEventService; -import org.dspace.app.nbevent.service.dto.MessageDto; -import org.dspace.app.nbevent.service.dto.OpenaireMessageDto; import org.dspace.content.Item; import org.dspace.content.NBEvent; import org.dspace.content.service.ItemService; @@ -34,6 +32,12 @@ import org.dspace.services.ConfigurationService; import org.springframework.beans.factory.annotation.Autowired; +/** + * Implementation of {@link NBEventActionService}. + * + * @author Andrea Bollini (andrea.bollini at 4science.it) + * + */ public class NBEventActionServiceImpl implements NBEventActionService { private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(NBEventActionServiceImpl.class); @@ -73,7 +77,7 @@ public void accept(Context context, NBEvent nbevent) { related = itemService.find(context, UUID.fromString(nbevent.getRelated())); } topicsToActions.get(nbevent.getTopic()).applyCorrection(context, item, related, - jsonMapper.readValue(nbevent.getMessage(), getMessageDtoClass(nbevent))); + jsonMapper.readValue(nbevent.getMessage(), nbevent.getMessageDtoClass())); nbEventService.deleteEventByEventId(context, nbevent.getEventId()); makeAcknowledgement(nbevent.getEventId(), NBEvent.ACCEPTED); } catch (SQLException | JsonProcessingException e) { @@ -81,15 +85,6 @@ public void accept(Context context, NBEvent nbevent) { } } - private Class getMessageDtoClass(NBEvent modelObject) { - switch (modelObject.getSource()) { - case OPENAIRE: - return OpenaireMessageDto.class; - default: - throw new IllegalArgumentException("Unknown event's source: " + modelObject.getSource()); - } - } - @Override public void discard(Context context, NBEvent nbevent) { nbEventService.deleteEventByEventId(context, nbevent.getEventId()); diff --git a/dspace-api/src/main/java/org/dspace/app/nbevent/NBMetadataMapAction.java b/dspace-api/src/main/java/org/dspace/app/nbevent/NBMetadataMapAction.java index 7e9de849b3a2..3d7e2114cef8 100644 --- a/dspace-api/src/main/java/org/dspace/app/nbevent/NBMetadataMapAction.java +++ b/dspace-api/src/main/java/org/dspace/app/nbevent/NBMetadataMapAction.java @@ -10,14 +10,21 @@ import java.sql.SQLException; import java.util.Map; -import org.dspace.app.nbevent.service.dto.MessageDto; -import org.dspace.app.nbevent.service.dto.OpenaireMessageDto; +import org.dspace.app.nbevent.service.dto.NBMessage; +import org.dspace.app.nbevent.service.dto.OpenaireMessage; import org.dspace.authorize.AuthorizeException; import org.dspace.content.Item; import org.dspace.content.service.ItemService; import org.dspace.core.Context; import org.springframework.beans.factory.annotation.Autowired; +/** + * Implementation of {@link NBAction} that add a specific metadata on the given + * item based on the OPENAIRE message type. + * + * @author Andrea Bollini (andrea.bollini at 4science.it) + * + */ public class NBMetadataMapAction implements NBAction { public static final String DEFAULT = "default"; @@ -38,13 +45,13 @@ public void setTypes(Map types) { } @Override - public void applyCorrection(Context context, Item item, Item relatedItem, MessageDto message) { + public void applyCorrection(Context context, Item item, Item relatedItem, NBMessage message) { - if (!(message instanceof OpenaireMessageDto)) { + if (!(message instanceof OpenaireMessage)) { throw new IllegalArgumentException("Unsupported message type: " + message.getClass()); } - OpenaireMessageDto openaireMessage = (OpenaireMessageDto) message; + OpenaireMessage openaireMessage = (OpenaireMessage) message; try { String targetMetadata = types.get(openaireMessage.getType()); diff --git a/dspace-api/src/main/java/org/dspace/app/nbevent/NBSimpleMetadataAction.java b/dspace-api/src/main/java/org/dspace/app/nbevent/NBSimpleMetadataAction.java index 910f799ad886..0bff9e05ff2a 100644 --- a/dspace-api/src/main/java/org/dspace/app/nbevent/NBSimpleMetadataAction.java +++ b/dspace-api/src/main/java/org/dspace/app/nbevent/NBSimpleMetadataAction.java @@ -9,14 +9,21 @@ import java.sql.SQLException; -import org.dspace.app.nbevent.service.dto.MessageDto; -import org.dspace.app.nbevent.service.dto.OpenaireMessageDto; +import org.dspace.app.nbevent.service.dto.NBMessage; +import org.dspace.app.nbevent.service.dto.OpenaireMessage; import org.dspace.authorize.AuthorizeException; import org.dspace.content.Item; import org.dspace.content.service.ItemService; import org.dspace.core.Context; import org.springframework.beans.factory.annotation.Autowired; +/** + * Implementation of {@link NBAction} that add a simple metadata to the given + * item. + * + * @author Andrea Bollini (andrea.bollini at 4science.it) + * + */ public class NBSimpleMetadataAction implements NBAction { private String metadata; private String metadataSchema; @@ -44,10 +51,10 @@ public void setMetadata(String metadata) { } @Override - public void applyCorrection(Context context, Item item, Item relatedItem, MessageDto message) { + public void applyCorrection(Context context, Item item, Item relatedItem, NBMessage message) { try { itemService.addMetadata(context, item, metadataSchema, metadataElement, metadataQualifier, null, - ((OpenaireMessageDto) message).getAbstracts()); + ((OpenaireMessage) message).getAbstracts()); itemService.update(context, item); } catch (SQLException | AuthorizeException e) { throw new RuntimeException(e); diff --git a/dspace-api/src/main/java/org/dspace/app/nbevent/NBEventsCliScriptConfiguration.java b/dspace-api/src/main/java/org/dspace/app/nbevent/OpenaireEventsCliScriptConfiguration.java similarity index 64% rename from dspace-api/src/main/java/org/dspace/app/nbevent/NBEventsCliScriptConfiguration.java rename to dspace-api/src/main/java/org/dspace/app/nbevent/OpenaireEventsCliScriptConfiguration.java index d6671676a046..5263bc559ba0 100644 --- a/dspace-api/src/main/java/org/dspace/app/nbevent/NBEventsCliScriptConfiguration.java +++ b/dspace-api/src/main/java/org/dspace/app/nbevent/OpenaireEventsCliScriptConfiguration.java @@ -9,7 +9,15 @@ import org.apache.commons.cli.Options; -public class NBEventsCliScriptConfiguration extends NBEventsScriptConfiguration { +/** + * Extension of {@link OpenaireEventsScriptConfiguration} to run the script on + * console. + * + * @author Alessandro Martelli (alessandro.martelli at 4science.it) + * + */ +public class OpenaireEventsCliScriptConfiguration + extends OpenaireEventsScriptConfiguration { @Override public Options getOptions() { diff --git a/dspace-api/src/main/java/org/dspace/app/nbevent/NBEventsRunnable.java b/dspace-api/src/main/java/org/dspace/app/nbevent/OpenaireEventsRunnable.java similarity index 90% rename from dspace-api/src/main/java/org/dspace/app/nbevent/NBEventsRunnable.java rename to dspace-api/src/main/java/org/dspace/app/nbevent/OpenaireEventsRunnable.java index 80f7f5dabb5d..d56858402be8 100644 --- a/dspace-api/src/main/java/org/dspace/app/nbevent/NBEventsRunnable.java +++ b/dspace-api/src/main/java/org/dspace/app/nbevent/OpenaireEventsRunnable.java @@ -21,7 +21,6 @@ import org.apache.commons.lang3.StringUtils; import org.dspace.app.nbevent.service.NBEventService; import org.dspace.content.NBEvent; -import org.dspace.content.NBSourceName; import org.dspace.core.Context; import org.dspace.eperson.EPerson; import org.dspace.eperson.factory.EPersonServiceFactory; @@ -37,9 +36,9 @@ * @author Alessandro Martelli (alessandro.martelli at 4science.it) * */ -public class NBEventsRunnable extends DSpaceRunnable> { +public class OpenaireEventsRunnable extends DSpaceRunnable> { - private static final Logger LOGGER = LoggerFactory.getLogger(NBEventsRunnable.class); + private static final Logger LOGGER = LoggerFactory.getLogger(OpenaireEventsRunnable.class); protected NBEventService nbEventService; @@ -55,9 +54,9 @@ public class NBEventsRunnable extends DSpaceRunnable extends ScriptConfiguration { +/** + * Extension of {@link ScriptConfiguration} to perfom a NBEvents import from + * file. + * + * @author Alessandro Martelli (alessandro.martelli at 4science.it) + * + */ +public class OpenaireEventsScriptConfiguration extends ScriptConfiguration { @Autowired private AuthorizeService authorizeService; @@ -30,7 +37,7 @@ public Class getDspaceRunnableClass() { /** * Generic setter for the dspaceRunnableClass - * @param dspaceRunnableClass The dspaceRunnableClass to be set on this NBEventsScriptConfiguration + * @param dspaceRunnableClass The dspaceRunnableClass to be set on this OpenaireEventsScriptConfiguration */ @Override public void setDspaceRunnableClass(Class dspaceRunnableClass) { diff --git a/dspace-api/src/main/java/org/dspace/app/nbevent/RawJsonDeserializer.java b/dspace-api/src/main/java/org/dspace/app/nbevent/RawJsonDeserializer.java index edc744d586a9..475cc44a7d9f 100644 --- a/dspace-api/src/main/java/org/dspace/app/nbevent/RawJsonDeserializer.java +++ b/dspace-api/src/main/java/org/dspace/app/nbevent/RawJsonDeserializer.java @@ -16,6 +16,12 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +/** + * Extension of {@link JsonDeserializer} that convert a json to a String. + * + * @author Andrea Bollini (andrea.bollini at 4science.it) + * + */ public class RawJsonDeserializer extends JsonDeserializer { @Override diff --git a/dspace-api/src/main/java/org/dspace/app/nbevent/dao/NBEventsDao.java b/dspace-api/src/main/java/org/dspace/app/nbevent/dao/NBEventsDao.java index f426ddf6ab44..db93eb95c5f0 100644 --- a/dspace-api/src/main/java/org/dspace/app/nbevent/dao/NBEventsDao.java +++ b/dspace-api/src/main/java/org/dspace/app/nbevent/dao/NBEventsDao.java @@ -15,22 +15,48 @@ import org.dspace.core.Context; import org.dspace.eperson.EPerson; +/** + * DAO that handle processed NB Events. + * + * @author Andrea Bollini (andrea.bollini at 4science.it) + * + */ public interface NBEventsDao { + /** * Search a page of notification broker events by notification ID. * - * @param c - * @param eventId - * @param start - * @param size - * @return - * @throws SQLException + * @param context the DSpace context + * @param eventId the event id + * @param start the start index + * @param size the size to be applied + * @return the processed events + * @throws SQLException if an SQL error occurs */ - public List searchByEventId(Context c, String eventId, Integer start, Integer size) + public List searchByEventId(Context context, String eventId, Integer start, Integer size) throws SQLException; - public boolean isEventStored(Context c, String checksum) throws SQLException; + /** + * Check if an event with the given checksum is already stored. + * + * @param context the DSpace context + * @param checksum the checksum to search for + * @return true if the given checksum is related to an already + * stored event, false otherwise + * @throws SQLException if an SQL error occurs + */ + public boolean isEventStored(Context context, String checksum) throws SQLException; - boolean storeEvent(Context c, String checksum, EPerson eperson, Item item); + /** + * Store an event related to the given checksum. + * + * @param context the DSpace context + * @param checksum the checksum of the event to be store + * @param eperson the eperson who handle the event + * @param item the item related to the event + * @return true if the creation is completed with success, false + * otherwise + */ + boolean storeEvent(Context context, String checksum, EPerson eperson, Item item); } diff --git a/dspace-api/src/main/java/org/dspace/app/nbevent/dao/impl/NBEventsDaoImpl.java b/dspace-api/src/main/java/org/dspace/app/nbevent/dao/impl/NBEventsDaoImpl.java index 49894441b2d7..db3977c1099f 100644 --- a/dspace-api/src/main/java/org/dspace/app/nbevent/dao/impl/NBEventsDaoImpl.java +++ b/dspace-api/src/main/java/org/dspace/app/nbevent/dao/impl/NBEventsDaoImpl.java @@ -19,6 +19,13 @@ import org.dspace.core.Context; import org.dspace.eperson.EPerson; +/** + * Implementation of {@link NBEventsDao} that store processed events using an + * SQL DBMS. + * + * @author Andrea Bollini (andrea.bollini at 4science.it) + * + */ public class NBEventsDaoImpl extends AbstractHibernateDAO implements NBEventsDao { @Override diff --git a/dspace-api/src/main/java/org/dspace/app/nbevent/service/NBEventService.java b/dspace-api/src/main/java/org/dspace/app/nbevent/service/NBEventService.java index bb3b5bbc4978..e2c457012915 100644 --- a/dspace-api/src/main/java/org/dspace/app/nbevent/service/NBEventService.java +++ b/dspace-api/src/main/java/org/dspace/app/nbevent/service/NBEventService.java @@ -13,37 +13,135 @@ import org.dspace.app.nbevent.NBSource; import org.dspace.app.nbevent.NBTopic; import org.dspace.content.NBEvent; -import org.dspace.content.NBSourceName; import org.dspace.core.Context; +/** + * Service that handles {@link NBEvent}. + * + * @author Andrea Bollini (andrea.bollini at 4science.it) + * + */ public interface NBEventService { + /** + * Find all the event's topics. + * + * @param context the DSpace context + * @param offset the offset to apply + * @param pageSize the page size + * @return the topics list + */ public List findAllTopics(Context context, long offset, long pageSize); - public List findAllTopicsBySource(Context context, NBSourceName source, long offset, long count); - + /** + * Find all the event's topics related to the given source. + * + * @param context the DSpace context + * @param source the source to search for + * @param offset the offset to apply + * @param pageSize the page size + * @return the topics list + */ + public List findAllTopicsBySource(Context context, String source, long offset, long count); + + /** + * Count all the event's topics. + * + * @param context the DSpace context + * @return the count result + */ public long countTopics(Context context); - public long countTopicsBySource(Context context, NBSourceName source); - + /** + * Count all the event's topics related to the given source. + * + * @param context the DSpace context + * @param source the source to search for + * @return the count result + */ + public long countTopicsBySource(Context context, String source); + + /** + * Find all the events by topic. + * + * @param context the DSpace context + * @param topic the topic to search for + * @param offset the offset to apply + * @param pageSize the page size + * @param orderField the field to order for + * @param ascending true if the order should be ascending, false otherwise + * @return the events + */ public List findEventsByTopicAndPage(Context context, String topic, long offset, int pageSize, String orderField, boolean ascending); + /** + * Find all the events by topic. + * + * @param context the DSpace context + * @param topic the topic to search for + * @return the events count + */ public long countEventsByTopic(Context context, String topic); + /** + * Find an event by the given id. + * + * @param context the DSpace context + * @param id the id of the event to search for + * @return the event + */ public NBEvent findEventByEventId(Context context, String id); + /** + * Store the given event. + * + * @param context the DSpace context + * @param event the event to store + */ public void store(Context context, NBEvent event); + /** + * Delete an event by the given id. + * + * @param context the DSpace context + * @param id the id of the event to delete + */ public void deleteEventByEventId(Context context, String id); + /** + * Delete events by the given target id. + * + * @param context the DSpace context + * @param id the id of the target id + */ public void deleteEventsByTargetId(Context context, UUID targetId); + /** + * Find a specific topid by the given id. + * + * @param topicId the topic id to search for + * @return the topic + */ public NBTopic findTopicByTopicId(String topicId); - public NBSource findSource(NBSourceName source); - + /** + * Find a specific source by the given name. + * + * @param source the source name + * @return the source + */ + public NBSource findSource(String source); + + /** + * Find all the event's sources. + * + * @param context the DSpace context + * @param offset the offset to apply + * @param pageSize the page size + * @return the sources list + */ public List findAllSources(Context context, long offset, int pageSize); } diff --git a/dspace-api/src/main/java/org/dspace/app/nbevent/service/dto/MessageDto.java b/dspace-api/src/main/java/org/dspace/app/nbevent/service/dto/NBMessage.java similarity index 54% rename from dspace-api/src/main/java/org/dspace/app/nbevent/service/dto/MessageDto.java rename to dspace-api/src/main/java/org/dspace/app/nbevent/service/dto/NBMessage.java index 55c1722de8a5..4c59ab1c85b1 100644 --- a/dspace-api/src/main/java/org/dspace/app/nbevent/service/dto/MessageDto.java +++ b/dspace-api/src/main/java/org/dspace/app/nbevent/service/dto/NBMessage.java @@ -7,7 +7,15 @@ */ package org.dspace.app.nbevent.service.dto; -public interface MessageDto { +import org.dspace.content.NBEvent; + +/** + * Interface for classes that contains the details related to a {@link NBEvent}. + * + * @author Luca Giamminonni (luca.giamminonni at 4science.it) + * + */ +public interface NBMessage { } diff --git a/dspace-api/src/main/java/org/dspace/app/nbevent/service/dto/OpenaireMessageDto.java b/dspace-api/src/main/java/org/dspace/app/nbevent/service/dto/OpenaireMessage.java similarity index 94% rename from dspace-api/src/main/java/org/dspace/app/nbevent/service/dto/OpenaireMessageDto.java rename to dspace-api/src/main/java/org/dspace/app/nbevent/service/dto/OpenaireMessage.java index 5ae6b29c3abb..188139afef79 100644 --- a/dspace-api/src/main/java/org/dspace/app/nbevent/service/dto/OpenaireMessageDto.java +++ b/dspace-api/src/main/java/org/dspace/app/nbevent/service/dto/OpenaireMessage.java @@ -9,7 +9,13 @@ import com.fasterxml.jackson.annotation.JsonProperty; -public class OpenaireMessageDto implements MessageDto { +/** + * Implementation of {@link NBMessage} that model message coming from OPENAIRE. + * + * @author Luca Giamminonni (luca.giamminonni at 4science.it) + * + */ +public class OpenaireMessage implements NBMessage { @JsonProperty("pids[0].value") private String value; diff --git a/dspace-api/src/main/java/org/dspace/app/nbevent/service/impl/NBEventServiceImpl.java b/dspace-api/src/main/java/org/dspace/app/nbevent/service/impl/NBEventServiceImpl.java index 6f745d080085..84853fb5e22e 100644 --- a/dspace-api/src/main/java/org/dspace/app/nbevent/service/impl/NBEventServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/app/nbevent/service/impl/NBEventServiceImpl.java @@ -18,6 +18,7 @@ import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.json.JsonMapper; +import org.apache.commons.lang3.ArrayUtils; import org.apache.log4j.Logger; import org.apache.solr.client.solrj.SolrClient; import org.apache.solr.client.solrj.SolrQuery; @@ -37,7 +38,6 @@ import org.dspace.app.nbevent.service.NBEventService; import org.dspace.content.Item; import org.dspace.content.NBEvent; -import org.dspace.content.NBSourceName; import org.dspace.content.service.ItemService; import org.dspace.core.Context; import org.dspace.handle.service.HandleService; @@ -45,6 +45,12 @@ import org.dspace.services.factory.DSpaceServicesFactory; import org.springframework.beans.factory.annotation.Autowired; +/** + * Implementation of {@link NBEventService} that use Solr to store events. + * + * @author Andrea Bollini (andrea.bollini at 4science.it) + * + */ public class NBEventServiceImpl implements NBEventService { private static final Logger log = Logger.getLogger(NBEventServiceImpl.class); @@ -112,7 +118,7 @@ public long countTopics(Context context) { } @Override - public long countTopicsBySource(Context context, NBSourceName source) { + public long countTopicsBySource(Context context, String source) { SolrQuery solrQuery = new SolrQuery(); solrQuery.setRows(0); solrQuery.setQuery("*:*"); @@ -197,7 +203,7 @@ public List findAllTopics(Context context, long offset, long count) { } @Override - public List findAllTopicsBySource(Context context, NBSourceName source, long offset, long count) { + public List findAllTopicsBySource(Context context, String source, long offset, long count) { SolrQuery solrQuery = new SolrQuery(); solrQuery.setRows(0); solrQuery.setQuery("*:*"); @@ -239,12 +245,17 @@ public List findAllTopicsBySource(Context context, NBSourceName source, public void store(Context context, NBEvent dto) { UpdateRequest updateRequest = new UpdateRequest(); String topic = dto.getTopic(); + + if (!ArrayUtils.contains(getSupportedSources(), dto.getSource())) { + throw new IllegalArgumentException("The source of the given event is not supported: " + dto.getSource()); + } + if (topic != null) { String checksum = dto.getEventId(); try { if (!nbEventsDao.isEventStored(context, checksum)) { SolrInputDocument doc = new SolrInputDocument(); - doc.addField(SOURCE, dto.getSource().name()); + doc.addField(SOURCE, dto.getSource()); doc.addField(EVENT_ID, checksum); doc.addField(ORIGINAL_ID, dto.getOriginalId()); doc.addField(TITLE, dto.getTitle()); @@ -291,7 +302,7 @@ public NBEvent findEventByEventId(Context context, String eventId) { private NBEvent getNBEventFromSOLR(SolrDocument doc) { NBEvent item = new NBEvent(); - item.setSource(NBSourceName.valueOf((String) doc.get(SOURCE))); + item.setSource((String) doc.get(SOURCE)); item.setEventId((String) doc.get(EVENT_ID)); item.setLastUpdate((Date) doc.get(LAST_UPDATE)); item.setMessage((String) doc.get(MESSAGE)); @@ -372,7 +383,7 @@ private String getHandleFromOriginalId(String originalId) { } @Override - public NBSource findSource(NBSourceName sourceName) { + public NBSource findSource(String sourceName) { SolrQuery solrQuery = new SolrQuery(); solrQuery.setRows(0); solrQuery.setQuery(SOURCE + ":" + sourceName); @@ -385,7 +396,7 @@ public NBSource findSource(NBSourceName sourceName) { response = getSolr().query(solrQuery); FacetField facetField = response.getFacetField(SOURCE); for (Count c : facetField.getValues()) { - if (c.getName().equalsIgnoreCase(sourceName.name())) { + if (c.getName().equalsIgnoreCase(sourceName)) { NBSource source = new NBSource(); source.setName(c.getName()); source.setTotalEvents(c.getCount()); @@ -398,18 +409,22 @@ public NBSource findSource(NBSourceName sourceName) { } NBSource source = new NBSource(); - source.setName(sourceName.name()); + source.setName(sourceName); source.setTotalEvents(0L); return source; } @Override public List findAllSources(Context context, long offset, int pageSize) { - return Arrays.stream(NBSourceName.values()).sorted() + return Arrays.stream(getSupportedSources()).sorted() .map((sourceName) -> findSource(sourceName)) .skip(offset) .limit(pageSize) .collect(Collectors.toList()); } + private String[] getSupportedSources() { + return configurationService.getArrayProperty("nbevent.sources", new String[] { NBEvent.OPENAIRE_SOURCE }); + } + } diff --git a/dspace-api/src/main/java/org/dspace/content/NBEvent.java b/dspace-api/src/main/java/org/dspace/content/NBEvent.java index 950fc37d01a7..e99fbaefa1b6 100644 --- a/dspace-api/src/main/java/org/dspace/content/NBEvent.java +++ b/dspace-api/src/main/java/org/dspace/content/NBEvent.java @@ -14,6 +14,8 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import org.dspace.app.nbevent.RawJsonDeserializer; +import org.dspace.app.nbevent.service.dto.NBMessage; +import org.dspace.app.nbevent.service.dto.OpenaireMessage; /** * This class represent the notification broker data as loaded in our solr @@ -27,7 +29,9 @@ public class NBEvent { public static final String REJECTED = "rejected"; public static final String DISCARDED = "discarded"; - private NBSourceName source; + public static final String OPENAIRE_SOURCE = "openaire"; + + private String source; private String eventId; @@ -53,7 +57,7 @@ public class NBEvent { public NBEvent() { } - public NBEvent(NBSourceName source, String originalId, String target, String title, + public NBEvent(String source, String originalId, String target, String title, String topic, double trust, String message, Date lastUpdate) { super(); this.source = source; @@ -159,11 +163,11 @@ public String getStatus() { return status; } - public NBSourceName getSource() { - return source != null ? source : NBSourceName.OPENAIRE; + public String getSource() { + return source != null ? source : OPENAIRE_SOURCE; } - public void setSource(NBSourceName source) { + public void setSource(String source) { this.source = source; } @@ -188,4 +192,13 @@ private void computedEventId() throws NoSuchAlgorithmException, UnsupportedEncod } + public Class getMessageDtoClass() { + switch (getSource()) { + case OPENAIRE_SOURCE: + return OpenaireMessage.class; + default: + throw new IllegalArgumentException("Unknown event's source: " + getSource()); + } + } + } diff --git a/dspace-api/src/main/java/org/dspace/content/NBSourceName.java b/dspace-api/src/main/java/org/dspace/content/NBSourceName.java deleted file mode 100644 index 705cc2605834..000000000000 --- a/dspace-api/src/main/java/org/dspace/content/NBSourceName.java +++ /dev/null @@ -1,13 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.content; - -public enum NBSourceName { - - OPENAIRE; -} diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.0_2020.10.16__nbevent_processed.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.3_2022.02.17__nbevent_processed.sql similarity index 100% rename from dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.0_2020.10.16__nbevent_processed.sql rename to dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.3_2022.02.17__nbevent_processed.sql diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2020.10.16__nbevent_processed.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.3_2022.02.17__nbevent_processed.sql similarity index 100% rename from dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2020.10.16__nbevent_processed.sql rename to dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.3_2022.02.17__nbevent_processed.sql diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.0_2020.10.16__nbevent_processed.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.3_2022.02.17__nbevent_processed.sql similarity index 100% rename from dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.0_2020.10.16__nbevent_processed.sql rename to dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.3_2022.02.17__nbevent_processed.sql diff --git a/dspace-api/src/test/java/org/dspace/builder/NBEventBuilder.java b/dspace-api/src/test/java/org/dspace/builder/NBEventBuilder.java index 57e5c2a2fe76..8bf1b206da54 100644 --- a/dspace-api/src/test/java/org/dspace/builder/NBEventBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/NBEventBuilder.java @@ -13,7 +13,6 @@ import org.dspace.content.Collection; import org.dspace.content.Item; import org.dspace.content.NBEvent; -import org.dspace.content.NBSourceName; import org.dspace.core.Context; /** @@ -25,7 +24,7 @@ public class NBEventBuilder extends AbstractBuilder { private Item item; private NBEvent target; - private NBSourceName source = NBSourceName.OPENAIRE; + private String source = NBEvent.OPENAIRE_SOURCE; private String title; private String topic; private String message; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/NBEventConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/NBEventConverter.java index 82230e8eeeb9..21bbdfff297d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/NBEventConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/NBEventConverter.java @@ -13,8 +13,8 @@ import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.json.JsonMapper; -import org.dspace.app.nbevent.service.dto.MessageDto; -import org.dspace.app.nbevent.service.dto.OpenaireMessageDto; +import org.dspace.app.nbevent.service.dto.NBMessage; +import org.dspace.app.nbevent.service.dto.OpenaireMessage; import org.dspace.app.rest.model.NBEventMessageRest; import org.dspace.app.rest.model.NBEventRest; import org.dspace.app.rest.model.OpenaireNBEventMessageRest; @@ -22,6 +22,13 @@ import org.dspace.content.NBEvent; import org.springframework.stereotype.Component; +/** + * Implementation of {@link DSpaceConverter} that converts {@link NBEvent} to + * {@link NBEventRest}. + * + * @author Andrea Bollini (andrea.bollini at 4science.it) + * + */ @Component public class NBEventConverter implements DSpaceConverter { @@ -39,7 +46,7 @@ public NBEventRest convert(NBEvent modelObject, Projection projection) { rest.setId(modelObject.getEventId()); try { rest.setMessage(convertMessage(jsonMapper.readValue(modelObject.getMessage(), - getMessageDtoClass(modelObject)))); + modelObject.getMessageDtoClass()))); } catch (JsonProcessingException e) { throw new RuntimeException(e); } @@ -54,18 +61,9 @@ public NBEventRest convert(NBEvent modelObject, Projection projection) { return rest; } - private Class getMessageDtoClass(NBEvent modelObject) { - switch (modelObject.getSource()) { - case OPENAIRE: - return OpenaireMessageDto.class; - default: - throw new IllegalArgumentException("Unknown event's source: " + modelObject.getSource()); - } - } - - private NBEventMessageRest convertMessage(MessageDto dto) { - if (dto instanceof OpenaireMessageDto) { - OpenaireMessageDto openaireDto = (OpenaireMessageDto) dto; + private NBEventMessageRest convertMessage(NBMessage dto) { + if (dto instanceof OpenaireMessage) { + OpenaireMessage openaireDto = (OpenaireMessage) dto; OpenaireNBEventMessageRest message = new OpenaireNBEventMessageRest(); message.setAbstractValue(openaireDto.getAbstracts()); message.setOpenaireId(openaireDto.getOpenaireId()); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/NBSourceConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/NBSourceConverter.java index 7524cc797559..a1b496df048d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/NBSourceConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/NBSourceConverter.java @@ -12,6 +12,13 @@ import org.dspace.app.rest.projection.Projection; import org.springframework.stereotype.Component; +/** + * Implementation of {@link DSpaceConverter} that converts {@link NBSource} to + * {@link NBSourceRest}. + * + * @author Luca Giamminonni (luca.giamminonni at 4science.it) + * + */ @Component public class NBSourceConverter implements DSpaceConverter { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/NBTopicConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/NBTopicConverter.java index 8a5a284fe1f6..f9ab34da89b6 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/NBTopicConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/NBTopicConverter.java @@ -12,6 +12,13 @@ import org.dspace.app.rest.projection.Projection; import org.springframework.stereotype.Component; +/** + * Implementation of {@link DSpaceConverter} that converts {@link NBTopic} to + * {@link NBTopicRest}. + * + * @author Andrea Bollini (andrea.bollini at 4science.it) + * + */ @Component public class NBTopicConverter implements DSpaceConverter { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/NBEventMessageRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/NBEventMessageRest.java index 7c2e03ac3428..df6187651c2a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/NBEventMessageRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/NBEventMessageRest.java @@ -7,6 +7,12 @@ */ package org.dspace.app.rest.model; +/** + * Interface for classes that model a message with the details of a NB event. + * + * @author Luca Giamminonni (luca.giamminonni at 4science.it) + * + */ public interface NBEventMessageRest { } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/NBEventRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/NBEventRest.java index 60dbce6d9b8e..0ccc1a55da3b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/NBEventRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/NBEventRest.java @@ -11,6 +11,12 @@ import org.dspace.app.rest.RestResourceController; +/** + * NB event Rest object. + * + * @author Andrea Bollini (andrea.bollini at 4science.it) + * + */ @LinksRest( links = { @LinkRest(name = "topic", method = "getTopic"), diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/OpenaireNBEventMessageRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/OpenaireNBEventMessageRest.java index 84021abb6edd..ca6ee5d06d42 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/OpenaireNBEventMessageRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/OpenaireNBEventMessageRest.java @@ -9,6 +9,12 @@ import com.fasterxml.jackson.annotation.JsonProperty; +/** + * Implementation of {@link NBEventMessageRest} related to OPENAIRE events. + * + * @author Luca Giamminonni (luca.giamminonni at 4science.it) + * + */ public class OpenaireNBEventMessageRest implements NBEventMessageRest { // pids private String type; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/NBEventResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/NBEventResource.java index bd3c266f1eb6..b052d3d4da07 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/NBEventResource.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/NBEventResource.java @@ -11,6 +11,12 @@ import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource; import org.dspace.app.rest.utils.Utils; +/** + * NB event Rest resource. + * + * @author Andrea Bollini (andrea.bollini at 4science.it) + * + */ @RelNameDSpaceResource(NBEventRest.NAME) public class NBEventResource extends DSpaceResource { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/NBSourceResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/NBSourceResource.java index 899b684199c1..55db5d6343d0 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/NBSourceResource.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/NBSourceResource.java @@ -11,6 +11,12 @@ import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource; import org.dspace.app.rest.utils.Utils; +/** + * NB source Rest resource. + * + * @author Luca Giamminonni (luca.giamminonni at 4Science) + * + */ @RelNameDSpaceResource(NBSourceRest.NAME) public class NBSourceResource extends DSpaceResource { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/NBTopicResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/NBTopicResource.java index a2fed4ffc6ec..78af04a7645b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/NBTopicResource.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/NBTopicResource.java @@ -11,6 +11,12 @@ import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource; import org.dspace.app.rest.utils.Utils; +/** + * NB topic Rest resource. + * + * @author Andrea Bollini (andrea.bollini at 4science.it) + * + */ @RelNameDSpaceResource(NBTopicRest.NAME) public class NBTopicResource extends DSpaceResource { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NBEventRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NBEventRestRepository.java index b00688a6ea95..d1bc46903625 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NBEventRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NBEventRestRepository.java @@ -35,6 +35,12 @@ import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Component; +/** + * Rest repository that handle NB events. + * + * @author Andrea Bollini (andrea.bollini at 4science.it) + * + */ @Component(NBEventRest.CATEGORY + "." + NBEventRest.NAME) public class NBEventRestRepository extends DSpaceRestRepository { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NBSourceRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NBSourceRestRepository.java index 95dfa6b61adc..ceeef3671abd 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NBSourceRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NBSourceRestRepository.java @@ -12,7 +12,6 @@ import org.dspace.app.nbevent.NBSource; import org.dspace.app.nbevent.service.NBEventService; import org.dspace.app.rest.model.NBSourceRest; -import org.dspace.content.NBSourceName; import org.dspace.core.Context; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; @@ -20,6 +19,12 @@ import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Component; +/** + * Rest repository that handle NB soufces. + * + * @author Luca Giamminonni (luca.giamminonni at 4science.it) + * + */ @Component(NBSourceRest.CATEGORY + "." + NBSourceRest.NAME) public class NBSourceRestRepository extends DSpaceRestRepository { @@ -29,19 +34,19 @@ public class NBSourceRestRepository extends DSpaceRestRepository findAll(Context context, Pageable pageable) { - List nbTopics = nbEventService.findAllSources(context, pageable.getOffset(), pageable.getPageSize()); + List nbSources = nbEventService.findAllSources(context, pageable.getOffset(), pageable.getPageSize()); long count = nbEventService.countTopics(context); - if (nbTopics == null) { + if (nbSources == null) { return null; } - return converter.toRestPage(nbTopics, pageable, count, utils.obtainProjection()); + return converter.toRestPage(nbSources, pageable, count, utils.obtainProjection()); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NBTopicRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NBTopicRestRepository.java index c738da7bd62f..479a606e0017 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NBTopicRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NBTopicRestRepository.java @@ -13,7 +13,6 @@ import org.dspace.app.nbevent.service.NBEventService; import org.dspace.app.rest.SearchRestMethod; import org.dspace.app.rest.model.NBTopicRest; -import org.dspace.content.NBSourceName; import org.dspace.core.Context; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; @@ -21,6 +20,12 @@ import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Component; +/** + * Rest repository that handle NB topics. + * + * @author Andrea Bollini (andrea.bollini at 4science.it) + * + */ @Component(NBTopicRest.CATEGORY + "." + NBTopicRest.NAME) public class NBTopicRestRepository extends DSpaceRestRepository { @@ -51,9 +56,9 @@ public Page findAll(Context context, Pageable pageable) { @SearchRestMethod(name = "bySource") @PreAuthorize("hasAuthority('ADMIN')") public Page findBySource(Context context, String source, Pageable pageable) { - List nbTopics = nbEventService.findAllTopicsBySource(context, NBSourceName.valueOf(source), + List nbTopics = nbEventService.findAllTopicsBySource(context, String.valueOf(source), pageable.getOffset(), pageable.getPageSize()); - long count = nbEventService.countTopicsBySource(context, NBSourceName.valueOf(source)); + long count = nbEventService.countTopicsBySource(context, String.valueOf(source)); if (nbTopics == null) { return null; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/NBEventStatusReplaceOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/NBEventStatusReplaceOperation.java index bd690ee683ec..55bfe3d2f13c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/NBEventStatusReplaceOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/NBEventStatusReplaceOperation.java @@ -18,6 +18,12 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; +/** + * Replace operation related to the {@link NBEvent} status. + * + * @author Andrea Bollini (andrea.bollini at 4science.it) + * + */ @Component public class NBEventStatusReplaceOperation extends PatchOperation { @Autowired diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/NBEventRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/NBEventRestRepositoryIT.java index ef9abfe9782e..a9f5d294279e 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/NBEventRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/NBEventRestRepositoryIT.java @@ -42,6 +42,12 @@ import org.hamcrest.Matchers; import org.junit.Test; +/** + * Integration tests for {@link NBEventRestRepository}. + * + * @author Andrea Bollini (andrea.bollini at 4science.it) + * + */ public class NBEventRestRepositoryIT extends AbstractControllerIntegrationTest { @Test diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/NBTopicRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/NBTopicRestRepositoryIT.java index 2d0095bd8fd1..dea109219a91 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/NBTopicRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/NBTopicRestRepositoryIT.java @@ -14,6 +14,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import org.dspace.app.rest.matcher.NBTopicMatcher; +import org.dspace.app.rest.repository.NBTopicRestRepository; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.builder.CollectionBuilder; import org.dspace.builder.CommunityBuilder; @@ -23,6 +24,12 @@ import org.hamcrest.Matchers; import org.junit.Test; +/** + * Integration tests for {@link NBTopicRestRepository}. + * + * @author Andrea Bollini (andrea.bollini at 4science.it) + * + */ public class NBTopicRestRepositoryIT extends AbstractControllerIntegrationTest { @Test diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/NBEventMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/NBEventMatcher.java index f8dca7e466b0..a09d115359d3 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/NBEventMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/NBEventMatcher.java @@ -18,12 +18,18 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.json.JsonMapper; import org.apache.commons.lang3.StringUtils; -import org.dspace.app.nbevent.service.dto.OpenaireMessageDto; +import org.dspace.app.nbevent.service.dto.OpenaireMessage; import org.dspace.content.NBEvent; import org.hamcrest.Matcher; import org.hamcrest.Matchers; import org.hamcrest.core.IsAnything; +/** + * Matcher related to {@link NBEventResource}. + * + * @author Andrea Bollini (andrea.bollini at 4science.it) + * + */ public class NBEventMatcher { private NBEventMatcher() { @@ -50,7 +56,7 @@ public static Matcher matchNBEventEntry(NBEvent event) { hasJsonPath("$.status", Matchers.equalToIgnoringCase(event.getStatus())), hasJsonPath("$.message", matchMessage(event.getTopic(), jsonMapper.readValue(event.getMessage(), - OpenaireMessageDto.class))), + OpenaireMessage.class))), hasJsonPath("$._links.target.href", Matchers.endsWith(event.getEventId() + "/target")), hasJsonPath("$._links.related.href", Matchers.endsWith(event.getEventId() + "/related")), hasJsonPath("$._links.topic.href", Matchers.endsWith(event.getEventId() + "/topic")), @@ -60,7 +66,7 @@ public static Matcher matchNBEventEntry(NBEvent event) { } } - private static Matcher matchMessage(String topic, OpenaireMessageDto message) { + private static Matcher matchMessage(String topic, OpenaireMessage message) { if (StringUtils.endsWith(topic, "/ABSTRACT")) { return allOf(hasJsonPath("$.abstract", is(message.getAbstracts()))); } else if (StringUtils.endsWith(topic, "/PID")) { diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/NBSourceMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/NBSourceMatcher.java new file mode 100644 index 000000000000..35031202f0a7 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/NBSourceMatcher.java @@ -0,0 +1,43 @@ +/** + * 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.matcher; + +import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.is; + +import org.dspace.app.rest.model.hateoas.NBSourceResource; +import org.hamcrest.Matcher; + +/** + * Matcher related to {@link NBSourceResource}. + * + * @author Luca Giamminonni (luca.giamminonni at 4science.it) + * + */ +public class NBSourceMatcher { + + private NBSourceMatcher() { } + + public static Matcher matchNBSourceEntry(String key, int totalEvents) { + return allOf( + hasJsonPath("$.type", is("nbsource")), + hasJsonPath("$.id", is(key)), + hasJsonPath("$.totalEvents", is(totalEvents)) + ); + } + + + public static Matcher matchNBSourceEntry(String key) { + return allOf( + hasJsonPath("$.type", is("nbsource")), + hasJsonPath("$.id", is(key)) + ); + } + +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/NBTopicMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/NBTopicMatcher.java index 644feeeec48d..7ad6972b1e30 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/NBTopicMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/NBTopicMatcher.java @@ -11,8 +11,15 @@ import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.is; +import org.dspace.app.rest.model.hateoas.NBTopicResource; import org.hamcrest.Matcher; +/** + * Matcher related to {@link NBTopicResource}. + * + * @author Andrea Bollini (andrea.bollini at 4science.it) + * + */ public class NBTopicMatcher { private NBTopicMatcher() { } diff --git a/dspace/config/spring/api/scripts.xml b/dspace/config/spring/api/scripts.xml index 61b06c2aa9ee..184950a137e5 100644 --- a/dspace/config/spring/api/scripts.xml +++ b/dspace/config/spring/api/scripts.xml @@ -4,9 +4,9 @@ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"> - + - + diff --git a/dspace/config/spring/rest/scripts.xml b/dspace/config/spring/rest/scripts.xml index 518d81009b4c..9dee8330893e 100644 --- a/dspace/config/spring/rest/scripts.xml +++ b/dspace/config/spring/rest/scripts.xml @@ -8,9 +8,9 @@ - + - + From d856cf31f29e4ad4f42ed97a257f87e89053561d Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Thu, 17 Feb 2022 13:22:16 +0100 Subject: [PATCH 005/510] [CST-5246] Added integration tests for NBSourceRestRepository --- .../app/nbevent/NBEventActionServiceImpl.java | 6 +- .../NBEventsDeleteCascadeConsumer.java | 3 +- .../app/nbevent/service/NBEventService.java | 36 ++-- .../service/impl/NBEventServiceImpl.java | 59 +++--- .../org/dspace/builder/NBEventBuilder.java | 8 +- .../app/rest/NBEventRestController.java | 4 +- .../NBEventRelatedLinkRepository.java | 2 +- .../repository/NBEventRestRepository.java | 13 +- .../NBEventTargetLinkRepository.java | 2 +- .../NBEventTopicLinkRepository.java | 2 +- .../repository/NBSourceRestRepository.java | 10 +- .../repository/NBTopicRestRepository.java | 8 +- .../app/rest/NBSourceRestRepositoryIT.java | 200 ++++++++++++++++++ 13 files changed, 278 insertions(+), 75 deletions(-) create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/NBSourceRestRepositoryIT.java diff --git a/dspace-api/src/main/java/org/dspace/app/nbevent/NBEventActionServiceImpl.java b/dspace-api/src/main/java/org/dspace/app/nbevent/NBEventActionServiceImpl.java index 970858218b87..ac9fdd5c3fe7 100644 --- a/dspace-api/src/main/java/org/dspace/app/nbevent/NBEventActionServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/app/nbevent/NBEventActionServiceImpl.java @@ -78,7 +78,7 @@ public void accept(Context context, NBEvent nbevent) { } topicsToActions.get(nbevent.getTopic()).applyCorrection(context, item, related, jsonMapper.readValue(nbevent.getMessage(), nbevent.getMessageDtoClass())); - nbEventService.deleteEventByEventId(context, nbevent.getEventId()); + nbEventService.deleteEventByEventId(nbevent.getEventId()); makeAcknowledgement(nbevent.getEventId(), NBEvent.ACCEPTED); } catch (SQLException | JsonProcessingException e) { throw new RuntimeException(e); @@ -87,13 +87,13 @@ public void accept(Context context, NBEvent nbevent) { @Override public void discard(Context context, NBEvent nbevent) { - nbEventService.deleteEventByEventId(context, nbevent.getEventId()); + nbEventService.deleteEventByEventId(nbevent.getEventId()); makeAcknowledgement(nbevent.getEventId(), NBEvent.DISCARDED); } @Override public void reject(Context context, NBEvent nbevent) { - nbEventService.deleteEventByEventId(context, nbevent.getEventId()); + nbEventService.deleteEventByEventId(nbevent.getEventId()); makeAcknowledgement(nbevent.getEventId(), NBEvent.REJECTED); } diff --git a/dspace-api/src/main/java/org/dspace/app/nbevent/NBEventsDeleteCascadeConsumer.java b/dspace-api/src/main/java/org/dspace/app/nbevent/NBEventsDeleteCascadeConsumer.java index 0eba13e90bf7..8297599bc5c8 100644 --- a/dspace-api/src/main/java/org/dspace/app/nbevent/NBEventsDeleteCascadeConsumer.java +++ b/dspace-api/src/main/java/org/dspace/app/nbevent/NBEventsDeleteCascadeConsumer.java @@ -26,7 +26,6 @@ public class NBEventsDeleteCascadeConsumer implements Consumer { private NBEventService nbEventService; @Override - @SuppressWarnings("unchecked") public void initialize() throws Exception { nbEventService = new DSpace().getSingletonService(NBEventService.class); } @@ -40,7 +39,7 @@ public void finish(Context context) throws Exception { public void consume(Context context, Event event) throws Exception { if (event.getEventType() == Event.DELETE) { if (event.getSubjectType() == Constants.ITEM && event.getSubjectID() != null) { - nbEventService.deleteEventsByTargetId(context, event.getSubjectID()); + nbEventService.deleteEventsByTargetId(event.getSubjectID()); } } } diff --git a/dspace-api/src/main/java/org/dspace/app/nbevent/service/NBEventService.java b/dspace-api/src/main/java/org/dspace/app/nbevent/service/NBEventService.java index e2c457012915..599806f42541 100644 --- a/dspace-api/src/main/java/org/dspace/app/nbevent/service/NBEventService.java +++ b/dspace-api/src/main/java/org/dspace/app/nbevent/service/NBEventService.java @@ -31,7 +31,7 @@ public interface NBEventService { * @param pageSize the page size * @return the topics list */ - public List findAllTopics(Context context, long offset, long pageSize); + public List findAllTopics(long offset, long pageSize); /** * Find all the event's topics related to the given source. @@ -42,7 +42,7 @@ public interface NBEventService { * @param pageSize the page size * @return the topics list */ - public List findAllTopicsBySource(Context context, String source, long offset, long count); + public List findAllTopicsBySource(String source, long offset, long count); /** * Count all the event's topics. @@ -50,7 +50,7 @@ public interface NBEventService { * @param context the DSpace context * @return the count result */ - public long countTopics(Context context); + public long countTopics(); /** * Count all the event's topics related to the given source. @@ -59,12 +59,11 @@ public interface NBEventService { * @param source the source to search for * @return the count result */ - public long countTopicsBySource(Context context, String source); + public long countTopicsBySource(String source); /** * Find all the events by topic. * - * @param context the DSpace context * @param topic the topic to search for * @param offset the offset to apply * @param pageSize the page size @@ -72,27 +71,24 @@ public interface NBEventService { * @param ascending true if the order should be ascending, false otherwise * @return the events */ - public List findEventsByTopicAndPage(Context context, String topic, - long offset, int pageSize, - String orderField, boolean ascending); + public List findEventsByTopicAndPage(String topic, long offset, int pageSize, + String orderField, boolean ascending); /** * Find all the events by topic. * - * @param context the DSpace context * @param topic the topic to search for * @return the events count */ - public long countEventsByTopic(Context context, String topic); + public long countEventsByTopic(String topic); /** * Find an event by the given id. * - * @param context the DSpace context * @param id the id of the event to search for * @return the event */ - public NBEvent findEventByEventId(Context context, String id); + public NBEvent findEventByEventId(String id); /** * Store the given event. @@ -105,18 +101,16 @@ public List findEventsByTopicAndPage(Context context, String topic, /** * Delete an event by the given id. * - * @param context the DSpace context * @param id the id of the event to delete */ - public void deleteEventByEventId(Context context, String id); + public void deleteEventByEventId(String id); /** * Delete events by the given target id. * - * @param context the DSpace context * @param id the id of the target id */ - public void deleteEventsByTargetId(Context context, UUID targetId); + public void deleteEventsByTargetId(UUID targetId); /** * Find a specific topid by the given id. @@ -137,11 +131,17 @@ public List findEventsByTopicAndPage(Context context, String topic, /** * Find all the event's sources. * - * @param context the DSpace context * @param offset the offset to apply * @param pageSize the page size * @return the sources list */ - public List findAllSources(Context context, long offset, int pageSize); + public List findAllSources(long offset, int pageSize); + + /** + * Count all the event's sources. + * + * @return the count result + */ + public long countSources(); } diff --git a/dspace-api/src/main/java/org/dspace/app/nbevent/service/impl/NBEventServiceImpl.java b/dspace-api/src/main/java/org/dspace/app/nbevent/service/impl/NBEventServiceImpl.java index 84853fb5e22e..47c98e1467b9 100644 --- a/dspace-api/src/main/java/org/dspace/app/nbevent/service/impl/NBEventServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/app/nbevent/service/impl/NBEventServiceImpl.java @@ -7,6 +7,8 @@ */ package org.dspace.app.nbevent.service.impl; +import static java.util.Comparator.comparing; + import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; @@ -100,7 +102,7 @@ protected SolrClient getSolr() { } @Override - public long countTopics(Context context) { + public long countTopics() { SolrQuery solrQuery = new SolrQuery(); solrQuery.setRows(0); solrQuery.setQuery("*:*"); @@ -118,7 +120,7 @@ public long countTopics(Context context) { } @Override - public long countTopicsBySource(Context context, String source) { + public long countTopicsBySource(String source) { SolrQuery solrQuery = new SolrQuery(); solrQuery.setRows(0); solrQuery.setQuery("*:*"); @@ -137,7 +139,7 @@ public long countTopicsBySource(Context context, String source) { } @Override - public void deleteEventByEventId(Context context, String id) { + public void deleteEventByEventId(String id) { try { getSolr().deleteById(id); getSolr().commit(); @@ -147,7 +149,7 @@ public void deleteEventByEventId(Context context, String id) { } @Override - public void deleteEventsByTargetId(Context context, UUID targetId) { + public void deleteEventsByTargetId(UUID targetId) { try { getSolr().deleteByQuery(RESOURCE_UUID + ":" + targetId.toString()); getSolr().commit(); @@ -185,25 +187,13 @@ public NBTopic findTopicByTopicId(String topicId) { return null; } - /** - * Method to get all topics and the number of entries for each topic - * - * @param context DSpace context - * @param offset number of results to skip - * @param count number of result to fetch - * @return list of topics with number of events - * @throws IOException - * @throws SolrServerException - * @throws InvalidEnumeratedDataValueException - * - */ @Override - public List findAllTopics(Context context, long offset, long count) { - return findAllTopicsBySource(context, null, offset, count); + public List findAllTopics(long offset, long count) { + return findAllTopicsBySource(null, offset, count); } @Override - public List findAllTopicsBySource(Context context, String source, long offset, long count) { + public List findAllTopicsBySource(String source, long offset, long count) { SolrQuery solrQuery = new SolrQuery(); solrQuery.setRows(0); solrQuery.setQuery("*:*"); @@ -282,7 +272,7 @@ public void store(Context context, NBEvent dto) { } @Override - public NBEvent findEventByEventId(Context context, String eventId) { + public NBEvent findEventByEventId(String eventId) { SolrQuery param = new SolrQuery(EVENT_ID + ":" + eventId); QueryResponse response; try { @@ -316,9 +306,8 @@ private NBEvent getNBEventFromSOLR(SolrDocument doc) { } @Override - public List findEventsByTopicAndPage(Context context, String topic, - long offset, int pageSize, - String orderField, boolean ascending) { + public List findEventsByTopicAndPage(String topic, long offset, + int pageSize, String orderField, boolean ascending) { SolrQuery solrQuery = new SolrQuery(); solrQuery.setStart(((Long) offset).intValue()); solrQuery.setRows(pageSize); @@ -343,7 +332,7 @@ public List findEventsByTopicAndPage(Context context, String topic, } @Override - public long countEventsByTopic(Context context, String topic) { + public long countEventsByTopic(String topic) { SolrQuery solrQuery = new SolrQuery(); solrQuery.setRows(0); solrQuery.setQuery(TOPIC + ":" + topic.replace("!", "/")); @@ -384,13 +373,18 @@ private String getHandleFromOriginalId(String originalId) { @Override public NBSource findSource(String sourceName) { - SolrQuery solrQuery = new SolrQuery(); + + if (!ArrayUtils.contains(getSupportedSources(), sourceName)) { + return null; + } + + SolrQuery solrQuery = new SolrQuery("*:*"); solrQuery.setRows(0); - solrQuery.setQuery(SOURCE + ":" + sourceName); + solrQuery.addFilterQuery(SOURCE + ":" + sourceName); solrQuery.setFacet(true); - // we would like to get eventually topic that has no longer active nb events solrQuery.setFacetMinCount(0); solrQuery.addFacetField(SOURCE); + QueryResponse response; try { response = getSolr().query(solrQuery); @@ -411,18 +405,25 @@ public NBSource findSource(String sourceName) { NBSource source = new NBSource(); source.setName(sourceName); source.setTotalEvents(0L); + return source; } @Override - public List findAllSources(Context context, long offset, int pageSize) { - return Arrays.stream(getSupportedSources()).sorted() + public List findAllSources(long offset, int pageSize) { + return Arrays.stream(getSupportedSources()) .map((sourceName) -> findSource(sourceName)) + .sorted(comparing(NBSource::getTotalEvents).reversed()) .skip(offset) .limit(pageSize) .collect(Collectors.toList()); } + @Override + public long countSources() { + return getSupportedSources().length; + } + private String[] getSupportedSources() { return configurationService.getArrayProperty("nbevent.sources", new String[] { NBEvent.OPENAIRE_SOURCE }); } diff --git a/dspace-api/src/test/java/org/dspace/builder/NBEventBuilder.java b/dspace-api/src/test/java/org/dspace/builder/NBEventBuilder.java index 8bf1b206da54..3ad22738c371 100644 --- a/dspace-api/src/test/java/org/dspace/builder/NBEventBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/NBEventBuilder.java @@ -71,6 +71,10 @@ public NBEventBuilder withTopic(final String topic) { this.topic = topic; return this; } + public NBEventBuilder withSource(final String source) { + this.source = source; + return this; + } public NBEventBuilder withTitle(final String title) { this.title = title; return this; @@ -108,7 +112,7 @@ public NBEvent build() { @Override public void cleanup() throws Exception { - nbEventService.deleteEventByEventId(context, target.getEventId()); + nbEventService.deleteEventByEventId(target.getEventId()); } @Override @@ -118,7 +122,7 @@ protected NBEventService getService() { @Override public void delete(Context c, NBEvent dso) throws Exception { - nbEventService.deleteEventByEventId(context, target.getEventId()); + nbEventService.deleteEventByEventId(target.getEventId()); // nbEventService.deleteTarget(dso); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/NBEventRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/NBEventRestController.java index 2411f4743d02..1245c3854e7f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/NBEventRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/NBEventRestController.java @@ -80,7 +80,7 @@ public ResponseEntity> postRelatedItem(@PathVariable(name @RequestParam(required = true, name = "item") UUID relatedItemUUID) throws SQLException, AuthorizeException { Context context = ContextUtil.obtainContext(request); - NBEvent nbevent = nbEventService.findEventByEventId(context, nbeventId); + NBEvent nbevent = nbEventService.findEventByEventId(nbeventId); if (nbevent == null) { throw new ResourceNotFoundException("No such nb event: " + nbeventId); } @@ -120,7 +120,7 @@ public ResponseEntity> deleteAdminGroup(@PathVariable(nam HttpServletResponse response, HttpServletRequest request) throws SQLException, AuthorizeException, IOException { Context context = ContextUtil.obtainContext(request); - NBEvent nbevent = nbEventService.findEventByEventId(context, nbeventId); + NBEvent nbevent = nbEventService.findEventByEventId(nbeventId); if (nbevent == null) { throw new ResourceNotFoundException("No such nb event: " + nbeventId); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NBEventRelatedLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NBEventRelatedLinkRepository.java index 3ec4660c4abf..901d600c1083 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NBEventRelatedLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NBEventRelatedLinkRepository.java @@ -55,7 +55,7 @@ public class NBEventRelatedLinkRepository extends AbstractDSpaceRestRepository i public ItemRest getRelated(@Nullable HttpServletRequest request, String id, @Nullable Pageable pageable, Projection projection) { Context context = obtainContext(); - NBEvent nbEvent = nbEventService.findEventByEventId(context, id); + NBEvent nbEvent = nbEventService.findEventByEventId(id); if (nbEvent == null) { throw new ResourceNotFoundException("No nb event with ID: " + id); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NBEventRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NBEventRestRepository.java index d1bc46903625..f173ebebc92f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NBEventRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NBEventRestRepository.java @@ -66,7 +66,7 @@ public class NBEventRestRepository extends DSpaceRestRepository findByTopic(Context context, @Parameter(value = "topic" if (pageable.getSort() != null && pageable.getSort().getOrderFor(ORDER_FIELD) != null) { ascending = pageable.getSort().getOrderFor(ORDER_FIELD).getDirection() == Direction.ASC; } - nbEvents = nbEventService.findEventsByTopicAndPage(context, topic, - pageable.getOffset(), pageable.getPageSize(), - ORDER_FIELD, ascending); - count = nbEventService.countEventsByTopic(context, topic); + nbEvents = nbEventService.findEventsByTopicAndPage(topic, + pageable.getOffset(), pageable.getPageSize(), ORDER_FIELD, ascending); + count = nbEventService.countEventsByTopic(topic); if (nbEvents == null) { return null; } @@ -105,7 +104,7 @@ protected void delete(Context context, String id) throws AuthorizeException { try { item = itemService.find(context, UUID.fromString(id)); EPerson eperson = context.getCurrentUser(); - nbEventService.deleteEventByEventId(context, id); + nbEventService.deleteEventByEventId(id); nbEventDao.storeEvent(context, id, eperson, item); } catch (SQLException e) { throw new RuntimeException("Unable to delete NBEvent " + id, e); @@ -121,7 +120,7 @@ public Page findAll(Context context, Pageable pageable) { @PreAuthorize("hasAuthority('ADMIN')") protected void patch(Context context, HttpServletRequest request, String apiCategory, String model, String id, Patch patch) throws SQLException, AuthorizeException { - NBEvent nbEvent = nbEventService.findEventByEventId(context, id); + NBEvent nbEvent = nbEventService.findEventByEventId(id); resourcePatch.patch(context, nbEvent, patch.getOperations()); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NBEventTargetLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NBEventTargetLinkRepository.java index 5c73f4d244c9..76584c41795c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NBEventTargetLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NBEventTargetLinkRepository.java @@ -54,7 +54,7 @@ public class NBEventTargetLinkRepository extends AbstractDSpaceRestRepository im public ItemRest getTarget(@Nullable HttpServletRequest request, String id, @Nullable Pageable pageable, Projection projection) { Context context = obtainContext(); - NBEvent nbEvent = nbEventService.findEventByEventId(context, id); + NBEvent nbEvent = nbEventService.findEventByEventId(id); if (nbEvent == null) { throw new ResourceNotFoundException("No nb event with ID: " + id); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NBEventTopicLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NBEventTopicLinkRepository.java index 94da3b8a507a..9b3ac3fd5c45 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NBEventTopicLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NBEventTopicLinkRepository.java @@ -48,7 +48,7 @@ public class NBEventTopicLinkRepository extends AbstractDSpaceRestRepository imp public NBTopicRest getTopic(@Nullable HttpServletRequest request, String id, @Nullable Pageable pageable, Projection projection) { Context context = obtainContext(); - NBEvent nbEvent = nbEventService.findEventByEventId(context, id); + NBEvent nbEvent = nbEventService.findEventByEventId(id); if (nbEvent == null) { throw new ResourceNotFoundException("No nb event with ID: " + id); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NBSourceRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NBSourceRestRepository.java index ceeef3671abd..872aa4d43971 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NBSourceRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NBSourceRestRepository.java @@ -35,17 +35,17 @@ public class NBSourceRestRepository extends DSpaceRestRepository findAll(Context context, Pageable pageable) { - List nbSources = nbEventService.findAllSources(context, pageable.getOffset(), pageable.getPageSize()); - long count = nbEventService.countTopics(context); - if (nbSources == null) { - return null; - } + List nbSources = nbEventService.findAllSources(pageable.getOffset(), pageable.getPageSize()); + long count = nbEventService.countSources(); return converter.toRestPage(nbSources, pageable, count, utils.obtainProjection()); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NBTopicRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NBTopicRestRepository.java index 479a606e0017..280d7e26d114 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NBTopicRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NBTopicRestRepository.java @@ -45,8 +45,8 @@ public NBTopicRest findOne(Context context, String id) { @Override @PreAuthorize("hasAuthority('ADMIN')") public Page findAll(Context context, Pageable pageable) { - List nbTopics = nbEventService.findAllTopics(context, pageable.getOffset(), pageable.getPageSize()); - long count = nbEventService.countTopics(context); + List nbTopics = nbEventService.findAllTopics(pageable.getOffset(), pageable.getPageSize()); + long count = nbEventService.countTopics(); if (nbTopics == null) { return null; } @@ -56,9 +56,9 @@ public Page findAll(Context context, Pageable pageable) { @SearchRestMethod(name = "bySource") @PreAuthorize("hasAuthority('ADMIN')") public Page findBySource(Context context, String source, Pageable pageable) { - List nbTopics = nbEventService.findAllTopicsBySource(context, String.valueOf(source), + List nbTopics = nbEventService.findAllTopicsBySource(source, pageable.getOffset(), pageable.getPageSize()); - long count = nbEventService.countTopicsBySource(context, String.valueOf(source)); + long count = nbEventService.countTopicsBySource(source); if (nbTopics == null) { return null; } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/NBSourceRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/NBSourceRestRepositoryIT.java new file mode 100644 index 000000000000..2af54b74da49 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/NBSourceRestRepositoryIT.java @@ -0,0 +1,200 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest; + +import static org.dspace.app.rest.matcher.NBSourceMatcher.matchNBSourceEntry; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.is; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.ItemBuilder; +import org.dspace.builder.NBEventBuilder; +import org.dspace.content.Collection; +import org.dspace.content.Item; +import org.dspace.content.NBEvent; +import org.dspace.services.ConfigurationService; +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Integration tests for {@link NBSourceRestRepository}. + * + * @author Luca Giamminonni (luca.giamminonni at 4science.it) + * + */ +public class NBSourceRestRepositoryIT extends AbstractControllerIntegrationTest { + + @Autowired + private ConfigurationService configurationService; + + private Item target; + + @Before + public void setup() { + + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withTitle("Community") + .build(); + + Collection collection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection") + .build(); + + target = ItemBuilder.createItem(context, collection) + .withTitle("Item") + .build(); + + context.restoreAuthSystemState(); + + configurationService.setProperty("nbevent.sources", + new String[] { "openaire", "test-source", "test-source-2" }); + + } + + @Test + public void testFindAll() throws Exception { + + context.turnOffAuthorisationSystem(); + + createEvent("openaire", "TOPIC/OPENAIRE/1", "Title 1"); + createEvent("openaire", "TOPIC/OPENAIRE/2", "Title 2"); + createEvent("openaire", "TOPIC/OPENAIRE/2", "Title 3"); + + createEvent("test-source", "TOPIC/TEST/1", "Title 4"); + createEvent("test-source", "TOPIC/TEST/1", "Title 5"); + + context.restoreAuthSystemState(); + + String authToken = getAuthToken(admin.getEmail(), password); + getClient(authToken).perform(get("/api/integration/nbsources")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.nbsources", contains( + matchNBSourceEntry("openaire", 3), + matchNBSourceEntry("test-source", 2), + matchNBSourceEntry("test-source-2", 0)))) + .andExpect(jsonPath("$.page.size", is(20))) + .andExpect(jsonPath("$.page.totalElements", is(3))); + + } + + @Test + public void testFindAllForbidden() throws Exception { + + context.turnOffAuthorisationSystem(); + + createEvent("openaire", "TOPIC/OPENAIRE/1", "Title 1"); + createEvent("test-source", "TOPIC/TEST/1", "Title 4"); + + context.restoreAuthSystemState(); + + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform(get("/api/integration/nbsources")) + .andExpect(status().isForbidden()); + + } + + @Test + public void testFindAllUnauthorized() throws Exception { + + context.turnOffAuthorisationSystem(); + + createEvent("openaire", "TOPIC/OPENAIRE/1", "Title 1"); + createEvent("test-source", "TOPIC/TEST/1", "Title 4"); + + context.restoreAuthSystemState(); + + getClient().perform(get("/api/integration/nbsources")) + .andExpect(status().isUnauthorized()); + + } + + @Test + public void testFindOne() throws Exception { + + context.turnOffAuthorisationSystem(); + + createEvent("openaire", "TOPIC/OPENAIRE/1", "Title 1"); + createEvent("openaire", "TOPIC/OPENAIRE/2", "Title 2"); + createEvent("openaire", "TOPIC/OPENAIRE/2", "Title 3"); + + createEvent("test-source", "TOPIC/TEST/1", "Title 4"); + createEvent("test-source", "TOPIC/TEST/1", "Title 5"); + + context.restoreAuthSystemState(); + + String authToken = getAuthToken(admin.getEmail(), password); + getClient(authToken).perform(get("/api/integration/nbsources/openaire")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$", matchNBSourceEntry("openaire", 3))); + + getClient(authToken).perform(get("/api/integration/nbsources/test-source")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$", matchNBSourceEntry("test-source", 2))); + + getClient(authToken).perform(get("/api/integration/nbsources/test-source-2")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$", matchNBSourceEntry("test-source-2", 0))); + + getClient(authToken).perform(get("/api/integration/nbsources/unknown-test-source")) + .andExpect(status().isNotFound()); + + } + + @Test + public void testFindOneForbidden() throws Exception { + + context.turnOffAuthorisationSystem(); + + createEvent("openaire", "TOPIC/OPENAIRE/1", "Title 1"); + createEvent("test-source", "TOPIC/TEST/1", "Title 4"); + + context.restoreAuthSystemState(); + + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform(get("/api/integration/nbsources/openaire")) + .andExpect(status().isForbidden()); + + } + + @Test + public void testFindOneUnauthorized() throws Exception { + + context.turnOffAuthorisationSystem(); + + createEvent("openaire", "TOPIC/OPENAIRE/1", "Title 1"); + createEvent("test-source", "TOPIC/TEST/1", "Title 4"); + + context.restoreAuthSystemState(); + + getClient().perform(get("/api/integration/nbsources/openaire")) + .andExpect(status().isUnauthorized()); + + } + + private NBEvent createEvent(String source, String topic, String title) { + return NBEventBuilder.createTarget(context, target) + .withSource(source) + .withTopic(topic) + .withTitle(title) + .build(); + } + +} From 4219a69f7072e4ab00b859c5caf68512aba25e5c Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Thu, 17 Feb 2022 15:04:38 +0100 Subject: [PATCH 006/510] [CST-5246] Added integration tests for search topics by source --- .../service/impl/NBEventServiceImpl.java | 29 ++-- .../repository/NBTopicRestRepository.java | 4 +- .../app/rest/NBTopicRestRepositoryIT.java | 127 +++++++++++++++--- 3 files changed, 132 insertions(+), 28 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/nbevent/service/impl/NBEventServiceImpl.java b/dspace-api/src/main/java/org/dspace/app/nbevent/service/impl/NBEventServiceImpl.java index 47c98e1467b9..667e446b5bbd 100644 --- a/dspace-api/src/main/java/org/dspace/app/nbevent/service/impl/NBEventServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/app/nbevent/service/impl/NBEventServiceImpl.java @@ -107,8 +107,7 @@ public long countTopics() { solrQuery.setRows(0); solrQuery.setQuery("*:*"); solrQuery.setFacet(true); - // we would like to get eventually topic that has no longer active nb events - solrQuery.setFacetMinCount(0); + solrQuery.setFacetMinCount(1); solrQuery.addFacetField(TOPIC); QueryResponse response; try { @@ -125,8 +124,7 @@ public long countTopicsBySource(String source) { solrQuery.setRows(0); solrQuery.setQuery("*:*"); solrQuery.setFacet(true); - // we would like to get eventually topic that has no longer active nb events - solrQuery.setFacetMinCount(0); + solrQuery.setFacetMinCount(1); solrQuery.addFacetField(TOPIC); solrQuery.addFilterQuery("source:" + source); QueryResponse response; @@ -164,8 +162,7 @@ public NBTopic findTopicByTopicId(String topicId) { solrQuery.setRows(0); solrQuery.setQuery(TOPIC + ":" + topicId.replaceAll("!", "/")); solrQuery.setFacet(true); - // we would like to get eventually topic that has no longer active nb events - solrQuery.setFacetMinCount(0); + solrQuery.setFacetMinCount(1); solrQuery.addFacetField(TOPIC); QueryResponse response; try { @@ -194,16 +191,20 @@ public List findAllTopics(long offset, long count) { @Override public List findAllTopicsBySource(String source, long offset, long count) { + + if (source != null && isNotSupportedSource(source)) { + return null; + } + SolrQuery solrQuery = new SolrQuery(); solrQuery.setRows(0); solrQuery.setQuery("*:*"); solrQuery.setFacet(true); - // we would like to get eventually topic that has no longer active nb events - solrQuery.setFacetMinCount(0); + solrQuery.setFacetMinCount(1); solrQuery.setFacetLimit((int) (offset + count)); solrQuery.addFacetField(TOPIC); if (source != null) { - solrQuery.addFilterQuery("source:" + source); + solrQuery.addFilterQuery(SOURCE + ":" + source); } QueryResponse response; List nbTopics = null; @@ -236,7 +237,7 @@ public void store(Context context, NBEvent dto) { UpdateRequest updateRequest = new UpdateRequest(); String topic = dto.getTopic(); - if (!ArrayUtils.contains(getSupportedSources(), dto.getSource())) { + if (isNotSupportedSource(dto.getSource())) { throw new IllegalArgumentException("The source of the given event is not supported: " + dto.getSource()); } @@ -374,7 +375,7 @@ private String getHandleFromOriginalId(String originalId) { @Override public NBSource findSource(String sourceName) { - if (!ArrayUtils.contains(getSupportedSources(), sourceName)) { + if (isNotSupportedSource(sourceName)) { return null; } @@ -382,7 +383,7 @@ public NBSource findSource(String sourceName) { solrQuery.setRows(0); solrQuery.addFilterQuery(SOURCE + ":" + sourceName); solrQuery.setFacet(true); - solrQuery.setFacetMinCount(0); + solrQuery.setFacetMinCount(1); solrQuery.addFacetField(SOURCE); QueryResponse response; @@ -424,6 +425,10 @@ public long countSources() { return getSupportedSources().length; } + private boolean isNotSupportedSource(String source) { + return !ArrayUtils.contains(getSupportedSources(), source); + } + private String[] getSupportedSources() { return configurationService.getArrayProperty("nbevent.sources", new String[] { NBEvent.OPENAIRE_SOURCE }); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NBTopicRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NBTopicRestRepository.java index 280d7e26d114..53b1a4be6cd8 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NBTopicRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NBTopicRestRepository.java @@ -11,6 +11,7 @@ import org.dspace.app.nbevent.NBTopic; import org.dspace.app.nbevent.service.NBEventService; +import org.dspace.app.rest.Parameter; import org.dspace.app.rest.SearchRestMethod; import org.dspace.app.rest.model.NBTopicRest; import org.dspace.core.Context; @@ -55,7 +56,8 @@ public Page findAll(Context context, Pageable pageable) { @SearchRestMethod(name = "bySource") @PreAuthorize("hasAuthority('ADMIN')") - public Page findBySource(Context context, String source, Pageable pageable) { + public Page findBySource(Context context, + @Parameter(value = "source", required = true) String source, Pageable pageable) { List nbTopics = nbEventService.findAllTopicsBySource(source, pageable.getOffset(), pageable.getPageSize()); long count = nbEventService.countTopicsBySource(source); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/NBTopicRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/NBTopicRestRepositoryIT.java index dea109219a91..7fe9dbc8b2d8 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/NBTopicRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/NBTopicRestRepositoryIT.java @@ -20,9 +20,10 @@ import org.dspace.builder.CommunityBuilder; import org.dspace.builder.NBEventBuilder; import org.dspace.content.Collection; -import org.dspace.content.NBEvent; +import org.dspace.services.ConfigurationService; import org.hamcrest.Matchers; import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; /** * Integration tests for {@link NBTopicRestRepository}. @@ -32,6 +33,9 @@ */ public class NBTopicRestRepositoryIT extends AbstractControllerIntegrationTest { + @Autowired + private ConfigurationService configurationService; + @Test public void findAllTest() throws Exception { context.turnOffAuthorisationSystem(); @@ -39,16 +43,16 @@ public void findAllTest() throws Exception { .withName("Parent Community") .build(); Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build(); - NBEvent event1 = NBEventBuilder.createTarget(context, col1, "Science and Freedom") + NBEventBuilder.createTarget(context, col1, "Science and Freedom") .withTopic("ENRICH/MISSING/PID") .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144300\"}").build(); - NBEvent event2 = NBEventBuilder.createTarget(context, col1, "Science and Freedom 2") + NBEventBuilder.createTarget(context, col1, "Science and Freedom 2") .withTopic("ENRICH/MISSING/PID") .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144301\"}").build(); - NBEvent event3 = NBEventBuilder.createTarget(context, col1, "Science and Freedom 3") + NBEventBuilder.createTarget(context, col1, "Science and Freedom 3") .withTopic("ENRICH/MORE/PID") .withMessage("{\"pids[0].type\":\"pmid\",\"pids[0].value\":\"10.2307/2144302\"}").build(); - NBEvent event4 = NBEventBuilder.createTarget(context, col1, "Science and Freedom 4") + NBEventBuilder.createTarget(context, col1, "Science and Freedom 4") .withTopic("ENRICH/MISSING/ABSTRACT") .withMessage( "{\"abstracts[0]\": \"Descrizione delle caratteristiche...\"}") @@ -84,16 +88,16 @@ public void findAllPaginationTest() throws Exception { .build(); //create collection Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build(); - NBEvent event1 = NBEventBuilder.createTarget(context, col1, "Science and Freedom") + NBEventBuilder.createTarget(context, col1, "Science and Freedom") .withTopic("ENRICH/MISSING/PID") .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144300\"}").build(); - NBEvent event2 = NBEventBuilder.createTarget(context, col1, "Science and Freedom 2") + NBEventBuilder.createTarget(context, col1, "Science and Freedom 2") .withTopic("ENRICH/MISSING/PID") .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144301\"}").build(); - NBEvent event3 = NBEventBuilder.createTarget(context, col1, "Science and Freedom 3") + NBEventBuilder.createTarget(context, col1, "Science and Freedom 3") .withTopic("ENRICH/MORE/PID") .withMessage("{\"pids[0].type\":\"pmid\",\"pids[0].value\":\"10.2307/2144302\"}").build(); - NBEvent event4 = NBEventBuilder.createTarget(context, col1, "Science and Freedom 4") + NBEventBuilder.createTarget(context, col1, "Science and Freedom 4") .withTopic("ENRICH/MISSING/ABSTRACT") .withMessage( "{\"abstracts[0]\": \"Descrizione delle caratteristiche...\"}") @@ -119,16 +123,16 @@ public void findOneTest() throws Exception { .withName("Parent Community") .build(); Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build(); - NBEvent event1 = NBEventBuilder.createTarget(context, col1, "Science and Freedom") + NBEventBuilder.createTarget(context, col1, "Science and Freedom") .withTopic("ENRICH/MISSING/PID") .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144300\"}").build(); - NBEvent event2 = NBEventBuilder.createTarget(context, col1, "Science and Freedom 2") + NBEventBuilder.createTarget(context, col1, "Science and Freedom 2") .withTopic("ENRICH/MISSING/PID") .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144301\"}").build(); - NBEvent event3 = NBEventBuilder.createTarget(context, col1, "Science and Freedom 3") + NBEventBuilder.createTarget(context, col1, "Science and Freedom 3") .withTopic("ENRICH/MORE/PID") .withMessage("{\"pids[0].type\":\"pmid\",\"pids[0].value\":\"10.2307/2144302\"}").build(); - NBEvent event4 = NBEventBuilder.createTarget(context, col1, "Science and Freedom 4") + NBEventBuilder.createTarget(context, col1, "Science and Freedom 4") .withTopic("ENRICH/MISSING/ABSTRACT") .withMessage( "{\"abstracts[0]\": \"Descrizione delle caratteristiche...\"}") @@ -148,7 +152,7 @@ public void findOneUnauthorizedTest() throws Exception { .withName("Parent Community") .build(); Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build(); - NBEvent event1 = NBEventBuilder.createTarget(context, col1, "Science and Freedom") + NBEventBuilder.createTarget(context, col1, "Science and Freedom") .withTopic("ENRICH/MISSING/PID").build(); context.restoreAuthSystemState(); getClient().perform(get("/api/integration/nbtopics/ENRICH!MISSING!PID")).andExpect(status().isUnauthorized()); @@ -163,7 +167,7 @@ public void findOneForbiddenTest() throws Exception { .withName("Parent Community") .build(); Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build(); - NBEvent event1 = NBEventBuilder.createTarget(context, col1, "Science and Freedom") + NBEventBuilder.createTarget(context, col1, "Science and Freedom") .withTopic("ENRICH/MISSING/PID").build(); context.restoreAuthSystemState(); String authToken = getAuthToken(eperson.getEmail(), password); @@ -173,4 +177,97 @@ public void findOneForbiddenTest() throws Exception { .andExpect(status().isForbidden()); } + @Test + public void findBySourceTest() throws Exception { + context.turnOffAuthorisationSystem(); + configurationService.setProperty("nbevent.sources", + new String[] { "openaire", "test-source", "test-source-2" }); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build(); + NBEventBuilder.createTarget(context, col1, "Science and Freedom") + .withTopic("ENRICH/MISSING/PID") + .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144300\"}").build(); + NBEventBuilder.createTarget(context, col1, "Science and Freedom 2") + .withTopic("ENRICH/MISSING/PID") + .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144301\"}").build(); + NBEventBuilder.createTarget(context, col1, "Science and Freedom 3") + .withTopic("ENRICH/MORE/PID") + .withMessage("{\"pids[0].type\":\"pmid\",\"pids[0].value\":\"10.2307/2144302\"}").build(); + NBEventBuilder.createTarget(context, col1, "Science and Freedom 4") + .withTopic("ENRICH/MISSING/ABSTRACT") + .withMessage( + "{\"abstracts[0]\": \"Descrizione delle caratteristiche...\"}") + .build(); + NBEventBuilder.createTarget(context, col1, "Science and Freedom 5") + .withTopic("TEST/TOPIC") + .withSource("test-source") + .build(); + NBEventBuilder.createTarget(context, col1, "Science and Freedom 6") + .withTopic("TEST/TOPIC") + .withSource("test-source") + .build(); + NBEventBuilder.createTarget(context, col1, "Science and Freedom 7") + .withTopic("TEST/TOPIC/2") + .withSource("test-source") + .build(); + context.restoreAuthSystemState(); + String authToken = getAuthToken(admin.getEmail(), password); + getClient(authToken).perform(get("/api/integration/nbtopics/search/bySource") + .param("source", "openaire")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.nbtopics", + Matchers.containsInAnyOrder(NBTopicMatcher.matchNBTopicEntry("ENRICH/MISSING/PID", 2), + NBTopicMatcher.matchNBTopicEntry("ENRICH/MISSING/ABSTRACT", 1), + NBTopicMatcher.matchNBTopicEntry("ENRICH/MORE/PID", 1)))) + .andExpect(jsonPath("$.page.size", is(20))).andExpect(jsonPath("$.page.totalElements", is(3))); + getClient(authToken).perform(get("/api/integration/nbtopics/search/bySource") + .param("source", "test-source")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.nbtopics", + Matchers.containsInAnyOrder(NBTopicMatcher.matchNBTopicEntry("TEST/TOPIC/2", 1), + NBTopicMatcher.matchNBTopicEntry("TEST/TOPIC", 2)))) + .andExpect(jsonPath("$.page.size", is(20))).andExpect(jsonPath("$.page.totalElements", is(2))); + getClient(authToken).perform(get("/api/integration/nbtopics/search/bySource") + .param("source", "test-source-2")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.nbtopics").doesNotExist()) + .andExpect(jsonPath("$.page.size", is(20))).andExpect(jsonPath("$.page.totalElements", is(0))); + } + + @Test + public void findBySourceUnauthorizedTest() throws Exception { + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build(); + NBEventBuilder.createTarget(context, col1, "Science and Freedom") + .withTopic("ENRICH/MISSING/PID").build(); + context.restoreAuthSystemState(); + getClient().perform(get("/api/integration/nbtopics/search/bySource") + .param("source", "openaire")) + .andExpect(status().isUnauthorized()); + } + + @Test + public void findBySourceForbiddenTest() throws Exception { + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build(); + NBEventBuilder.createTarget(context, col1, "Science and Freedom") + .withTopic("ENRICH/MISSING/PID").build(); + context.restoreAuthSystemState(); + String authToken = getAuthToken(eperson.getEmail(), password); + getClient(authToken).perform(get("/api/integration/nbtopics/search/bySource") + .param("source", "openaire")) + .andExpect(status().isForbidden()); + } + } From d0498d2863da962b1ea14d40c9818c34883d5e16 Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Fri, 18 Feb 2022 17:08:59 +0100 Subject: [PATCH 007/510] [CST-5249] Openaire correction service improvements --- .../org/dspace/app/nbevent/NBEntityMetadataAction.java | 9 ++++++--- .../org/dspace/app/nbevent/OpenaireEventsRunnable.java | 7 ++++--- .../app/nbevent/service/impl/NBEventServiceImpl.java | 7 +++---- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/nbevent/NBEntityMetadataAction.java b/dspace-api/src/main/java/org/dspace/app/nbevent/NBEntityMetadataAction.java index f2322fe6b703..8051362cfa2c 100644 --- a/dspace-api/src/main/java/org/dspace/app/nbevent/NBEntityMetadataAction.java +++ b/dspace-api/src/main/java/org/dspace/app/nbevent/NBEntityMetadataAction.java @@ -108,12 +108,15 @@ public void applyCorrection(Context context, Item item, Item relatedItem, NBMess if (relatedItem != null) { link(context, item, relatedItem); } else { + Collection collection = collectionService.retrieveCollectionByEntityType(context, item, entityType); + if (collection == null) { + throw new IllegalStateException("No collection found by entity type: " + collection); + } + WorkspaceItem workspaceItem = workspaceItemService.create(context, collection, false); relatedItem = workspaceItem.getItem(); - if (StringUtils.isNotBlank(entityType)) { - itemService.addMetadata(context, relatedItem, "dspace", "entity", "type", null, entityType); - } + for (String key : entityMetadata.keySet()) { String value = getValue(message, key); if (StringUtils.isNotBlank(value)) { diff --git a/dspace-api/src/main/java/org/dspace/app/nbevent/OpenaireEventsRunnable.java b/dspace-api/src/main/java/org/dspace/app/nbevent/OpenaireEventsRunnable.java index d56858402be8..4969267d9440 100644 --- a/dspace-api/src/main/java/org/dspace/app/nbevent/OpenaireEventsRunnable.java +++ b/dspace-api/src/main/java/org/dspace/app/nbevent/OpenaireEventsRunnable.java @@ -7,6 +7,8 @@ */ package org.dspace.app.nbevent; +import static org.apache.commons.lang3.exception.ExceptionUtils.getRootCauseMessage; + import java.io.IOException; import java.io.InputStream; import java.sql.SQLException; @@ -96,8 +98,7 @@ public void internalRun() throws Exception { this.entries = jsonMapper.readValue(getNBEventsInputStream(), new TypeReference>() { }); } catch (IOException e) { - LOGGER.error("File is not found or not readable: " + fileLocation); - e.printStackTrace(); + LOGGER.error("File is not found or not readable: " + fileLocation, e); System.exit(1); } @@ -110,7 +111,7 @@ public void internalRun() throws Exception { try { nbEventService.store(context, entry); } catch (RuntimeException e) { - System.out.println("Skip event for originalId " + entry.getOriginalId() + ": " + e.getMessage()); + handler.logWarning(getRootCauseMessage(e)); } } diff --git a/dspace-api/src/main/java/org/dspace/app/nbevent/service/impl/NBEventServiceImpl.java b/dspace-api/src/main/java/org/dspace/app/nbevent/service/impl/NBEventServiceImpl.java index 667e446b5bbd..03fbde444ae6 100644 --- a/dspace-api/src/main/java/org/dspace/app/nbevent/service/impl/NBEventServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/app/nbevent/service/impl/NBEventServiceImpl.java @@ -256,9 +256,8 @@ public void store(Context context, NBEvent dto) { doc.addField(LAST_UPDATE, new Date()); final String resourceUUID = getResourceUUID(context, dto.getOriginalId()); if (resourceUUID == null) { - log.warn("Skipped event " + checksum + " related to the oai record " + dto.getOriginalId() - + " as the record was not found"); - return; + throw new IllegalArgumentException("Skipped event " + checksum + + " related to the oai record " + dto.getOriginalId() + " as the record was not found"); } doc.addField(RESOURCE_UUID, resourceUUID); doc.addField(RELATED_UUID, dto.getRelated()); @@ -358,7 +357,7 @@ private String getResourceUUID(Context context, String originalId) throws Except return null; } } else { - throw new RuntimeException("Malformed originalId " + originalId); + throw new IllegalArgumentException("Malformed originalId " + originalId); } } From 714293cc9e0f68cac7cfd878f46385443893a727 Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Fri, 22 Apr 2022 16:46:56 +0200 Subject: [PATCH 008/510] [CST-5249] Community feedbacks --- .../java/org/dspace/app/nbevent/NBAction.java | 4 ++-- ...ava => NBEntityOpenaireMetadataAction.java} | 18 +++++++++++------- .../app/nbevent/NBEventActionServiceImpl.java | 13 ++++++++----- ...n.java => NBOpenaireMetadataMapAction.java} | 17 +++++++++++------ ...ava => NBOpenaireSimpleMetadataAction.java} | 10 +++++----- .../java/org/dspace/app/nbevent/NBSource.java | 2 +- .../dto/{NBMessage.java => NBMessageDTO.java} | 2 +- ...ireMessage.java => OpenaireMessageDTO.java} | 4 ++-- .../service/impl/NBEventServiceImpl.java | 3 --- .../main/java/org/dspace/content/NBEvent.java | 8 ++++---- .../app/rest/converter/NBEventConverter.java | 10 +++++----- .../app/rest/matcher/NBEventMatcher.java | 6 +++--- dspace/config/modules/oaire-nbevents.cfg | 4 ++-- dspace/config/spring/api/nbevents.xml | 6 +++--- 14 files changed, 58 insertions(+), 49 deletions(-) rename dspace-api/src/main/java/org/dspace/app/nbevent/{NBEntityMetadataAction.java => NBEntityOpenaireMetadataAction.java} (92%) rename dspace-api/src/main/java/org/dspace/app/nbevent/{NBMetadataMapAction.java => NBOpenaireMetadataMapAction.java} (82%) rename dspace-api/src/main/java/org/dspace/app/nbevent/{NBSimpleMetadataAction.java => NBOpenaireSimpleMetadataAction.java} (85%) rename dspace-api/src/main/java/org/dspace/app/nbevent/service/dto/{NBMessage.java => NBMessageDTO.java} (93%) rename dspace-api/src/main/java/org/dspace/app/nbevent/service/dto/{OpenaireMessage.java => OpenaireMessageDTO.java} (96%) diff --git a/dspace-api/src/main/java/org/dspace/app/nbevent/NBAction.java b/dspace-api/src/main/java/org/dspace/app/nbevent/NBAction.java index 70e762419733..099982d289c8 100644 --- a/dspace-api/src/main/java/org/dspace/app/nbevent/NBAction.java +++ b/dspace-api/src/main/java/org/dspace/app/nbevent/NBAction.java @@ -7,7 +7,7 @@ */ package org.dspace.app.nbevent; -import org.dspace.app.nbevent.service.dto.NBMessage; +import org.dspace.app.nbevent.service.dto.NBMessageDTO; import org.dspace.content.Item; import org.dspace.core.Context; @@ -27,5 +27,5 @@ public interface NBAction { * @param relatedItem the related item, if any * @param message the message with the correction details */ - public void applyCorrection(Context context, Item item, Item relatedItem, NBMessage message); + public void applyCorrection(Context context, Item item, Item relatedItem, NBMessageDTO message); } diff --git a/dspace-api/src/main/java/org/dspace/app/nbevent/NBEntityMetadataAction.java b/dspace-api/src/main/java/org/dspace/app/nbevent/NBEntityOpenaireMetadataAction.java similarity index 92% rename from dspace-api/src/main/java/org/dspace/app/nbevent/NBEntityMetadataAction.java rename to dspace-api/src/main/java/org/dspace/app/nbevent/NBEntityOpenaireMetadataAction.java index 8051362cfa2c..e16c18edd0ac 100644 --- a/dspace-api/src/main/java/org/dspace/app/nbevent/NBEntityMetadataAction.java +++ b/dspace-api/src/main/java/org/dspace/app/nbevent/NBEntityOpenaireMetadataAction.java @@ -11,8 +11,8 @@ import java.util.Map; import org.apache.commons.lang3.StringUtils; -import org.dspace.app.nbevent.service.dto.NBMessage; -import org.dspace.app.nbevent.service.dto.OpenaireMessage; +import org.dspace.app.nbevent.service.dto.NBMessageDTO; +import org.dspace.app.nbevent.service.dto.OpenaireMessageDTO; import org.dspace.authorize.AuthorizeException; import org.dspace.content.Collection; import org.dspace.content.EntityType; @@ -37,7 +37,7 @@ * @author Andrea Bollini (andrea.bollini at 4science.it) * */ -public class NBEntityMetadataAction implements NBAction { +public class NBEntityOpenaireMetadataAction implements NBAction { private String relation; private String entityType; private Map entityMetadata; @@ -103,7 +103,7 @@ public void setEntityMetadata(Map entityMetadata) { } @Override - public void applyCorrection(Context context, Item item, Item relatedItem, NBMessage message) { + public void applyCorrection(Context context, Item item, Item relatedItem, NBMessageDTO message) { try { if (relatedItem != null) { link(context, item, relatedItem); @@ -134,6 +134,10 @@ public void applyCorrection(Context context, Item item, Item relatedItem, NBMess } } + /** + * Create a new relationship between the two given item, based on the configured + * relation. + */ private void link(Context context, Item item, Item relatedItem) throws SQLException, AuthorizeException { EntityType project = entityTypeService.findByEntityType(context, entityType); RelationshipType relType = relationshipTypeService.findByEntityType(context, project).stream() @@ -151,12 +155,12 @@ private void link(Context context, Item item, Item relatedItem) throws SQLExcept relationshipService.update(context, persistedRelationship); } - private String getValue(NBMessage message, String key) { - if (!(message instanceof OpenaireMessage)) { + private String getValue(NBMessageDTO message, String key) { + if (!(message instanceof OpenaireMessageDTO)) { return null; } - OpenaireMessage openaireMessage = (OpenaireMessage) message; + OpenaireMessageDTO openaireMessage = (OpenaireMessageDTO) message; if (StringUtils.equals(key, "acronym")) { return openaireMessage.getAcronym(); diff --git a/dspace-api/src/main/java/org/dspace/app/nbevent/NBEventActionServiceImpl.java b/dspace-api/src/main/java/org/dspace/app/nbevent/NBEventActionServiceImpl.java index ac9fdd5c3fe7..9ebd796a8069 100644 --- a/dspace-api/src/main/java/org/dspace/app/nbevent/NBEventActionServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/app/nbevent/NBEventActionServiceImpl.java @@ -79,7 +79,7 @@ public void accept(Context context, NBEvent nbevent) { topicsToActions.get(nbevent.getTopic()).applyCorrection(context, item, related, jsonMapper.readValue(nbevent.getMessage(), nbevent.getMessageDtoClass())); nbEventService.deleteEventByEventId(nbevent.getEventId()); - makeAcknowledgement(nbevent.getEventId(), NBEvent.ACCEPTED); + makeAcknowledgement(nbevent.getEventId(), nbevent.getSource(), NBEvent.ACCEPTED); } catch (SQLException | JsonProcessingException e) { throw new RuntimeException(e); } @@ -88,17 +88,20 @@ public void accept(Context context, NBEvent nbevent) { @Override public void discard(Context context, NBEvent nbevent) { nbEventService.deleteEventByEventId(nbevent.getEventId()); - makeAcknowledgement(nbevent.getEventId(), NBEvent.DISCARDED); + makeAcknowledgement(nbevent.getEventId(), nbevent.getSource(), NBEvent.DISCARDED); } @Override public void reject(Context context, NBEvent nbevent) { nbEventService.deleteEventByEventId(nbevent.getEventId()); - makeAcknowledgement(nbevent.getEventId(), NBEvent.REJECTED); + makeAcknowledgement(nbevent.getEventId(), nbevent.getSource(), NBEvent.REJECTED); } - private void makeAcknowledgement(String eventId, String status) { - String[] ackwnoledgeCallbacks = configurationService.getArrayProperty("oaire-nbevents.acknowledge-url"); + /** + * Make acknowledgement to the configured urls for the event status. + */ + private void makeAcknowledgement(String eventId, String source, String status) { + String[] ackwnoledgeCallbacks = configurationService.getArrayProperty(source + "-nbevents.acknowledge-url"); if (ackwnoledgeCallbacks != null) { for (String ackwnoledgeCallback : ackwnoledgeCallbacks) { if (StringUtils.isNotBlank(ackwnoledgeCallback)) { diff --git a/dspace-api/src/main/java/org/dspace/app/nbevent/NBMetadataMapAction.java b/dspace-api/src/main/java/org/dspace/app/nbevent/NBOpenaireMetadataMapAction.java similarity index 82% rename from dspace-api/src/main/java/org/dspace/app/nbevent/NBMetadataMapAction.java rename to dspace-api/src/main/java/org/dspace/app/nbevent/NBOpenaireMetadataMapAction.java index 3d7e2114cef8..216f44a2d502 100644 --- a/dspace-api/src/main/java/org/dspace/app/nbevent/NBMetadataMapAction.java +++ b/dspace-api/src/main/java/org/dspace/app/nbevent/NBOpenaireMetadataMapAction.java @@ -10,8 +10,8 @@ import java.sql.SQLException; import java.util.Map; -import org.dspace.app.nbevent.service.dto.NBMessage; -import org.dspace.app.nbevent.service.dto.OpenaireMessage; +import org.dspace.app.nbevent.service.dto.NBMessageDTO; +import org.dspace.app.nbevent.service.dto.OpenaireMessageDTO; import org.dspace.authorize.AuthorizeException; import org.dspace.content.Item; import org.dspace.content.service.ItemService; @@ -25,7 +25,7 @@ * @author Andrea Bollini (andrea.bollini at 4science.it) * */ -public class NBMetadataMapAction implements NBAction { +public class NBOpenaireMetadataMapAction implements NBAction { public static final String DEFAULT = "default"; private Map types; @@ -44,14 +44,18 @@ public void setTypes(Map types) { this.types = types; } + /** + * Apply the correction on one metadata field of the given item based on the + * openaire message type. + */ @Override - public void applyCorrection(Context context, Item item, Item relatedItem, NBMessage message) { + public void applyCorrection(Context context, Item item, Item relatedItem, NBMessageDTO message) { - if (!(message instanceof OpenaireMessage)) { + if (!(message instanceof OpenaireMessageDTO)) { throw new IllegalArgumentException("Unsupported message type: " + message.getClass()); } - OpenaireMessage openaireMessage = (OpenaireMessage) message; + OpenaireMessageDTO openaireMessage = (OpenaireMessageDTO) message; try { String targetMetadata = types.get(openaireMessage.getType()); @@ -65,6 +69,7 @@ public void applyCorrection(Context context, Item item, Item relatedItem, NBMess } catch (SQLException | AuthorizeException e) { throw new RuntimeException(e); } + } public String[] splitMetadata(String metadata) { diff --git a/dspace-api/src/main/java/org/dspace/app/nbevent/NBSimpleMetadataAction.java b/dspace-api/src/main/java/org/dspace/app/nbevent/NBOpenaireSimpleMetadataAction.java similarity index 85% rename from dspace-api/src/main/java/org/dspace/app/nbevent/NBSimpleMetadataAction.java rename to dspace-api/src/main/java/org/dspace/app/nbevent/NBOpenaireSimpleMetadataAction.java index 0bff9e05ff2a..f08b3f7db4f4 100644 --- a/dspace-api/src/main/java/org/dspace/app/nbevent/NBSimpleMetadataAction.java +++ b/dspace-api/src/main/java/org/dspace/app/nbevent/NBOpenaireSimpleMetadataAction.java @@ -9,8 +9,8 @@ import java.sql.SQLException; -import org.dspace.app.nbevent.service.dto.NBMessage; -import org.dspace.app.nbevent.service.dto.OpenaireMessage; +import org.dspace.app.nbevent.service.dto.NBMessageDTO; +import org.dspace.app.nbevent.service.dto.OpenaireMessageDTO; import org.dspace.authorize.AuthorizeException; import org.dspace.content.Item; import org.dspace.content.service.ItemService; @@ -24,7 +24,7 @@ * @author Andrea Bollini (andrea.bollini at 4science.it) * */ -public class NBSimpleMetadataAction implements NBAction { +public class NBOpenaireSimpleMetadataAction implements NBAction { private String metadata; private String metadataSchema; private String metadataElement; @@ -51,10 +51,10 @@ public void setMetadata(String metadata) { } @Override - public void applyCorrection(Context context, Item item, Item relatedItem, NBMessage message) { + public void applyCorrection(Context context, Item item, Item relatedItem, NBMessageDTO message) { try { itemService.addMetadata(context, item, metadataSchema, metadataElement, metadataQualifier, null, - ((OpenaireMessage) message).getAbstracts()); + ((OpenaireMessageDTO) message).getAbstracts()); itemService.update(context, item); } catch (SQLException | AuthorizeException e) { throw new RuntimeException(e); diff --git a/dspace-api/src/main/java/org/dspace/app/nbevent/NBSource.java b/dspace-api/src/main/java/org/dspace/app/nbevent/NBSource.java index e74547d531f6..42a416bf90db 100644 --- a/dspace-api/src/main/java/org/dspace/app/nbevent/NBSource.java +++ b/dspace-api/src/main/java/org/dspace/app/nbevent/NBSource.java @@ -10,7 +10,7 @@ import java.util.Date; /** - * This model class represent the notification broker source concept + * This model class represent the source/provider of the NB events (as OpenAIRE). * * @author Luca Giamminonni (luca.giamminonni at 4Science) * diff --git a/dspace-api/src/main/java/org/dspace/app/nbevent/service/dto/NBMessage.java b/dspace-api/src/main/java/org/dspace/app/nbevent/service/dto/NBMessageDTO.java similarity index 93% rename from dspace-api/src/main/java/org/dspace/app/nbevent/service/dto/NBMessage.java rename to dspace-api/src/main/java/org/dspace/app/nbevent/service/dto/NBMessageDTO.java index 4c59ab1c85b1..e341c9bd604e 100644 --- a/dspace-api/src/main/java/org/dspace/app/nbevent/service/dto/NBMessage.java +++ b/dspace-api/src/main/java/org/dspace/app/nbevent/service/dto/NBMessageDTO.java @@ -15,7 +15,7 @@ * @author Luca Giamminonni (luca.giamminonni at 4science.it) * */ -public interface NBMessage { +public interface NBMessageDTO { } diff --git a/dspace-api/src/main/java/org/dspace/app/nbevent/service/dto/OpenaireMessage.java b/dspace-api/src/main/java/org/dspace/app/nbevent/service/dto/OpenaireMessageDTO.java similarity index 96% rename from dspace-api/src/main/java/org/dspace/app/nbevent/service/dto/OpenaireMessage.java rename to dspace-api/src/main/java/org/dspace/app/nbevent/service/dto/OpenaireMessageDTO.java index 188139afef79..5558aa3cb001 100644 --- a/dspace-api/src/main/java/org/dspace/app/nbevent/service/dto/OpenaireMessage.java +++ b/dspace-api/src/main/java/org/dspace/app/nbevent/service/dto/OpenaireMessageDTO.java @@ -10,12 +10,12 @@ import com.fasterxml.jackson.annotation.JsonProperty; /** - * Implementation of {@link NBMessage} that model message coming from OPENAIRE. + * Implementation of {@link NBMessageDTO} that model message coming from OPENAIRE. * * @author Luca Giamminonni (luca.giamminonni at 4science.it) * */ -public class OpenaireMessage implements NBMessage { +public class OpenaireMessageDTO implements NBMessageDTO { @JsonProperty("pids[0].value") private String value; diff --git a/dspace-api/src/main/java/org/dspace/app/nbevent/service/impl/NBEventServiceImpl.java b/dspace-api/src/main/java/org/dspace/app/nbevent/service/impl/NBEventServiceImpl.java index 03fbde444ae6..84f02474ec27 100644 --- a/dspace-api/src/main/java/org/dspace/app/nbevent/service/impl/NBEventServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/app/nbevent/service/impl/NBEventServiceImpl.java @@ -21,7 +21,6 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.json.JsonMapper; import org.apache.commons.lang3.ArrayUtils; -import org.apache.log4j.Logger; import org.apache.solr.client.solrj.SolrClient; import org.apache.solr.client.solrj.SolrQuery; import org.apache.solr.client.solrj.SolrQuery.ORDER; @@ -55,8 +54,6 @@ */ public class NBEventServiceImpl implements NBEventService { - private static final Logger log = Logger.getLogger(NBEventServiceImpl.class); - @Autowired(required = true) protected ConfigurationService configurationService; diff --git a/dspace-api/src/main/java/org/dspace/content/NBEvent.java b/dspace-api/src/main/java/org/dspace/content/NBEvent.java index e99fbaefa1b6..a029d1f3e3a8 100644 --- a/dspace-api/src/main/java/org/dspace/content/NBEvent.java +++ b/dspace-api/src/main/java/org/dspace/content/NBEvent.java @@ -14,8 +14,8 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import org.dspace.app.nbevent.RawJsonDeserializer; -import org.dspace.app.nbevent.service.dto.NBMessage; -import org.dspace.app.nbevent.service.dto.OpenaireMessage; +import org.dspace.app.nbevent.service.dto.NBMessageDTO; +import org.dspace.app.nbevent.service.dto.OpenaireMessageDTO; /** * This class represent the notification broker data as loaded in our solr @@ -192,10 +192,10 @@ private void computedEventId() throws NoSuchAlgorithmException, UnsupportedEncod } - public Class getMessageDtoClass() { + public Class getMessageDtoClass() { switch (getSource()) { case OPENAIRE_SOURCE: - return OpenaireMessage.class; + return OpenaireMessageDTO.class; default: throw new IllegalArgumentException("Unknown event's source: " + getSource()); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/NBEventConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/NBEventConverter.java index 21bbdfff297d..1acbbf51bbb9 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/NBEventConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/NBEventConverter.java @@ -13,8 +13,8 @@ import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.json.JsonMapper; -import org.dspace.app.nbevent.service.dto.NBMessage; -import org.dspace.app.nbevent.service.dto.OpenaireMessage; +import org.dspace.app.nbevent.service.dto.NBMessageDTO; +import org.dspace.app.nbevent.service.dto.OpenaireMessageDTO; import org.dspace.app.rest.model.NBEventMessageRest; import org.dspace.app.rest.model.NBEventRest; import org.dspace.app.rest.model.OpenaireNBEventMessageRest; @@ -61,9 +61,9 @@ public NBEventRest convert(NBEvent modelObject, Projection projection) { return rest; } - private NBEventMessageRest convertMessage(NBMessage dto) { - if (dto instanceof OpenaireMessage) { - OpenaireMessage openaireDto = (OpenaireMessage) dto; + private NBEventMessageRest convertMessage(NBMessageDTO dto) { + if (dto instanceof OpenaireMessageDTO) { + OpenaireMessageDTO openaireDto = (OpenaireMessageDTO) dto; OpenaireNBEventMessageRest message = new OpenaireNBEventMessageRest(); message.setAbstractValue(openaireDto.getAbstracts()); message.setOpenaireId(openaireDto.getOpenaireId()); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/NBEventMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/NBEventMatcher.java index a09d115359d3..f0a0a1219430 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/NBEventMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/NBEventMatcher.java @@ -18,7 +18,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.json.JsonMapper; import org.apache.commons.lang3.StringUtils; -import org.dspace.app.nbevent.service.dto.OpenaireMessage; +import org.dspace.app.nbevent.service.dto.OpenaireMessageDTO; import org.dspace.content.NBEvent; import org.hamcrest.Matcher; import org.hamcrest.Matchers; @@ -56,7 +56,7 @@ public static Matcher matchNBEventEntry(NBEvent event) { hasJsonPath("$.status", Matchers.equalToIgnoringCase(event.getStatus())), hasJsonPath("$.message", matchMessage(event.getTopic(), jsonMapper.readValue(event.getMessage(), - OpenaireMessage.class))), + OpenaireMessageDTO.class))), hasJsonPath("$._links.target.href", Matchers.endsWith(event.getEventId() + "/target")), hasJsonPath("$._links.related.href", Matchers.endsWith(event.getEventId() + "/related")), hasJsonPath("$._links.topic.href", Matchers.endsWith(event.getEventId() + "/topic")), @@ -66,7 +66,7 @@ public static Matcher matchNBEventEntry(NBEvent event) { } } - private static Matcher matchMessage(String topic, OpenaireMessage message) { + private static Matcher matchMessage(String topic, OpenaireMessageDTO message) { if (StringUtils.endsWith(topic, "/ABSTRACT")) { return allOf(hasJsonPath("$.abstract", is(message.getAbstracts()))); } else if (StringUtils.endsWith(topic, "/PID")) { diff --git a/dspace/config/modules/oaire-nbevents.cfg b/dspace/config/modules/oaire-nbevents.cfg index 68baec3d1de0..993a637a18d4 100644 --- a/dspace/config/modules/oaire-nbevents.cfg +++ b/dspace/config/modules/oaire-nbevents.cfg @@ -5,8 +5,8 @@ #---------------------------------------------------------------# oaire-nbevents.solr.server = ${solr.server}/${solr.multicorePrefix}nbevent # A POST to these url(s) will be done to notify oaire of decision taken for each nbevents -oaire-nbevents.acknowledge-url = https://beta.api-broker.openaire.eu/feedback/events -#oaire-nbevents.acknowledge-url = +openaire-nbevents.acknowledge-url = https://beta.api-broker.openaire.eu/feedback/events +#openaire-nbevents.acknowledge-url = oaire-nbevents.import.topic = ENRICH/MISSING/ABSTRACT oaire-nbevents.import.topic = ENRICH/MISSING/PID oaire-nbevents.import.topic = ENRICH/MORE/PID diff --git a/dspace/config/spring/api/nbevents.xml b/dspace/config/spring/api/nbevents.xml index 34dca93ebbb3..8cb039c39fea 100644 --- a/dspace/config/spring/api/nbevents.xml +++ b/dspace/config/spring/api/nbevents.xml @@ -29,7 +29,7 @@ - + @@ -47,10 +47,10 @@ - + - + + @@ -29,6 +29,8 @@ + @@ -50,6 +52,7 @@ + diff --git a/dspace/solr/nbevent/conf/admin-extra.html b/dspace/solr/nbevent/conf/admin-extra.html deleted file mode 100644 index aa739da862cf..000000000000 --- a/dspace/solr/nbevent/conf/admin-extra.html +++ /dev/null @@ -1,31 +0,0 @@ - - - diff --git a/dspace/solr/nbevent/conf/elevate.xml b/dspace/solr/nbevent/conf/elevate.xml deleted file mode 100644 index 7630ebe20fb9..000000000000 --- a/dspace/solr/nbevent/conf/elevate.xml +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/dspace/solr/nbevent/conf/schema.xml b/dspace/solr/nbevent/conf/schema.xml index 338fbdcdcd6b..68eb79afd0d4 100644 --- a/dspace/solr/nbevent/conf/schema.xml +++ b/dspace/solr/nbevent/conf/schema.xml @@ -16,221 +16,55 @@ limitations under the License. --> - - - - - + - - + - - - - - - - - - - - - - - - - - - - - - + @@ -270,9 +102,6 @@ - - - @@ -292,10 +121,6 @@ - - @@ -316,44 +141,14 @@ - - - - - + - - - + - + @@ -370,22 +165,10 @@ - - @@ -393,16 +176,8 @@ - - - - @@ -427,7 +200,6 @@ - @@ -451,10 +223,6 @@ - @@ -483,32 +251,7 @@ - - + @@ -532,14 +275,6 @@ - event_id - - - - + diff --git a/dspace/solr/nbevent/conf/scripts.conf b/dspace/solr/nbevent/conf/scripts.conf deleted file mode 100644 index f58b262ae0c8..000000000000 --- a/dspace/solr/nbevent/conf/scripts.conf +++ /dev/null @@ -1,24 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. -# The ASF licenses this file to You under the Apache License, Version 2.0 -# (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -user= -solr_hostname=localhost -solr_port=8983 -rsyncd_port=18983 -data_dir= -webapp_name=solr -master_host= -master_data_dir= -master_status_dir= diff --git a/dspace/solr/nbevent/conf/solrconfig.xml b/dspace/solr/nbevent/conf/solrconfig.xml index a4cfbed4a989..0565e56df4de 100644 --- a/dspace/solr/nbevent/conf/solrconfig.xml +++ b/dspace/solr/nbevent/conf/solrconfig.xml @@ -17,1927 +17,105 @@ --> - - - - 7.7.2 - - - - - - - - - - - - - - - ${solr.data.dir:} - - - - - - - - - - - - - - - - - - - - - - - - - 32 - 1000 - - - - - - - - - - - - ${solr.lock.type:native} - - - - - - - - - - - - - - - - - - - - - - true - - - - - - - - - - - - - - - - ${solr.ulog.dir:} - - - - - 10000 - ${solr.autoCommit.maxTime:10000} - false - - - - - - ${solr.autoSoftCommit.maxTime:1000} - - - - - - - - - - - - - - - - - - - - 1024 - - - - - - - - - 8.8.1 + + ${solr.data.dir:} + + + + + + + + + + 32 + 1000 + ${solr.lock.type:native} + + false + + + + + + + 10000 + ${solr.autoCommit.maxTime:10000} + true + + + + ${solr.autoSoftCommit.maxTime:-1} + + + + + + ${solr.max.booleanClauses:1024} + + + - - - - - - - - - - - - - true - - - - - - 20 - - - 200 - - - - - - - - - - - - static firstSearcher warming in solrconfig.xml - - - - - - false - - - 2 - - - - - - - - + - Set HTTP caching related parameters (for proxy caches and clients). - - The options below instruct Solr not to output any HTTP Caching - related headers - --> - - - - - - - - - - - - + + + + true + 20 + 200 + false + 2 + + + + + + + + + + + explicit 10 event_id - - - - - - - - - - - explicit - json - true - event_id - - - - - - - - true - json - true - - - - - - - - explicit - - - velocity - browse - layout - Solritas - - - edismax - - text^0.5 features^1.0 name^1.2 sku^1.5 event_id^10.0 manu^1.1 cat^1.4 - title^10.0 description^5.0 keywords^5.0 author^2.0 resourcename^1.0 - - event_id - 100% - *:* - 10 - *,score - - - text^0.5 features^1.0 name^1.2 sku^1.5 event_id^10.0 manu^1.1 cat^1.4 - title^10.0 description^5.0 keywords^5.0 author^2.0 resourcename^1.0 - - text,features,name,sku,event_id,manu,cat,title,description,keywords,author,resourcename - 3 - - - on - cat - manu_exact - content_type - author_s - ipod - GB - 1 - cat,inStock - after - price - 0 - 600 - 50 - popularity - 0 - 10 - 3 - manufacturedate_dt - NOW/YEAR-10YEARS - NOW - +1YEAR - before - after + + - - on - content features title name - html - <b> - </b> - 0 - title - 0 - name - 3 - 200 - content - 750 - - - on - false - 5 - 2 - 5 - true - true - 5 - 3 - - - - - spellcheck - - - - - - - - - - - - - - - application/json - - - - - - - application/csv - - - - - - - - true - ignored_ - - - true - links - ignored_ - - - - - - - - - - - - - - - - - - - - - - solrpingquery - - - all - - - - - - - - - explicit - true - - - - - - - - - - - - - - - - textSpell - - - default - name - ./spellchecker - - - - - - - - - - - - false - - false - - 1 - - - spellcheck - - - - - - - - true - - - tvComponent - - - - - - - - - text_general - - - - - - default - event_id - solr.DirectSolrSpellChecker - - internal - - 0.5 - - 2 - - 1 - - 5 - - 4 - - 0.01 - - - - - - wordbreak - solr.WordBreakSolrSpellChecker - name - true - true - 10 - - - - - - - - - - - - - - - - event_id - - default - wordbreak - on - true - 10 - 5 - 5 - true - true - 10 - 5 - - - spellcheck - - - - - - - - - - event_id - true - - - tvComponent - - - - - - - - - default - - - org.carrot2.clustering.lingo.LingoClusteringAlgorithm - - - 20 - - - clustering/carrot2 - - - ENGLISH - - - stc - org.carrot2.clustering.stc.STCClusteringAlgorithm - - - - - - - true - default - true - - name - event_id - - features - - true - - - - false - - edismax - - text^0.5 features^1.0 name^1.2 sku^1.5 event_id^10.0 manu^1.1 cat^1.4 - - *:* - 10 - *,score - - - clustering - - - - - - - - - - true - false - - - terms - - - - - - - - string - elevate.xml - - - - - - explicit - event_id - - - elevator - - - - - - - - - - - 100 - - - - - - - - 70 - - 0.5 - - [-\w ,/\n\"']{20,200} - - - - - - - ]]> - ]]> - - - - - - - - - - - - - - - - - - - - - - - - ,, - ,, - ,, - ,, - ,]]> - ]]> - - - - - - 10 - .,!? - - - - + - - WORD - - - en - US + application/json - - - - - - - - - - - - - - - - - - - - - - - text/plain; charset=UTF-8 - - - - - - - - - 5 - - - - - - - - - - - - - - - - - - *:* - + + diff --git a/dspace/solr/nbevent/conf/spellings.txt b/dspace/solr/nbevent/conf/spellings.txt deleted file mode 100644 index d7ede6f5611d..000000000000 --- a/dspace/solr/nbevent/conf/spellings.txt +++ /dev/null @@ -1,2 +0,0 @@ -pizza -history \ No newline at end of file From 32c6300afaa3122d45c543f5a2cc1422644a2f4f Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Tue, 26 Apr 2022 18:10:52 +0200 Subject: [PATCH 010/510] [CST-5249] Reverted related item check --- .../org/dspace/app/nbevent/NBOpenaireMetadataMapAction.java | 4 ---- .../dspace/app/nbevent/NBOpenaireSimpleMetadataAction.java | 5 ----- .../main/java/org/dspace/app/rest/NBEventRestController.java | 2 ++ 3 files changed, 2 insertions(+), 9 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/nbevent/NBOpenaireMetadataMapAction.java b/dspace-api/src/main/java/org/dspace/app/nbevent/NBOpenaireMetadataMapAction.java index c5909de962a2..216f44a2d502 100644 --- a/dspace-api/src/main/java/org/dspace/app/nbevent/NBOpenaireMetadataMapAction.java +++ b/dspace-api/src/main/java/org/dspace/app/nbevent/NBOpenaireMetadataMapAction.java @@ -51,10 +51,6 @@ public void setTypes(Map types) { @Override public void applyCorrection(Context context, Item item, Item relatedItem, NBMessageDTO message) { - if (relatedItem != null) { - throw new IllegalArgumentException("NBOpenaireMetadataMapAction does not support related item"); - } - if (!(message instanceof OpenaireMessageDTO)) { throw new IllegalArgumentException("Unsupported message type: " + message.getClass()); } diff --git a/dspace-api/src/main/java/org/dspace/app/nbevent/NBOpenaireSimpleMetadataAction.java b/dspace-api/src/main/java/org/dspace/app/nbevent/NBOpenaireSimpleMetadataAction.java index 8666353f8a33..f08b3f7db4f4 100644 --- a/dspace-api/src/main/java/org/dspace/app/nbevent/NBOpenaireSimpleMetadataAction.java +++ b/dspace-api/src/main/java/org/dspace/app/nbevent/NBOpenaireSimpleMetadataAction.java @@ -52,11 +52,6 @@ public void setMetadata(String metadata) { @Override public void applyCorrection(Context context, Item item, Item relatedItem, NBMessageDTO message) { - - if (relatedItem != null) { - throw new IllegalArgumentException("NBOpenaireSimpleMetadataAction does not support related item"); - } - try { itemService.addMetadata(context, item, metadataSchema, metadataElement, metadataQualifier, null, ((OpenaireMessageDTO) message).getAbstracts()); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/NBEventRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/NBEventRestController.java index f7bb4b2e48e7..30875080a03d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/NBEventRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/NBEventRestController.java @@ -88,6 +88,8 @@ public ResponseEntity> postRelatedItem(@PathVariable(name if (nbevent.getRelated() != null) { throw new UnprocessableEntityException("The nb event with ID: " + nbeventId + " already has " + "a related item"); + } else if (!StringUtils.endsWith(nbevent.getTopic(), "/PROJECT")) { + return ControllerUtils.toEmptyResponse(HttpStatus.BAD_REQUEST); } Item relatedItem = itemService.find(context, relatedItemUUID); From 7013673318fd18ba6bd0c5e00bac1dfa66359cb1 Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Thu, 26 May 2022 12:41:15 +0200 Subject: [PATCH 011/510] [CST-5249] Fixed compilation error --- .../src/main/java/org/dspace/app/rest/NBEventRestController.java | 1 + 1 file changed, 1 insertion(+) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/NBEventRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/NBEventRestController.java index 30875080a03d..10618288aa2c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/NBEventRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/NBEventRestController.java @@ -15,6 +15,7 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.apache.commons.lang3.StringUtils; import org.dspace.app.nbevent.service.NBEventService; import org.dspace.app.rest.converter.ConverterService; import org.dspace.app.rest.exception.UnprocessableEntityException; From 7259393600140adf7e983cd87989078bc2d1b835 Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Mon, 4 Jul 2022 16:40:15 +0200 Subject: [PATCH 012/510] [CST-5249] Renamed NB with QA --- .../content/{NBEvent.java => QAEvent.java} | 16 +- ...ntProcessed.java => QAEventProcessed.java} | 8 +- .../OpenaireEventsCliScriptConfiguration.java | 2 +- .../OpenaireEventsRunnable.java | 34 +- .../OpenaireEventsRunnableCli.java | 4 +- .../OpenaireEventsScriptConfiguration.java | 6 +- .../QAEntityOpenaireMetadataAction.java} | 26 +- .../QAEventActionService.java} | 20 +- .../QAEventActionServiceImpl.java} | 52 +-- .../QAEventsDeleteCascadeConsumer.java} | 14 +- .../QAOpenaireMetadataMapAction.java} | 12 +- .../QAOpenaireSimpleMetadataAction.java} | 12 +- .../NBSource.java => qaevent/QASource.java} | 6 +- .../NBTopic.java => qaevent/QATopic.java} | 6 +- .../QualityAssuranceAction.java} | 8 +- .../dao/QAEventsDao.java} | 12 +- .../dao/impl/QAEventsDaoImpl.java} | 28 +- .../service/QAEventService.java} | 28 +- .../service/dto/OpenaireMessageDTO.java | 6 +- .../service/dto/QAMessageDTO.java} | 8 +- .../service/impl/QAEventServiceImpl.java} | 74 ++--- ...=> V7.3_2022.02.17__qaevent_processed.sql} | 8 +- .../V7.3_2022.02.17__qaevent_processed.sql} | 10 +- .../V7.3_2022.02.17__qaevent_processed.sql} | 10 +- .../test/data/dspaceFolder/config/local.cfg | 4 +- .../config/spring/api/solr-services.xml | 6 +- .../org/dspace/builder/AbstractBuilder.java | 8 +- ...BEventBuilder.java => QAEventBuilder.java} | 58 ++-- .../MockQAEventService.java} | 10 +- ...roller.java => QAEventRestController.java} | 58 ++-- ...ntConverter.java => QAEventConverter.java} | 34 +- ...eConverter.java => QASourceConverter.java} | 18 +- ...icConverter.java => QATopicConverter.java} | 18 +- ...t.java => OpenaireQAEventMessageRest.java} | 4 +- ...ssageRest.java => QAEventMessageRest.java} | 4 +- .../{NBEventRest.java => QAEventRest.java} | 12 +- .../{NBSourceRest.java => QASourceRest.java} | 6 +- .../{NBTopicRest.java => QATopicRest.java} | 6 +- ...ventResource.java => QAEventResource.java} | 10 +- ...rceResource.java => QASourceResource.java} | 10 +- ...opicResource.java => QATopicResource.java} | 10 +- .../repository/NBTopicRestRepository.java | 75 ----- ...java => QAEventRelatedLinkRepository.java} | 30 +- ...sitory.java => QAEventRestRepository.java} | 66 ++-- ....java => QAEventTargetLinkRepository.java} | 28 +- ...y.java => QAEventTopicLinkRepository.java} | 34 +- ...itory.java => QASourceRestRepository.java} | 34 +- .../repository/QATopicRestRepository.java | 75 +++++ ...ava => QAEventStatusReplaceOperation.java} | 36 +- ...ryIT.java => QAEventRestRepositoryIT.java} | 310 +++++++++--------- ...yIT.java => QASourceRestRepositoryIT.java} | 48 +-- ...ryIT.java => QATopicRestRepositoryIT.java} | 122 +++---- ...BEventMatcher.java => QAEventMatcher.java} | 18 +- ...ourceMatcher.java => QASourceMatcher.java} | 16 +- ...BTopicMatcher.java => QATopicMatcher.java} | 16 +- dspace/config/dspace.cfg | 10 +- dspace/config/hibernate.cfg.xml | 2 +- .../modules/{nbevents.cfg => qaevents.cfg} | 22 +- .../spring/api/{nbevents.xml => qaevents.xml} | 18 +- dspace/config/spring/api/scripts.xml | 6 +- dspace/config/spring/api/solr-services.xml | 4 +- dspace/config/spring/rest/scripts.xml | 6 +- .../{nbevent => qaevent}/conf/protwords.txt | 0 .../solr/{nbevent => qaevent}/conf/schema.xml | 0 .../{nbevent => qaevent}/conf/solrconfig.xml | 2 +- .../{nbevent => qaevent}/conf/stopwords.txt | 0 .../{nbevent => qaevent}/conf/synonyms.txt | 0 .../solr/{nbevent => qaevent}/core.properties | 0 68 files changed, 830 insertions(+), 834 deletions(-) rename dspace-api/src/main/java/org/dspace/content/{NBEvent.java => QAEvent.java} (92%) rename dspace-api/src/main/java/org/dspace/content/{NBEventProcessed.java => QAEventProcessed.java} (91%) rename dspace-api/src/main/java/org/dspace/{app/nbevent => qaevent}/OpenaireEventsCliScriptConfiguration.java (96%) rename dspace-api/src/main/java/org/dspace/{app/nbevent => qaevent}/OpenaireEventsRunnable.java (83%) rename dspace-api/src/main/java/org/dspace/{app/nbevent => qaevent}/OpenaireEventsRunnableCli.java (94%) rename dspace-api/src/main/java/org/dspace/{app/nbevent => qaevent}/OpenaireEventsScriptConfiguration.java (93%) rename dspace-api/src/main/java/org/dspace/{app/nbevent/NBEntityOpenaireMetadataAction.java => qaevent/QAEntityOpenaireMetadataAction.java} (88%) rename dspace-api/src/main/java/org/dspace/{app/nbevent/NBEventActionService.java => qaevent/QAEventActionService.java} (60%) rename dspace-api/src/main/java/org/dspace/{app/nbevent/NBEventActionServiceImpl.java => qaevent/QAEventActionServiceImpl.java} (67%) rename dspace-api/src/main/java/org/dspace/{app/nbevent/NBEventsDeleteCascadeConsumer.java => qaevent/QAEventsDeleteCascadeConsumer.java} (71%) rename dspace-api/src/main/java/org/dspace/{app/nbevent/NBOpenaireMetadataMapAction.java => qaevent/QAOpenaireMetadataMapAction.java} (87%) rename dspace-api/src/main/java/org/dspace/{app/nbevent/NBOpenaireSimpleMetadataAction.java => qaevent/QAOpenaireSimpleMetadataAction.java} (82%) rename dspace-api/src/main/java/org/dspace/{app/nbevent/NBSource.java => qaevent/QASource.java} (87%) rename dspace-api/src/main/java/org/dspace/{app/nbevent/NBTopic.java => qaevent/QATopic.java} (87%) rename dspace-api/src/main/java/org/dspace/{app/nbevent/NBAction.java => qaevent/QualityAssuranceAction.java} (82%) rename dspace-api/src/main/java/org/dspace/{app/nbevent/dao/NBEventsDao.java => qaevent/dao/QAEventsDao.java} (86%) rename dspace-api/src/main/java/org/dspace/{app/nbevent/dao/impl/NBEventsDaoImpl.java => qaevent/dao/impl/QAEventsDaoImpl.java} (62%) rename dspace-api/src/main/java/org/dspace/{app/nbevent/service/NBEventService.java => qaevent/service/QAEventService.java} (82%) rename dspace-api/src/main/java/org/dspace/{app/nbevent => qaevent}/service/dto/OpenaireMessageDTO.java (95%) rename dspace-api/src/main/java/org/dspace/{app/nbevent/service/dto/NBMessageDTO.java => qaevent/service/dto/QAMessageDTO.java} (73%) rename dspace-api/src/main/java/org/dspace/{app/nbevent/service/impl/NBEventServiceImpl.java => qaevent/service/impl/QAEventServiceImpl.java} (87%) rename dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/{V7.3_2022.02.17__nbevent_processed.sql => V7.3_2022.02.17__qaevent_processed.sql} (65%) rename dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/{postgres/V7.3_2022.02.17__nbevent_processed.sql => oracle/V7.3_2022.02.17__qaevent_processed.sql} (66%) rename dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/{oracle/V7.3_2022.02.17__nbevent_processed.sql => postgres/V7.3_2022.02.17__qaevent_processed.sql} (66%) rename dspace-api/src/test/java/org/dspace/builder/{NBEventBuilder.java => QAEventBuilder.java} (58%) rename dspace-api/src/test/java/org/dspace/{app/nbevent/MockNBEventService.java => qaevent/MockQAEventService.java} (76%) rename dspace-server-webapp/src/main/java/org/dspace/app/rest/{NBEventRestController.java => QAEventRestController.java} (75%) rename dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/{NBEventConverter.java => QAEventConverter.java} (76%) rename dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/{NBSourceConverter.java => QASourceConverter.java} (66%) rename dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/{NBTopicConverter.java => QATopicConverter.java} (68%) rename dspace-server-webapp/src/main/java/org/dspace/app/rest/model/{OpenaireNBEventMessageRest.java => OpenaireQAEventMessageRest.java} (94%) rename dspace-server-webapp/src/main/java/org/dspace/app/rest/model/{NBEventMessageRest.java => QAEventMessageRest.java} (88%) rename dspace-server-webapp/src/main/java/org/dspace/app/rest/model/{NBEventRest.java => QAEventRest.java} (90%) rename dspace-server-webapp/src/main/java/org/dspace/app/rest/model/{NBSourceRest.java => QASourceRest.java} (88%) rename dspace-server-webapp/src/main/java/org/dspace/app/rest/model/{NBTopicRest.java => QATopicRest.java} (89%) rename dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/{NBEventResource.java => QAEventResource.java} (67%) rename dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/{NBSourceResource.java => QASourceResource.java} (67%) rename dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/{NBTopicResource.java => QATopicResource.java} (67%) delete mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NBTopicRestRepository.java rename dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/{NBEventRelatedLinkRepository.java => QAEventRelatedLinkRepository.java} (72%) rename dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/{NBEventRestRepository.java => QAEventRestRepository.java} (62%) rename dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/{NBEventTargetLinkRepository.java => QAEventTargetLinkRepository.java} (70%) rename dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/{NBEventTopicLinkRepository.java => QAEventTopicLinkRepository.java} (61%) rename dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/{NBSourceRestRepository.java => QASourceRestRepository.java} (50%) create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QATopicRestRepository.java rename dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/{NBEventStatusReplaceOperation.java => QAEventStatusReplaceOperation.java} (60%) rename dspace-server-webapp/src/test/java/org/dspace/app/rest/{NBEventRestRepositoryIT.java => QAEventRestRepositoryIT.java} (80%) rename dspace-server-webapp/src/test/java/org/dspace/app/rest/{NBSourceRestRepositoryIT.java => QASourceRestRepositoryIT.java} (78%) rename dspace-server-webapp/src/test/java/org/dspace/app/rest/{NBTopicRestRepositoryIT.java => QATopicRestRepositoryIT.java} (73%) rename dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/{NBEventMatcher.java => QAEventMatcher.java} (88%) rename dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/{NBSourceMatcher.java => QASourceMatcher.java} (66%) rename dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/{NBTopicMatcher.java => QATopicMatcher.java} (69%) rename dspace/config/modules/{nbevents.cfg => qaevents.cfg} (51%) rename dspace/config/spring/api/{nbevents.xml => qaevents.xml} (78%) rename dspace/solr/{nbevent => qaevent}/conf/protwords.txt (100%) rename dspace/solr/{nbevent => qaevent}/conf/schema.xml (100%) rename dspace/solr/{nbevent => qaevent}/conf/solrconfig.xml (99%) rename dspace/solr/{nbevent => qaevent}/conf/stopwords.txt (100%) rename dspace/solr/{nbevent => qaevent}/conf/synonyms.txt (100%) rename dspace/solr/{nbevent => qaevent}/core.properties (100%) diff --git a/dspace-api/src/main/java/org/dspace/content/NBEvent.java b/dspace-api/src/main/java/org/dspace/content/QAEvent.java similarity index 92% rename from dspace-api/src/main/java/org/dspace/content/NBEvent.java rename to dspace-api/src/main/java/org/dspace/content/QAEvent.java index b53aef2815c4..64f8a1202636 100644 --- a/dspace-api/src/main/java/org/dspace/content/NBEvent.java +++ b/dspace-api/src/main/java/org/dspace/content/QAEvent.java @@ -13,16 +13,16 @@ import java.util.Date; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import org.dspace.app.nbevent.service.dto.NBMessageDTO; -import org.dspace.app.nbevent.service.dto.OpenaireMessageDTO; +import org.dspace.qaevent.service.dto.OpenaireMessageDTO; +import org.dspace.qaevent.service.dto.QAMessageDTO; import org.dspace.util.RawJsonDeserializer; /** - * This class represent the notification broker data as loaded in our solr - * nbevent core + * This class represent the Quality Assurance broker data as loaded in our solr + * qaevent core * */ -public class NBEvent { +public class QAEvent { public static final char[] HEX_DIGITS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; public static final String ACCEPTED = "accepted"; @@ -54,10 +54,10 @@ public class NBEvent { private String status = "PENDING"; - public NBEvent() { + public QAEvent() { } - public NBEvent(String source, String originalId, String target, String title, + public QAEvent(String source, String originalId, String target, String title, String topic, double trust, String message, Date lastUpdate) { super(); this.source = source; @@ -193,7 +193,7 @@ private void computedEventId() throws NoSuchAlgorithmException, UnsupportedEncod } - public Class getMessageDtoClass() { + public Class getMessageDtoClass() { switch (getSource()) { case OPENAIRE_SOURCE: return OpenaireMessageDTO.class; diff --git a/dspace-api/src/main/java/org/dspace/content/NBEventProcessed.java b/dspace-api/src/main/java/org/dspace/content/QAEventProcessed.java similarity index 91% rename from dspace-api/src/main/java/org/dspace/content/NBEventProcessed.java rename to dspace-api/src/main/java/org/dspace/content/QAEventProcessed.java index 62f8222e2478..3657c2fdc458 100644 --- a/dspace-api/src/main/java/org/dspace/content/NBEventProcessed.java +++ b/dspace-api/src/main/java/org/dspace/content/QAEventProcessed.java @@ -26,17 +26,17 @@ * */ @Entity -@Table(name = "nbevent_processed") -public class NBEventProcessed implements Serializable { +@Table(name = "qaevent_processed") +public class QAEventProcessed implements Serializable { private static final long serialVersionUID = 3427340199132007814L; @Id - @Column(name = "nbevent_id") + @Column(name = "qaevent_id") private String eventId; @Temporal(TemporalType.TIMESTAMP) - @Column(name = "nbevent_timestamp") + @Column(name = "qaevent_timestamp") private Date eventTimestamp; @JoinColumn(name = "eperson_uuid") diff --git a/dspace-api/src/main/java/org/dspace/app/nbevent/OpenaireEventsCliScriptConfiguration.java b/dspace-api/src/main/java/org/dspace/qaevent/OpenaireEventsCliScriptConfiguration.java similarity index 96% rename from dspace-api/src/main/java/org/dspace/app/nbevent/OpenaireEventsCliScriptConfiguration.java rename to dspace-api/src/main/java/org/dspace/qaevent/OpenaireEventsCliScriptConfiguration.java index 5263bc559ba0..bad7ec5d5b62 100644 --- a/dspace-api/src/main/java/org/dspace/app/nbevent/OpenaireEventsCliScriptConfiguration.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/OpenaireEventsCliScriptConfiguration.java @@ -5,7 +5,7 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.nbevent; +package org.dspace.qaevent; import org.apache.commons.cli.Options; diff --git a/dspace-api/src/main/java/org/dspace/app/nbevent/OpenaireEventsRunnable.java b/dspace-api/src/main/java/org/dspace/qaevent/OpenaireEventsRunnable.java similarity index 83% rename from dspace-api/src/main/java/org/dspace/app/nbevent/OpenaireEventsRunnable.java rename to dspace-api/src/main/java/org/dspace/qaevent/OpenaireEventsRunnable.java index e2b66cd7499b..6a0f5cf8e5f2 100644 --- a/dspace-api/src/main/java/org/dspace/app/nbevent/OpenaireEventsRunnable.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/OpenaireEventsRunnable.java @@ -5,7 +5,7 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.nbevent; +package org.dspace.qaevent; import static org.apache.commons.lang3.exception.ExceptionUtils.getRootCauseMessage; @@ -21,11 +21,11 @@ import com.fasterxml.jackson.databind.json.JsonMapper; import org.apache.commons.cli.ParseException; import org.apache.commons.lang3.StringUtils; -import org.dspace.app.nbevent.service.NBEventService; -import org.dspace.content.NBEvent; +import org.dspace.content.QAEvent; import org.dspace.core.Context; import org.dspace.eperson.EPerson; import org.dspace.eperson.factory.EPersonServiceFactory; +import org.dspace.qaevent.service.QAEventService; import org.dspace.scripts.DSpaceRunnable; import org.dspace.services.ConfigurationService; import org.dspace.utils.DSpace; @@ -33,7 +33,7 @@ import org.slf4j.LoggerFactory; /** - * Implementation of {@link DSpaceRunnable} to perfom a NBEvents import from a + * Implementation of {@link DSpaceRunnable} to perfom a QAEvents import from a * json file. The JSON file contains an array of JSON Events, where each event * has the following structure * @@ -62,7 +62,7 @@ public class OpenaireEventsRunnable extends DSpaceRunnable entries; + protected List entries; protected Context context; @@ -86,17 +86,17 @@ public OpenaireEventsScriptConfiguration getScriptConfiguration() { public void setup() throws ParseException { DSpace dspace = new DSpace(); - nbEventService = dspace.getSingletonService(NBEventService.class); - if (nbEventService == null) { - LOGGER.error("nbEventService is NULL. Error in spring configuration"); + qaEventService = dspace.getSingletonService(QAEventService.class); + if (qaEventService == null) { + LOGGER.error("qaEventService is NULL. Error in spring configuration"); throw new IllegalStateException(); } else { - LOGGER.debug("nbEventService correctly loaded"); + LOGGER.debug("qaEventService correctly loaded"); } configurationService = dspace.getConfigurationService(); - topicsToImport = configurationService.getArrayProperty("nbevents.openaire.import.topic"); + topicsToImport = configurationService.getArrayProperty("qaevents.openaire.import.topic"); fileLocation = commandLine.getOptionValue("f"); @@ -115,21 +115,21 @@ public void internalRun() throws Exception { ObjectMapper jsonMapper = new JsonMapper(); jsonMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); try { - this.entries = jsonMapper.readValue(getNBEventsInputStream(), new TypeReference>() { + this.entries = jsonMapper.readValue(getQAEventsInputStream(), new TypeReference>() { }); } catch (IOException e) { LOGGER.error("File is not found or not readable: " + fileLocation, e); System.exit(1); } - for (NBEvent entry : entries) { - entry.setSource(NBEvent.OPENAIRE_SOURCE); + for (QAEvent entry : entries) { + entry.setSource(QAEvent.OPENAIRE_SOURCE); if (!StringUtils.equalsAny(entry.getTopic(), topicsToImport)) { - LOGGER.info("Skip event for topic " + entry.getTopic() + " is not allowed in the oaire-nbevents.cfg"); + LOGGER.info("Skip event for topic " + entry.getTopic() + " is not allowed in the oaire-qaevents.cfg"); continue; } try { - nbEventService.store(context, entry); + qaEventService.store(context, entry); } catch (RuntimeException e) { handler.logWarning(getRootCauseMessage(e)); } @@ -142,7 +142,7 @@ public void internalRun() throws Exception { * @return * @throws Exception */ - protected InputStream getNBEventsInputStream() throws Exception { + protected InputStream getQAEventsInputStream() throws Exception { this.assignCurrentUserInContext(); diff --git a/dspace-api/src/main/java/org/dspace/app/nbevent/OpenaireEventsRunnableCli.java b/dspace-api/src/main/java/org/dspace/qaevent/OpenaireEventsRunnableCli.java similarity index 94% rename from dspace-api/src/main/java/org/dspace/app/nbevent/OpenaireEventsRunnableCli.java rename to dspace-api/src/main/java/org/dspace/qaevent/OpenaireEventsRunnableCli.java index ef111733dabd..c8f260a072c4 100644 --- a/dspace-api/src/main/java/org/dspace/app/nbevent/OpenaireEventsRunnableCli.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/OpenaireEventsRunnableCli.java @@ -5,7 +5,7 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.nbevent; +package org.dspace.qaevent; import java.io.File; import java.io.FileInputStream; @@ -47,7 +47,7 @@ public void setup() throws ParseException { * Get the events input stream from a local file. */ @Override - protected InputStream getNBEventsInputStream() throws Exception { + protected InputStream getQAEventsInputStream() throws Exception { return new FileInputStream(new File(fileLocation)); } diff --git a/dspace-api/src/main/java/org/dspace/app/nbevent/OpenaireEventsScriptConfiguration.java b/dspace-api/src/main/java/org/dspace/qaevent/OpenaireEventsScriptConfiguration.java similarity index 93% rename from dspace-api/src/main/java/org/dspace/app/nbevent/OpenaireEventsScriptConfiguration.java rename to dspace-api/src/main/java/org/dspace/qaevent/OpenaireEventsScriptConfiguration.java index f075c5adfee0..7d47f8219ad2 100644 --- a/dspace-api/src/main/java/org/dspace/app/nbevent/OpenaireEventsScriptConfiguration.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/OpenaireEventsScriptConfiguration.java @@ -5,7 +5,7 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.nbevent; +package org.dspace.qaevent; import java.io.InputStream; import java.sql.SQLException; @@ -17,7 +17,7 @@ import org.springframework.beans.factory.annotation.Autowired; /** - * Extension of {@link ScriptConfiguration} to perfom a NBEvents import from + * Extension of {@link ScriptConfiguration} to perfom a QAEvents import from * file. * * @author Alessandro Martelli (alessandro.martelli at 4science.it) @@ -58,7 +58,7 @@ public Options getOptions() { if (options == null) { Options options = new Options(); - options.addOption("f", "file", true, "Import data from OpenAIRE notification broker files"); + options.addOption("f", "file", true, "Import data from OpenAIRE quality assurance broker files"); options.getOption("f").setType(InputStream.class); options.getOption("f").setRequired(true); diff --git a/dspace-api/src/main/java/org/dspace/app/nbevent/NBEntityOpenaireMetadataAction.java b/dspace-api/src/main/java/org/dspace/qaevent/QAEntityOpenaireMetadataAction.java similarity index 88% rename from dspace-api/src/main/java/org/dspace/app/nbevent/NBEntityOpenaireMetadataAction.java rename to dspace-api/src/main/java/org/dspace/qaevent/QAEntityOpenaireMetadataAction.java index 926160aa4851..c272df1cf45e 100644 --- a/dspace-api/src/main/java/org/dspace/app/nbevent/NBEntityOpenaireMetadataAction.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/QAEntityOpenaireMetadataAction.java @@ -5,14 +5,12 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.nbevent; +package org.dspace.qaevent; import java.sql.SQLException; import java.util.Map; import org.apache.commons.lang3.StringUtils; -import org.dspace.app.nbevent.service.dto.NBMessageDTO; -import org.dspace.app.nbevent.service.dto.OpenaireMessageDTO; import org.dspace.authorize.AuthorizeException; import org.dspace.content.Collection; import org.dspace.content.EntityType; @@ -28,16 +26,18 @@ import org.dspace.content.service.RelationshipTypeService; import org.dspace.content.service.WorkspaceItemService; import org.dspace.core.Context; +import org.dspace.qaevent.service.dto.OpenaireMessageDTO; +import org.dspace.qaevent.service.dto.QAMessageDTO; import org.springframework.beans.factory.annotation.Autowired; /** - * Implementation of {@link NBAction} that handle the relationship between the + * Implementation of {@link QualityAssuranceAction} that handle the relationship between the * item to correct and a related item. * * @author Andrea Bollini (andrea.bollini at 4science.it) * */ -public class NBEntityOpenaireMetadataAction implements NBAction { +public class QAEntityOpenaireMetadataAction implements QualityAssuranceAction { private String relation; private String entityType; private Map entityMetadata; @@ -103,7 +103,7 @@ public void setEntityMetadata(Map entityMetadata) { } @Override - public void applyCorrection(Context context, Item item, Item relatedItem, NBMessageDTO message) { + public void applyCorrection(Context context, Item item, Item relatedItem, QAMessageDTO message) { try { if (relatedItem != null) { link(context, item, relatedItem); @@ -145,18 +145,18 @@ private void link(Context context, Item item, Item relatedItem) throws SQLExcept .filter(r -> StringUtils.equals(r.getRightwardType(), relation)).findFirst() .orElseThrow(() -> new IllegalStateException("No relationshipType named " + relation + " was found for the entity type " + entityType - + ". A proper configuration is required to use the NBEntitiyMetadataAction." + + ". A proper configuration is required to use the QAEntitiyMetadataAction." + " If you don't manage funding in your repository please skip this topic in" - + " the oaire-nbevents.cfg")); + + " the qaevents.cfg")); // Create the relationship - int leftPlace = relationshipService.findNextLeftPlaceByLeftItem(context, item); - int rightPlace = relationshipService.findNextRightPlaceByRightItem(context, relatedItem); - Relationship persistedRelationship = relationshipService.create(context, item, relatedItem, - relType, leftPlace, rightPlace); + Relationship persistedRelationship = relationshipService.create(context); + persistedRelationship.setRelationshipType(relType); + persistedRelationship.setLeftItem(item); + persistedRelationship.setRightItem(relatedItem); relationshipService.update(context, persistedRelationship); } - private String getValue(NBMessageDTO message, String key) { + private String getValue(QAMessageDTO message, String key) { if (!(message instanceof OpenaireMessageDTO)) { return null; } diff --git a/dspace-api/src/main/java/org/dspace/app/nbevent/NBEventActionService.java b/dspace-api/src/main/java/org/dspace/qaevent/QAEventActionService.java similarity index 60% rename from dspace-api/src/main/java/org/dspace/app/nbevent/NBEventActionService.java rename to dspace-api/src/main/java/org/dspace/qaevent/QAEventActionService.java index e6a29173849e..048e2fe77594 100644 --- a/dspace-api/src/main/java/org/dspace/app/nbevent/NBEventActionService.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/QAEventActionService.java @@ -5,41 +5,41 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.nbevent; +package org.dspace.qaevent; -import org.dspace.content.NBEvent; +import org.dspace.content.QAEvent; import org.dspace.core.Context; /** * Service that handle the actions that can be done related to an - * {@link NBEvent}. + * {@link QAEvent}. * * @author Andrea Bollini (andrea.bollini at 4science.it) * */ -public interface NBEventActionService { +public interface QAEventActionService { /** * Accept the given event. * * @param context the DSpace context - * @param nbevent the event to be accepted + * @param qaevent the event to be accepted */ - public void accept(Context context, NBEvent nbevent); + public void accept(Context context, QAEvent qaevent); /** * Discard the given event. * * @param context the DSpace context - * @param nbevent the event to be discarded + * @param qaevent the event to be discarded */ - public void discard(Context context, NBEvent nbevent); + public void discard(Context context, QAEvent qaevent); /** * Reject the given event. * * @param context the DSpace context - * @param nbevent the event to be rejected + * @param qaevent the event to be rejected */ - public void reject(Context context, NBEvent nbevent); + public void reject(Context context, QAEvent qaevent); } diff --git a/dspace-api/src/main/java/org/dspace/app/nbevent/NBEventActionServiceImpl.java b/dspace-api/src/main/java/org/dspace/qaevent/QAEventActionServiceImpl.java similarity index 67% rename from dspace-api/src/main/java/org/dspace/app/nbevent/NBEventActionServiceImpl.java rename to dspace-api/src/main/java/org/dspace/qaevent/QAEventActionServiceImpl.java index a14dcf5b2287..7bfb940cbbcf 100644 --- a/dspace-api/src/main/java/org/dspace/app/nbevent/NBEventActionServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/QAEventActionServiceImpl.java @@ -5,7 +5,7 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.nbevent; +package org.dspace.qaevent; import java.io.IOException; import java.sql.SQLException; @@ -24,27 +24,27 @@ import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.logging.log4j.Logger; -import org.dspace.app.nbevent.service.NBEventService; import org.dspace.content.Item; -import org.dspace.content.NBEvent; +import org.dspace.content.QAEvent; import org.dspace.content.service.ItemService; import org.dspace.core.Context; +import org.dspace.qaevent.service.QAEventService; import org.dspace.services.ConfigurationService; import org.springframework.beans.factory.annotation.Autowired; /** - * Implementation of {@link NBEventActionService}. + * Implementation of {@link QAEventActionService}. * * @author Andrea Bollini (andrea.bollini at 4science.it) * */ -public class NBEventActionServiceImpl implements NBEventActionService { - private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(NBEventActionServiceImpl.class); +public class QAEventActionServiceImpl implements QAEventActionService { + private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(QAEventActionServiceImpl.class); private ObjectMapper jsonMapper; @Autowired - private NBEventService nbEventService; + private QAEventService qaEventService; @Autowired private ItemService itemService; @@ -52,49 +52,49 @@ public class NBEventActionServiceImpl implements NBEventActionService { @Autowired private ConfigurationService configurationService; - private Map topicsToActions; + private Map topicsToActions; - public void setTopicsToActions(Map topicsToActions) { + public void setTopicsToActions(Map topicsToActions) { this.topicsToActions = topicsToActions; } - public Map getTopicsToActions() { + public Map getTopicsToActions() { return topicsToActions; } - public NBEventActionServiceImpl() { + public QAEventActionServiceImpl() { jsonMapper = new JsonMapper(); jsonMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); } @Override - public void accept(Context context, NBEvent nbevent) { + public void accept(Context context, QAEvent qaevent) { Item item = null; Item related = null; try { - item = itemService.find(context, UUID.fromString(nbevent.getTarget())); - if (nbevent.getRelated() != null) { - related = itemService.find(context, UUID.fromString(nbevent.getRelated())); + item = itemService.find(context, UUID.fromString(qaevent.getTarget())); + if (qaevent.getRelated() != null) { + related = itemService.find(context, UUID.fromString(qaevent.getRelated())); } - topicsToActions.get(nbevent.getTopic()).applyCorrection(context, item, related, - jsonMapper.readValue(nbevent.getMessage(), nbevent.getMessageDtoClass())); - nbEventService.deleteEventByEventId(nbevent.getEventId()); - makeAcknowledgement(nbevent.getEventId(), nbevent.getSource(), NBEvent.ACCEPTED); + topicsToActions.get(qaevent.getTopic()).applyCorrection(context, item, related, + jsonMapper.readValue(qaevent.getMessage(), qaevent.getMessageDtoClass())); + qaEventService.deleteEventByEventId(qaevent.getEventId()); + makeAcknowledgement(qaevent.getEventId(), qaevent.getSource(), QAEvent.ACCEPTED); } catch (SQLException | JsonProcessingException e) { throw new RuntimeException(e); } } @Override - public void discard(Context context, NBEvent nbevent) { - nbEventService.deleteEventByEventId(nbevent.getEventId()); - makeAcknowledgement(nbevent.getEventId(), nbevent.getSource(), NBEvent.DISCARDED); + public void discard(Context context, QAEvent qaevent) { + qaEventService.deleteEventByEventId(qaevent.getEventId()); + makeAcknowledgement(qaevent.getEventId(), qaevent.getSource(), QAEvent.DISCARDED); } @Override - public void reject(Context context, NBEvent nbevent) { - nbEventService.deleteEventByEventId(nbevent.getEventId()); - makeAcknowledgement(nbevent.getEventId(), nbevent.getSource(), NBEvent.REJECTED); + public void reject(Context context, QAEvent qaevent) { + qaEventService.deleteEventByEventId(qaevent.getEventId()); + makeAcknowledgement(qaevent.getEventId(), qaevent.getSource(), QAEvent.REJECTED); } /** @@ -102,7 +102,7 @@ public void reject(Context context, NBEvent nbevent) { */ private void makeAcknowledgement(String eventId, String source, String status) { String[] ackwnoledgeCallbacks = configurationService - .getArrayProperty("nbevents." + source + ".acknowledge-url"); + .getArrayProperty("qaevents." + source + ".acknowledge-url"); if (ackwnoledgeCallbacks != null) { for (String ackwnoledgeCallback : ackwnoledgeCallbacks) { if (StringUtils.isNotBlank(ackwnoledgeCallback)) { diff --git a/dspace-api/src/main/java/org/dspace/app/nbevent/NBEventsDeleteCascadeConsumer.java b/dspace-api/src/main/java/org/dspace/qaevent/QAEventsDeleteCascadeConsumer.java similarity index 71% rename from dspace-api/src/main/java/org/dspace/app/nbevent/NBEventsDeleteCascadeConsumer.java rename to dspace-api/src/main/java/org/dspace/qaevent/QAEventsDeleteCascadeConsumer.java index 8297599bc5c8..68976430e626 100644 --- a/dspace-api/src/main/java/org/dspace/app/nbevent/NBEventsDeleteCascadeConsumer.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/QAEventsDeleteCascadeConsumer.java @@ -6,28 +6,28 @@ * http://www.dspace.org/license/ */ -package org.dspace.app.nbevent; +package org.dspace.qaevent; -import org.dspace.app.nbevent.service.NBEventService; import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.event.Consumer; import org.dspace.event.Event; +import org.dspace.qaevent.service.QAEventService; import org.dspace.utils.DSpace; /** - * Consumer to delete nbevents once the target item is deleted + * Consumer to delete qaevents once the target item is deleted * * @author Andrea Bollini (andrea.bollini at 4science.it) * */ -public class NBEventsDeleteCascadeConsumer implements Consumer { +public class QAEventsDeleteCascadeConsumer implements Consumer { - private NBEventService nbEventService; + private QAEventService qaEventService; @Override public void initialize() throws Exception { - nbEventService = new DSpace().getSingletonService(NBEventService.class); + qaEventService = new DSpace().getSingletonService(QAEventService.class); } @Override @@ -39,7 +39,7 @@ public void finish(Context context) throws Exception { public void consume(Context context, Event event) throws Exception { if (event.getEventType() == Event.DELETE) { if (event.getSubjectType() == Constants.ITEM && event.getSubjectID() != null) { - nbEventService.deleteEventsByTargetId(event.getSubjectID()); + qaEventService.deleteEventsByTargetId(event.getSubjectID()); } } } diff --git a/dspace-api/src/main/java/org/dspace/app/nbevent/NBOpenaireMetadataMapAction.java b/dspace-api/src/main/java/org/dspace/qaevent/QAOpenaireMetadataMapAction.java similarity index 87% rename from dspace-api/src/main/java/org/dspace/app/nbevent/NBOpenaireMetadataMapAction.java rename to dspace-api/src/main/java/org/dspace/qaevent/QAOpenaireMetadataMapAction.java index 216f44a2d502..038c42bb38e7 100644 --- a/dspace-api/src/main/java/org/dspace/app/nbevent/NBOpenaireMetadataMapAction.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/QAOpenaireMetadataMapAction.java @@ -5,27 +5,27 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.nbevent; +package org.dspace.qaevent; import java.sql.SQLException; import java.util.Map; -import org.dspace.app.nbevent.service.dto.NBMessageDTO; -import org.dspace.app.nbevent.service.dto.OpenaireMessageDTO; import org.dspace.authorize.AuthorizeException; import org.dspace.content.Item; import org.dspace.content.service.ItemService; import org.dspace.core.Context; +import org.dspace.qaevent.service.dto.OpenaireMessageDTO; +import org.dspace.qaevent.service.dto.QAMessageDTO; import org.springframework.beans.factory.annotation.Autowired; /** - * Implementation of {@link NBAction} that add a specific metadata on the given + * Implementation of {@link QualityAssuranceAction} that add a specific metadata on the given * item based on the OPENAIRE message type. * * @author Andrea Bollini (andrea.bollini at 4science.it) * */ -public class NBOpenaireMetadataMapAction implements NBAction { +public class QAOpenaireMetadataMapAction implements QualityAssuranceAction { public static final String DEFAULT = "default"; private Map types; @@ -49,7 +49,7 @@ public void setTypes(Map types) { * openaire message type. */ @Override - public void applyCorrection(Context context, Item item, Item relatedItem, NBMessageDTO message) { + public void applyCorrection(Context context, Item item, Item relatedItem, QAMessageDTO message) { if (!(message instanceof OpenaireMessageDTO)) { throw new IllegalArgumentException("Unsupported message type: " + message.getClass()); diff --git a/dspace-api/src/main/java/org/dspace/app/nbevent/NBOpenaireSimpleMetadataAction.java b/dspace-api/src/main/java/org/dspace/qaevent/QAOpenaireSimpleMetadataAction.java similarity index 82% rename from dspace-api/src/main/java/org/dspace/app/nbevent/NBOpenaireSimpleMetadataAction.java rename to dspace-api/src/main/java/org/dspace/qaevent/QAOpenaireSimpleMetadataAction.java index f08b3f7db4f4..6f63c2a64f8f 100644 --- a/dspace-api/src/main/java/org/dspace/app/nbevent/NBOpenaireSimpleMetadataAction.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/QAOpenaireSimpleMetadataAction.java @@ -5,26 +5,26 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.nbevent; +package org.dspace.qaevent; import java.sql.SQLException; -import org.dspace.app.nbevent.service.dto.NBMessageDTO; -import org.dspace.app.nbevent.service.dto.OpenaireMessageDTO; import org.dspace.authorize.AuthorizeException; import org.dspace.content.Item; import org.dspace.content.service.ItemService; import org.dspace.core.Context; +import org.dspace.qaevent.service.dto.OpenaireMessageDTO; +import org.dspace.qaevent.service.dto.QAMessageDTO; import org.springframework.beans.factory.annotation.Autowired; /** - * Implementation of {@link NBAction} that add a simple metadata to the given + * Implementation of {@link QualityAssuranceAction} that add a simple metadata to the given * item. * * @author Andrea Bollini (andrea.bollini at 4science.it) * */ -public class NBOpenaireSimpleMetadataAction implements NBAction { +public class QAOpenaireSimpleMetadataAction implements QualityAssuranceAction { private String metadata; private String metadataSchema; private String metadataElement; @@ -51,7 +51,7 @@ public void setMetadata(String metadata) { } @Override - public void applyCorrection(Context context, Item item, Item relatedItem, NBMessageDTO message) { + public void applyCorrection(Context context, Item item, Item relatedItem, QAMessageDTO message) { try { itemService.addMetadata(context, item, metadataSchema, metadataElement, metadataQualifier, null, ((OpenaireMessageDTO) message).getAbstracts()); diff --git a/dspace-api/src/main/java/org/dspace/app/nbevent/NBSource.java b/dspace-api/src/main/java/org/dspace/qaevent/QASource.java similarity index 87% rename from dspace-api/src/main/java/org/dspace/app/nbevent/NBSource.java rename to dspace-api/src/main/java/org/dspace/qaevent/QASource.java index 42a416bf90db..b3f7be5f5221 100644 --- a/dspace-api/src/main/java/org/dspace/app/nbevent/NBSource.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/QASource.java @@ -5,17 +5,17 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.nbevent; +package org.dspace.qaevent; import java.util.Date; /** - * This model class represent the source/provider of the NB events (as OpenAIRE). + * This model class represent the source/provider of the QA events (as OpenAIRE). * * @author Luca Giamminonni (luca.giamminonni at 4Science) * */ -public class NBSource { +public class QASource { private String name; private long totalEvents; private Date lastEvent; diff --git a/dspace-api/src/main/java/org/dspace/app/nbevent/NBTopic.java b/dspace-api/src/main/java/org/dspace/qaevent/QATopic.java similarity index 87% rename from dspace-api/src/main/java/org/dspace/app/nbevent/NBTopic.java rename to dspace-api/src/main/java/org/dspace/qaevent/QATopic.java index afa9990d3da3..1ce09fe45dcf 100644 --- a/dspace-api/src/main/java/org/dspace/app/nbevent/NBTopic.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/QATopic.java @@ -5,17 +5,17 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.nbevent; +package org.dspace.qaevent; import java.util.Date; /** - * This model class represent the notification broker topic concept + * This model class represent the quality assurance broker topic concept * * @author Andrea Bollini (andrea.bollini at 4science.it) * */ -public class NBTopic { +public class QATopic { private String key; private long totalEvents; private Date lastEvent; diff --git a/dspace-api/src/main/java/org/dspace/app/nbevent/NBAction.java b/dspace-api/src/main/java/org/dspace/qaevent/QualityAssuranceAction.java similarity index 82% rename from dspace-api/src/main/java/org/dspace/app/nbevent/NBAction.java rename to dspace-api/src/main/java/org/dspace/qaevent/QualityAssuranceAction.java index 099982d289c8..f2aebba799bd 100644 --- a/dspace-api/src/main/java/org/dspace/app/nbevent/NBAction.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/QualityAssuranceAction.java @@ -5,11 +5,11 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.nbevent; +package org.dspace.qaevent; -import org.dspace.app.nbevent.service.dto.NBMessageDTO; import org.dspace.content.Item; import org.dspace.core.Context; +import org.dspace.qaevent.service.dto.QAMessageDTO; /** * Interface for classes that perform a correction on the given item. @@ -17,7 +17,7 @@ * @author Andrea Bollini (andrea.bollini at 4science.it) * */ -public interface NBAction { +public interface QualityAssuranceAction { /** * Perform a correction on the given item. @@ -27,5 +27,5 @@ public interface NBAction { * @param relatedItem the related item, if any * @param message the message with the correction details */ - public void applyCorrection(Context context, Item item, Item relatedItem, NBMessageDTO message); + public void applyCorrection(Context context, Item item, Item relatedItem, QAMessageDTO message); } diff --git a/dspace-api/src/main/java/org/dspace/app/nbevent/dao/NBEventsDao.java b/dspace-api/src/main/java/org/dspace/qaevent/dao/QAEventsDao.java similarity index 86% rename from dspace-api/src/main/java/org/dspace/app/nbevent/dao/NBEventsDao.java rename to dspace-api/src/main/java/org/dspace/qaevent/dao/QAEventsDao.java index db93eb95c5f0..30a74e55baf6 100644 --- a/dspace-api/src/main/java/org/dspace/app/nbevent/dao/NBEventsDao.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/dao/QAEventsDao.java @@ -5,26 +5,26 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.nbevent.dao; +package org.dspace.qaevent.dao; import java.sql.SQLException; import java.util.List; import org.dspace.content.Item; -import org.dspace.content.NBEventProcessed; +import org.dspace.content.QAEventProcessed; import org.dspace.core.Context; import org.dspace.eperson.EPerson; /** - * DAO that handle processed NB Events. + * DAO that handle processed QA Events. * * @author Andrea Bollini (andrea.bollini at 4science.it) * */ -public interface NBEventsDao { +public interface QAEventsDao { /** - * Search a page of notification broker events by notification ID. + * Search a page of quality assurance broker events by notification ID. * * @param context the DSpace context * @param eventId the event id @@ -33,7 +33,7 @@ public interface NBEventsDao { * @return the processed events * @throws SQLException if an SQL error occurs */ - public List searchByEventId(Context context, String eventId, Integer start, Integer size) + public List searchByEventId(Context context, String eventId, Integer start, Integer size) throws SQLException; /** diff --git a/dspace-api/src/main/java/org/dspace/app/nbevent/dao/impl/NBEventsDaoImpl.java b/dspace-api/src/main/java/org/dspace/qaevent/dao/impl/QAEventsDaoImpl.java similarity index 62% rename from dspace-api/src/main/java/org/dspace/app/nbevent/dao/impl/NBEventsDaoImpl.java rename to dspace-api/src/main/java/org/dspace/qaevent/dao/impl/QAEventsDaoImpl.java index db3977c1099f..550027441b93 100644 --- a/dspace-api/src/main/java/org/dspace/app/nbevent/dao/impl/NBEventsDaoImpl.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/dao/impl/QAEventsDaoImpl.java @@ -5,38 +5,38 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.nbevent.dao.impl; +package org.dspace.qaevent.dao.impl; import java.sql.SQLException; import java.util.Date; import java.util.List; import javax.persistence.Query; -import org.dspace.app.nbevent.dao.NBEventsDao; import org.dspace.content.Item; -import org.dspace.content.NBEventProcessed; +import org.dspace.content.QAEventProcessed; import org.dspace.core.AbstractHibernateDAO; import org.dspace.core.Context; import org.dspace.eperson.EPerson; +import org.dspace.qaevent.dao.QAEventsDao; /** - * Implementation of {@link NBEventsDao} that store processed events using an + * Implementation of {@link QAEventsDao} that store processed events using an * SQL DBMS. * * @author Andrea Bollini (andrea.bollini at 4science.it) * */ -public class NBEventsDaoImpl extends AbstractHibernateDAO implements NBEventsDao { +public class QAEventsDaoImpl extends AbstractHibernateDAO implements QAEventsDao { @Override public boolean storeEvent(Context context, String checksum, EPerson eperson, Item item) { - NBEventProcessed nbEvent = new NBEventProcessed(); - nbEvent.setEperson(eperson); - nbEvent.setEventId(checksum); - nbEvent.setItem(item); - nbEvent.setEventTimestamp(new Date()); + QAEventProcessed qaEvent = new QAEventProcessed(); + qaEvent.setEperson(eperson); + qaEvent.setEventId(checksum); + qaEvent.setItem(item); + qaEvent.setEventTimestamp(new Date()); try { - create(context, nbEvent); + create(context, qaEvent); return true; } catch (SQLException e) { return false; @@ -46,16 +46,16 @@ public boolean storeEvent(Context context, String checksum, EPerson eperson, Ite @Override public boolean isEventStored(Context context, String checksum) throws SQLException { Query query = createQuery(context, - "SELECT count(eventId) FROM NBEventProcessed nbevent WHERE nbevent.eventId = :event_id "); + "SELECT count(eventId) FROM QAEventProcessed qaevent WHERE qaevent.eventId = :event_id "); query.setParameter("event_id", checksum); return count(query) != 0; } @Override - public List searchByEventId(Context context, String eventId, Integer start, Integer size) + public List searchByEventId(Context context, String eventId, Integer start, Integer size) throws SQLException { Query query = createQuery(context, - "SELECT * " + "FROM NBEventProcessed nbevent WHERE nbevent.nbevent_id = :event_id "); + "SELECT * " + "FROM QAEventProcessed qaevent WHERE qaevent.qaevent_id = :event_id "); query.setFirstResult(start); query.setMaxResults(size); query.setParameter("event_id", eventId); diff --git a/dspace-api/src/main/java/org/dspace/app/nbevent/service/NBEventService.java b/dspace-api/src/main/java/org/dspace/qaevent/service/QAEventService.java similarity index 82% rename from dspace-api/src/main/java/org/dspace/app/nbevent/service/NBEventService.java rename to dspace-api/src/main/java/org/dspace/qaevent/service/QAEventService.java index 599806f42541..8e840037de9a 100644 --- a/dspace-api/src/main/java/org/dspace/app/nbevent/service/NBEventService.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/service/QAEventService.java @@ -5,23 +5,23 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.nbevent.service; +package org.dspace.qaevent.service; import java.util.List; import java.util.UUID; -import org.dspace.app.nbevent.NBSource; -import org.dspace.app.nbevent.NBTopic; -import org.dspace.content.NBEvent; +import org.dspace.content.QAEvent; import org.dspace.core.Context; +import org.dspace.qaevent.QASource; +import org.dspace.qaevent.QATopic; /** - * Service that handles {@link NBEvent}. + * Service that handles {@link QAEvent}. * * @author Andrea Bollini (andrea.bollini at 4science.it) * */ -public interface NBEventService { +public interface QAEventService { /** * Find all the event's topics. @@ -31,7 +31,7 @@ public interface NBEventService { * @param pageSize the page size * @return the topics list */ - public List findAllTopics(long offset, long pageSize); + public List findAllTopics(long offset, long pageSize); /** * Find all the event's topics related to the given source. @@ -42,7 +42,7 @@ public interface NBEventService { * @param pageSize the page size * @return the topics list */ - public List findAllTopicsBySource(String source, long offset, long count); + public List findAllTopicsBySource(String source, long offset, long count); /** * Count all the event's topics. @@ -71,7 +71,7 @@ public interface NBEventService { * @param ascending true if the order should be ascending, false otherwise * @return the events */ - public List findEventsByTopicAndPage(String topic, long offset, int pageSize, + public List findEventsByTopicAndPage(String topic, long offset, int pageSize, String orderField, boolean ascending); /** @@ -88,7 +88,7 @@ public List findEventsByTopicAndPage(String topic, long offset, int pag * @param id the id of the event to search for * @return the event */ - public NBEvent findEventByEventId(String id); + public QAEvent findEventByEventId(String id); /** * Store the given event. @@ -96,7 +96,7 @@ public List findEventsByTopicAndPage(String topic, long offset, int pag * @param context the DSpace context * @param event the event to store */ - public void store(Context context, NBEvent event); + public void store(Context context, QAEvent event); /** * Delete an event by the given id. @@ -118,7 +118,7 @@ public List findEventsByTopicAndPage(String topic, long offset, int pag * @param topicId the topic id to search for * @return the topic */ - public NBTopic findTopicByTopicId(String topicId); + public QATopic findTopicByTopicId(String topicId); /** * Find a specific source by the given name. @@ -126,7 +126,7 @@ public List findEventsByTopicAndPage(String topic, long offset, int pag * @param source the source name * @return the source */ - public NBSource findSource(String source); + public QASource findSource(String source); /** * Find all the event's sources. @@ -135,7 +135,7 @@ public List findEventsByTopicAndPage(String topic, long offset, int pag * @param pageSize the page size * @return the sources list */ - public List findAllSources(long offset, int pageSize); + public List findAllSources(long offset, int pageSize); /** * Count all the event's sources. diff --git a/dspace-api/src/main/java/org/dspace/app/nbevent/service/dto/OpenaireMessageDTO.java b/dspace-api/src/main/java/org/dspace/qaevent/service/dto/OpenaireMessageDTO.java similarity index 95% rename from dspace-api/src/main/java/org/dspace/app/nbevent/service/dto/OpenaireMessageDTO.java rename to dspace-api/src/main/java/org/dspace/qaevent/service/dto/OpenaireMessageDTO.java index 5558aa3cb001..117b764ca02f 100644 --- a/dspace-api/src/main/java/org/dspace/app/nbevent/service/dto/OpenaireMessageDTO.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/service/dto/OpenaireMessageDTO.java @@ -5,17 +5,17 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.nbevent.service.dto; +package org.dspace.qaevent.service.dto; import com.fasterxml.jackson.annotation.JsonProperty; /** - * Implementation of {@link NBMessageDTO} that model message coming from OPENAIRE. + * Implementation of {@link QAMessageDTO} that model message coming from OPENAIRE. * * @author Luca Giamminonni (luca.giamminonni at 4science.it) * */ -public class OpenaireMessageDTO implements NBMessageDTO { +public class OpenaireMessageDTO implements QAMessageDTO { @JsonProperty("pids[0].value") private String value; diff --git a/dspace-api/src/main/java/org/dspace/app/nbevent/service/dto/NBMessageDTO.java b/dspace-api/src/main/java/org/dspace/qaevent/service/dto/QAMessageDTO.java similarity index 73% rename from dspace-api/src/main/java/org/dspace/app/nbevent/service/dto/NBMessageDTO.java rename to dspace-api/src/main/java/org/dspace/qaevent/service/dto/QAMessageDTO.java index e341c9bd604e..2a63f42e615c 100644 --- a/dspace-api/src/main/java/org/dspace/app/nbevent/service/dto/NBMessageDTO.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/service/dto/QAMessageDTO.java @@ -5,17 +5,17 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.nbevent.service.dto; +package org.dspace.qaevent.service.dto; -import org.dspace.content.NBEvent; +import org.dspace.content.QAEvent; /** - * Interface for classes that contains the details related to a {@link NBEvent}. + * Interface for classes that contains the details related to a {@link QAEvent}. * * @author Luca Giamminonni (luca.giamminonni at 4science.it) * */ -public interface NBMessageDTO { +public interface QAMessageDTO { } diff --git a/dspace-api/src/main/java/org/dspace/app/nbevent/service/impl/NBEventServiceImpl.java b/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventServiceImpl.java similarity index 87% rename from dspace-api/src/main/java/org/dspace/app/nbevent/service/impl/NBEventServiceImpl.java rename to dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventServiceImpl.java index 94e9e32eb644..04a830358c35 100644 --- a/dspace-api/src/main/java/org/dspace/app/nbevent/service/impl/NBEventServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventServiceImpl.java @@ -5,7 +5,7 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.nbevent.service.impl; +package org.dspace.qaevent.service.impl; import static java.util.Comparator.comparing; @@ -33,30 +33,30 @@ import org.apache.solr.common.SolrDocument; import org.apache.solr.common.SolrDocumentList; import org.apache.solr.common.SolrInputDocument; -import org.dspace.app.nbevent.NBSource; -import org.dspace.app.nbevent.NBTopic; -import org.dspace.app.nbevent.dao.NBEventsDao; -import org.dspace.app.nbevent.dao.impl.NBEventsDaoImpl; -import org.dspace.app.nbevent.service.NBEventService; import org.dspace.content.Item; -import org.dspace.content.NBEvent; +import org.dspace.content.QAEvent; import org.dspace.content.service.ItemService; import org.dspace.core.Context; import org.dspace.handle.service.HandleService; +import org.dspace.qaevent.QASource; +import org.dspace.qaevent.QATopic; +import org.dspace.qaevent.dao.QAEventsDao; +import org.dspace.qaevent.dao.impl.QAEventsDaoImpl; +import org.dspace.qaevent.service.QAEventService; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; import org.springframework.beans.factory.annotation.Autowired; /** - * Implementation of {@link NBEventService} that use Solr to store events. When + * Implementation of {@link QAEventService} that use Solr to store events. When * the user performs an action on the event (such as accepting the suggestion or * rejecting it) then the event is removed from solr and saved in the database - * (see {@link NBEventsDao}) so that it is no longer proposed. + * (see {@link QAEventsDao}) so that it is no longer proposed. * * @author Andrea Bollini (andrea.bollini at 4science.it) * */ -public class NBEventServiceImpl implements NBEventService { +public class QAEventServiceImpl implements QAEventService { @Autowired(required = true) protected ConfigurationService configurationService; @@ -68,11 +68,11 @@ public class NBEventServiceImpl implements NBEventService { private HandleService handleService; @Autowired - private NBEventsDaoImpl nbEventsDao; + private QAEventsDaoImpl qaEventsDao; private ObjectMapper jsonMapper; - public NBEventServiceImpl() { + public QAEventServiceImpl() { jsonMapper = new JsonMapper(); jsonMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); } @@ -96,7 +96,7 @@ public NBEventServiceImpl() { protected SolrClient getSolr() { if (solr == null) { String solrService = DSpaceServicesFactory.getInstance().getConfigurationService() - .getProperty("nbevents.solr.server", "http://localhost:8983/solr/nbevent"); + .getProperty("qaevents.solr.server", "http://localhost:8983/solr/qaevent"); return new HttpSolrClient.Builder(solrService).build(); } return solr; @@ -158,7 +158,7 @@ public void deleteEventsByTargetId(UUID targetId) { } @Override - public NBTopic findTopicByTopicId(String topicId) { + public QATopic findTopicByTopicId(String topicId) { SolrQuery solrQuery = new SolrQuery(); solrQuery.setRows(0); solrQuery.setQuery(TOPIC + ":" + topicId.replaceAll("!", "/")); @@ -171,7 +171,7 @@ public NBTopic findTopicByTopicId(String topicId) { FacetField facetField = response.getFacetField(TOPIC); for (Count c : facetField.getValues()) { if (c.getName().equals(topicId.replace("!", "/"))) { - NBTopic topic = new NBTopic(); + QATopic topic = new QATopic(); topic.setKey(c.getName()); // topic.setName(OpenstarSupportedTopic.sorlToRest(c.getName())); topic.setTotalEvents(c.getCount()); @@ -186,12 +186,12 @@ public NBTopic findTopicByTopicId(String topicId) { } @Override - public List findAllTopics(long offset, long count) { + public List findAllTopics(long offset, long count) { return findAllTopicsBySource(null, offset, count); } @Override - public List findAllTopicsBySource(String source, long offset, long count) { + public List findAllTopicsBySource(String source, long offset, long count) { if (source != null && isNotSupportedSource(source)) { return null; @@ -208,33 +208,33 @@ public List findAllTopicsBySource(String source, long offset, long coun solrQuery.addFilterQuery(SOURCE + ":" + source); } QueryResponse response; - List nbTopics = null; + List topics = null; try { response = getSolr().query(solrQuery); FacetField facetField = response.getFacetField(TOPIC); - nbTopics = new ArrayList<>(); + topics = new ArrayList<>(); int idx = 0; for (Count c : facetField.getValues()) { if (idx < offset) { idx++; continue; } - NBTopic topic = new NBTopic(); + QATopic topic = new QATopic(); topic.setKey(c.getName()); // topic.setName(c.getName().replaceAll("/", "!")); topic.setTotalEvents(c.getCount()); topic.setLastEvent(new Date()); - nbTopics.add(topic); + topics.add(topic); idx++; } } catch (SolrServerException | IOException e) { throw new RuntimeException(e); } - return nbTopics; + return topics; } @Override - public void store(Context context, NBEvent dto) { + public void store(Context context, QAEvent dto) { UpdateRequest updateRequest = new UpdateRequest(); String topic = dto.getTopic(); @@ -245,7 +245,7 @@ public void store(Context context, NBEvent dto) { if (topic != null) { String checksum = dto.getEventId(); try { - if (!nbEventsDao.isEventStored(context, checksum)) { + if (!qaEventsDao.isEventStored(context, checksum)) { SolrInputDocument doc = new SolrInputDocument(); doc.addField(SOURCE, dto.getSource()); doc.addField(EVENT_ID, checksum); @@ -273,7 +273,7 @@ public void store(Context context, NBEvent dto) { } @Override - public NBEvent findEventByEventId(String eventId) { + public QAEvent findEventByEventId(String eventId) { SolrQuery param = new SolrQuery(EVENT_ID + ":" + eventId); QueryResponse response; try { @@ -282,7 +282,7 @@ public NBEvent findEventByEventId(String eventId) { SolrDocumentList list = response.getResults(); if (list != null && list.size() == 1) { SolrDocument doc = list.get(0); - return getNBEventFromSOLR(doc); + return getQAEventFromSOLR(doc); } } } catch (SolrServerException | IOException e) { @@ -291,8 +291,8 @@ public NBEvent findEventByEventId(String eventId) { return null; } - private NBEvent getNBEventFromSOLR(SolrDocument doc) { - NBEvent item = new NBEvent(); + private QAEvent getQAEventFromSOLR(SolrDocument doc) { + QAEvent item = new QAEvent(); item.setSource((String) doc.get(SOURCE)); item.setEventId((String) doc.get(EVENT_ID)); item.setLastUpdate((Date) doc.get(LAST_UPDATE)); @@ -307,7 +307,7 @@ private NBEvent getNBEventFromSOLR(SolrDocument doc) { } @Override - public List findEventsByTopicAndPage(String topic, long offset, + public List findEventsByTopicAndPage(String topic, long offset, int pageSize, String orderField, boolean ascending) { SolrQuery solrQuery = new SolrQuery(); solrQuery.setStart(((Long) offset).intValue()); @@ -319,9 +319,9 @@ public List findEventsByTopicAndPage(String topic, long offset, response = getSolr().query(solrQuery); if (response != null) { SolrDocumentList list = response.getResults(); - List responseItem = new ArrayList<>(); + List responseItem = new ArrayList<>(); for (SolrDocument doc : list) { - NBEvent item = getNBEventFromSOLR(doc); + QAEvent item = getQAEventFromSOLR(doc); responseItem.add(item); } return responseItem; @@ -373,7 +373,7 @@ private String getHandleFromOriginalId(String originalId) { } @Override - public NBSource findSource(String sourceName) { + public QASource findSource(String sourceName) { if (isNotSupportedSource(sourceName)) { return null; @@ -392,7 +392,7 @@ public NBSource findSource(String sourceName) { FacetField facetField = response.getFacetField(SOURCE); for (Count c : facetField.getValues()) { if (c.getName().equalsIgnoreCase(sourceName)) { - NBSource source = new NBSource(); + QASource source = new QASource(); source.setName(c.getName()); source.setTotalEvents(c.getCount()); source.setLastEvent(new Date()); @@ -403,7 +403,7 @@ public NBSource findSource(String sourceName) { throw new RuntimeException(e); } - NBSource source = new NBSource(); + QASource source = new QASource(); source.setName(sourceName); source.setTotalEvents(0L); @@ -411,10 +411,10 @@ public NBSource findSource(String sourceName) { } @Override - public List findAllSources(long offset, int pageSize) { + public List findAllSources(long offset, int pageSize) { return Arrays.stream(getSupportedSources()) .map((sourceName) -> findSource(sourceName)) - .sorted(comparing(NBSource::getTotalEvents).reversed()) + .sorted(comparing(QASource::getTotalEvents).reversed()) .skip(offset) .limit(pageSize) .collect(Collectors.toList()); @@ -430,7 +430,7 @@ private boolean isNotSupportedSource(String source) { } private String[] getSupportedSources() { - return configurationService.getArrayProperty("nbevent.sources", new String[] { NBEvent.OPENAIRE_SOURCE }); + return configurationService.getArrayProperty("qaevent.sources", new String[] { QAEvent.OPENAIRE_SOURCE }); } } diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.3_2022.02.17__nbevent_processed.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.3_2022.02.17__qaevent_processed.sql similarity index 65% rename from dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.3_2022.02.17__nbevent_processed.sql rename to dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.3_2022.02.17__qaevent_processed.sql index b64c52248b43..467de85f8505 100644 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.3_2022.02.17__nbevent_processed.sql +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.3_2022.02.17__qaevent_processed.sql @@ -6,11 +6,11 @@ -- http://www.dspace.org/license/ -- -CREATE TABLE nbevent_processed ( - nbevent_id VARCHAR(255) NOT NULL, - nbevent_timestamp TIMESTAMP NULL, +CREATE TABLE qaevent_processed ( + qaevent_id VARCHAR(255) NOT NULL, + qaevent_timestamp TIMESTAMP NULL, eperson_uuid UUID NULL REFERENCES eperson(uuid), item_uuid uuid NOT NULL REFERENCES item(uuid) ); -CREATE INDEX item_uuid_idx ON nbevent_processed(item_uuid); +CREATE INDEX item_uuid_idx ON qaevent_processed(item_uuid); diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.3_2022.02.17__nbevent_processed.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.3_2022.02.17__qaevent_processed.sql similarity index 66% rename from dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.3_2022.02.17__nbevent_processed.sql rename to dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.3_2022.02.17__qaevent_processed.sql index 5cf9a0484f8d..5c3f0fac73ce 100644 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.3_2022.02.17__nbevent_processed.sql +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.3_2022.02.17__qaevent_processed.sql @@ -6,14 +6,14 @@ -- http://www.dspace.org/license/ -- -CREATE TABLE nbevent_processed ( - nbevent_id VARCHAR(255) NOT NULL, - nbevent_timestamp TIMESTAMP NULL, +CREATE TABLE qaevent_processed ( + qaevent_id VARCHAR(255) NOT NULL, + qaevent_timestamp TIMESTAMP NULL, eperson_uuid UUID NULL, item_uuid UUID NULL, - CONSTRAINT nbevent_pk PRIMARY KEY (nbevent_id), + CONSTRAINT qaevent_pk PRIMARY KEY (qaevent_id), CONSTRAINT eperson_uuid_fkey FOREIGN KEY (eperson_uuid) REFERENCES eperson (uuid), CONSTRAINT item_uuid_fkey FOREIGN KEY (item_uuid) REFERENCES item (uuid) ); -CREATE INDEX item_uuid_idx ON nbevent_processed(item_uuid); +CREATE INDEX item_uuid_idx ON qaevent_processed(item_uuid); diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.3_2022.02.17__nbevent_processed.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.3_2022.02.17__qaevent_processed.sql similarity index 66% rename from dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.3_2022.02.17__nbevent_processed.sql rename to dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.3_2022.02.17__qaevent_processed.sql index 5cf9a0484f8d..5c3f0fac73ce 100644 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.3_2022.02.17__nbevent_processed.sql +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.3_2022.02.17__qaevent_processed.sql @@ -6,14 +6,14 @@ -- http://www.dspace.org/license/ -- -CREATE TABLE nbevent_processed ( - nbevent_id VARCHAR(255) NOT NULL, - nbevent_timestamp TIMESTAMP NULL, +CREATE TABLE qaevent_processed ( + qaevent_id VARCHAR(255) NOT NULL, + qaevent_timestamp TIMESTAMP NULL, eperson_uuid UUID NULL, item_uuid UUID NULL, - CONSTRAINT nbevent_pk PRIMARY KEY (nbevent_id), + CONSTRAINT qaevent_pk PRIMARY KEY (qaevent_id), CONSTRAINT eperson_uuid_fkey FOREIGN KEY (eperson_uuid) REFERENCES eperson (uuid), CONSTRAINT item_uuid_fkey FOREIGN KEY (item_uuid) REFERENCES item (uuid) ); -CREATE INDEX item_uuid_idx ON nbevent_processed(item_uuid); +CREATE INDEX item_uuid_idx ON qaevent_processed(item_uuid); diff --git a/dspace-api/src/test/data/dspaceFolder/config/local.cfg b/dspace-api/src/test/data/dspaceFolder/config/local.cfg index 328daa72bec7..39d6a6f6b50c 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/local.cfg +++ b/dspace-api/src/test/data/dspaceFolder/config/local.cfg @@ -84,14 +84,14 @@ loglevel.dspace = INFO # IIIF TEST SETTINGS # ######################## iiif.enabled = true -event.dispatcher.default.consumers = versioning, discovery, eperson, orcidqueue, iiif, nbeventsdelete +event.dispatcher.default.consumers = versioning, discovery, eperson, orcidqueue, iiif, qaeventsdelete ########################################### # CUSTOM UNIT / INTEGRATION TEST SETTINGS # ########################################### # custom dispatcher to be used by dspace-api IT that doesn't need SOLR event.dispatcher.exclude-discovery.class = org.dspace.event.BasicDispatcher -event.dispatcher.exclude-discovery.consumers = versioning, eperson, nbeventsdelete +event.dispatcher.exclude-discovery.consumers = versioning, eperson, qaeventsdelete # Configure authority control for Unit Testing (in DSpaceControlledVocabularyTest) # (This overrides default, commented out settings in dspace.cfg) diff --git a/dspace-api/src/test/data/dspaceFolder/config/spring/api/solr-services.xml b/dspace-api/src/test/data/dspaceFolder/config/spring/api/solr-services.xml index b71736c4682e..29703e3ee07a 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/spring/api/solr-services.xml +++ b/dspace-api/src/test/data/dspaceFolder/config/spring/api/solr-services.xml @@ -48,9 +48,9 @@ class="org.dspace.statistics.MockSolrStatisticsCore" autowire-candidate="true"/> - - + + diff --git a/dspace-api/src/test/java/org/dspace/builder/AbstractBuilder.java b/dspace-api/src/test/java/org/dspace/builder/AbstractBuilder.java index 107a1a6c02a7..8053774ea93f 100644 --- a/dspace-api/src/test/java/org/dspace/builder/AbstractBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/AbstractBuilder.java @@ -13,7 +13,6 @@ import org.apache.commons.collections4.CollectionUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.dspace.app.nbevent.service.NBEventService; import org.dspace.app.requestitem.factory.RequestItemServiceFactory; import org.dspace.app.requestitem.service.RequestItemService; import org.dspace.authorize.AuthorizeException; @@ -47,6 +46,7 @@ import org.dspace.orcid.service.OrcidHistoryService; import org.dspace.orcid.service.OrcidQueueService; import org.dspace.orcid.service.OrcidTokenService; +import org.dspace.qaevent.service.QAEventService; import org.dspace.scripts.factory.ScriptServiceFactory; import org.dspace.scripts.service.ProcessService; import org.dspace.services.factory.DSpaceServicesFactory; @@ -104,7 +104,7 @@ public abstract class AbstractBuilder { static OrcidHistoryService orcidHistoryService; static OrcidQueueService orcidQueueService; static OrcidTokenService orcidTokenService; - static NBEventService nbEventService; + static QAEventService qaEventService; protected Context context; @@ -164,7 +164,7 @@ public static void init() { orcidHistoryService = OrcidServiceFactory.getInstance().getOrcidHistoryService(); orcidQueueService = OrcidServiceFactory.getInstance().getOrcidQueueService(); orcidTokenService = OrcidServiceFactory.getInstance().getOrcidTokenService(); - nbEventService = new DSpace().getSingletonService(NBEventService.class); + qaEventService = new DSpace().getSingletonService(QAEventService.class); } @@ -198,7 +198,7 @@ public static void destroy() { requestItemService = null; versioningService = null; orcidTokenService = null; - nbEventService = null; + qaEventService = null; } diff --git a/dspace-api/src/test/java/org/dspace/builder/NBEventBuilder.java b/dspace-api/src/test/java/org/dspace/builder/QAEventBuilder.java similarity index 58% rename from dspace-api/src/test/java/org/dspace/builder/NBEventBuilder.java rename to dspace-api/src/test/java/org/dspace/builder/QAEventBuilder.java index 3ad22738c371..154bf737d921 100644 --- a/dspace-api/src/test/java/org/dspace/builder/NBEventBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/QAEventBuilder.java @@ -9,22 +9,22 @@ import java.util.Date; -import org.dspace.app.nbevent.service.NBEventService; import org.dspace.content.Collection; import org.dspace.content.Item; -import org.dspace.content.NBEvent; +import org.dspace.content.QAEvent; import org.dspace.core.Context; +import org.dspace.qaevent.service.QAEventService; /** - * Builder to construct Notification Broker Event objects + * Builder to construct Quality Assurance Broker Event objects * * @author Andrea Bollini (andrea.bollini at 4science.it) */ -public class NBEventBuilder extends AbstractBuilder { +public class QAEventBuilder extends AbstractBuilder { private Item item; - private NBEvent target; - private String source = NBEvent.OPENAIRE_SOURCE; + private QAEvent target; + private String source = QAEvent.OPENAIRE_SOURCE; private String title; private String topic; private String message; @@ -32,21 +32,21 @@ public class NBEventBuilder extends AbstractBuilder { private double trust = 0.5; private Date lastUpdate = new Date(); - protected NBEventBuilder(Context context) { + protected QAEventBuilder(Context context) { super(context); } - public static NBEventBuilder createTarget(final Context context, final Collection col, final String name) { - NBEventBuilder builder = new NBEventBuilder(context); + public static QAEventBuilder createTarget(final Context context, final Collection col, final String name) { + QAEventBuilder builder = new QAEventBuilder(context); return builder.create(context, col, name); } - public static NBEventBuilder createTarget(final Context context, final Item item) { - NBEventBuilder builder = new NBEventBuilder(context); + public static QAEventBuilder createTarget(final Context context, final Item item) { + QAEventBuilder builder = new QAEventBuilder(context); return builder.create(context, item); } - private NBEventBuilder create(final Context context, final Collection col, final String name) { + private QAEventBuilder create(final Context context, final Collection col, final String name) { this.context = context; try { @@ -61,49 +61,49 @@ private NBEventBuilder create(final Context context, final Collection col, final return this; } - private NBEventBuilder create(final Context context, final Item item) { + private QAEventBuilder create(final Context context, final Item item) { this.context = context; this.item = item; return this; } - public NBEventBuilder withTopic(final String topic) { + public QAEventBuilder withTopic(final String topic) { this.topic = topic; return this; } - public NBEventBuilder withSource(final String source) { + public QAEventBuilder withSource(final String source) { this.source = source; return this; } - public NBEventBuilder withTitle(final String title) { + public QAEventBuilder withTitle(final String title) { this.title = title; return this; } - public NBEventBuilder withMessage(final String message) { + public QAEventBuilder withMessage(final String message) { this.message = message; return this; } - public NBEventBuilder withTrust(final double trust) { + public QAEventBuilder withTrust(final double trust) { this.trust = trust; return this; } - public NBEventBuilder withLastUpdate(final Date lastUpdate) { + public QAEventBuilder withLastUpdate(final Date lastUpdate) { this.lastUpdate = lastUpdate; return this; } - public NBEventBuilder withRelatedItem(String relatedItem) { + public QAEventBuilder withRelatedItem(String relatedItem) { this.relatedItem = relatedItem; return this; } @Override - public NBEvent build() { - target = new NBEvent(source, "oai:www.dspace.org:" + item.getHandle(), item.getID().toString(), title, topic, + public QAEvent build() { + target = new QAEvent(source, "oai:www.dspace.org:" + item.getHandle(), item.getID().toString(), title, topic, trust, message, lastUpdate); target.setRelated(relatedItem); try { - nbEventService.store(context, target); + qaEventService.store(context, target); } catch (Exception e) { e.printStackTrace(); } @@ -112,18 +112,18 @@ public NBEvent build() { @Override public void cleanup() throws Exception { - nbEventService.deleteEventByEventId(target.getEventId()); + qaEventService.deleteEventByEventId(target.getEventId()); } @Override - protected NBEventService getService() { - return nbEventService; + protected QAEventService getService() { + return qaEventService; } @Override - public void delete(Context c, NBEvent dso) throws Exception { - nbEventService.deleteEventByEventId(target.getEventId()); + public void delete(Context c, QAEvent dso) throws Exception { + qaEventService.deleteEventByEventId(target.getEventId()); -// nbEventService.deleteTarget(dso); +// qaEventService.deleteTarget(dso); } } \ No newline at end of file diff --git a/dspace-api/src/test/java/org/dspace/app/nbevent/MockNBEventService.java b/dspace-api/src/test/java/org/dspace/qaevent/MockQAEventService.java similarity index 76% rename from dspace-api/src/test/java/org/dspace/app/nbevent/MockNBEventService.java rename to dspace-api/src/test/java/org/dspace/qaevent/MockQAEventService.java index 12058fbf7351..443e6f8d39a8 100644 --- a/dspace-api/src/test/java/org/dspace/app/nbevent/MockNBEventService.java +++ b/dspace-api/src/test/java/org/dspace/qaevent/MockQAEventService.java @@ -5,24 +5,24 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.nbevent; +package org.dspace.qaevent; -import org.dspace.app.nbevent.service.impl.NBEventServiceImpl; +import org.dspace.qaevent.service.impl.QAEventServiceImpl; import org.dspace.solr.MockSolrServer; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.InitializingBean; import org.springframework.stereotype.Service; /** - * Mock SOLR service for the nbevents Core. + * Mock SOLR service for the qaevents Core. */ @Service -public class MockNBEventService extends NBEventServiceImpl implements InitializingBean, DisposableBean { +public class MockQAEventService extends QAEventServiceImpl implements InitializingBean, DisposableBean { private MockSolrServer mockSolrServer; @Override public void afterPropertiesSet() throws Exception { - mockSolrServer = new MockSolrServer("nbevent"); + mockSolrServer = new MockSolrServer("qaevent"); solr = mockSolrServer.getSolrServer(); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/NBEventRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/QAEventRestController.java similarity index 75% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/NBEventRestController.java rename to dspace-server-webapp/src/main/java/org/dspace/app/rest/QAEventRestController.java index 10618288aa2c..1584c48e65fa 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/NBEventRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/QAEventRestController.java @@ -16,19 +16,19 @@ import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang3.StringUtils; -import org.dspace.app.nbevent.service.NBEventService; import org.dspace.app.rest.converter.ConverterService; import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.ItemRest; -import org.dspace.app.rest.model.NBEventRest; +import org.dspace.app.rest.model.QAEventRest; import org.dspace.app.rest.model.hateoas.ItemResource; import org.dspace.app.rest.utils.ContextUtil; import org.dspace.app.rest.utils.Utils; import org.dspace.authorize.AuthorizeException; import org.dspace.content.Item; -import org.dspace.content.NBEvent; +import org.dspace.content.QAEvent; import org.dspace.content.service.ItemService; import org.dspace.core.Context; +import org.dspace.qaevent.service.QAEventService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.rest.webmvc.ControllerUtils; import org.springframework.data.rest.webmvc.ResourceNotFoundException; @@ -44,13 +44,13 @@ import org.springframework.web.bind.annotation.RestController; /** - * This RestController will take care to manipulate the related item eventually associated with a nb event - * "/api/integration/nbevents/{nbeventid}/related" + * This RestController will take care to manipulate the related item eventually associated with a qa event + * "/api/integration/qaevents/{qaeventid}/related" */ @RestController -@RequestMapping("/api/" + NBEventRest.CATEGORY + "/nbevents" + REGEX_REQUESTMAPPING_IDENTIFIER_AS_STRING_VERSION_STRONG +@RequestMapping("/api/" + QAEventRest.CATEGORY + "/qaevents" + REGEX_REQUESTMAPPING_IDENTIFIER_AS_STRING_VERSION_STRONG + "/related") -public class NBEventRestController { +public class QAEventRestController { @Autowired protected Utils utils; @@ -61,15 +61,15 @@ public class NBEventRestController { private ItemService itemService; @Autowired - private NBEventService nbEventService; + private QAEventService qaEventService; /** - * This method associate an item to a nb event + * This method associate an item to a qa event * - * @param nbeventId The nb event id + * @param qaeventId The qa event id * @param response The current response * @param request The current request - * @param relatedItemUUID The uuid of the related item to associate with the nb + * @param relatedItemUUID The uuid of the related item to associate with the qa * event * @return The related item * @throws SQLException If something goes wrong @@ -77,26 +77,26 @@ public class NBEventRestController { */ @RequestMapping(method = RequestMethod.POST) @PreAuthorize("hasAuthority('ADMIN')") - public ResponseEntity> postRelatedItem(@PathVariable(name = "id") String nbeventId, + public ResponseEntity> postRelatedItem(@PathVariable(name = "id") String qaeventId, HttpServletResponse response, HttpServletRequest request, @RequestParam(required = true, name = "item") UUID relatedItemUUID) throws SQLException, AuthorizeException { Context context = ContextUtil.obtainContext(request); - NBEvent nbevent = nbEventService.findEventByEventId(nbeventId); - if (nbevent == null) { - throw new ResourceNotFoundException("No such nb event: " + nbeventId); + QAEvent qaevent = qaEventService.findEventByEventId(qaeventId); + if (qaevent == null) { + throw new ResourceNotFoundException("No such qa event: " + qaeventId); } - if (nbevent.getRelated() != null) { - throw new UnprocessableEntityException("The nb event with ID: " + nbeventId + " already has " + + if (qaevent.getRelated() != null) { + throw new UnprocessableEntityException("The qa event with ID: " + qaeventId + " already has " + "a related item"); - } else if (!StringUtils.endsWith(nbevent.getTopic(), "/PROJECT")) { + } else if (!StringUtils.endsWith(qaevent.getTopic(), "/PROJECT")) { return ControllerUtils.toEmptyResponse(HttpStatus.BAD_REQUEST); } Item relatedItem = itemService.find(context, relatedItemUUID); if (relatedItem != null) { - nbevent.setRelated(relatedItemUUID.toString()); - nbEventService.store(context, nbevent); + qaevent.setRelated(relatedItemUUID.toString()); + qaEventService.store(context, qaevent); } else { throw new UnprocessableEntityException("The proposed related item was not found"); } @@ -107,9 +107,9 @@ public ResponseEntity> postRelatedItem(@PathVariable(name } /** - * This method remove the association to a related item from a nb event + * This method remove the association to a related item from a qa event * - * @param nbeventId The nb event id + * @param qaeventId The qa event id * @param response The current response * @param request The current request * @return The related item @@ -118,17 +118,17 @@ public ResponseEntity> postRelatedItem(@PathVariable(name */ @RequestMapping(method = RequestMethod.DELETE) @PreAuthorize("hasAuthority('ADMIN')") - public ResponseEntity> deleteAdminGroup(@PathVariable(name = "id") String nbeventId, + public ResponseEntity> deleteAdminGroup(@PathVariable(name = "id") String qaeventId, HttpServletResponse response, HttpServletRequest request) throws SQLException, AuthorizeException, IOException { Context context = ContextUtil.obtainContext(request); - NBEvent nbevent = nbEventService.findEventByEventId(nbeventId); - if (nbevent == null) { - throw new ResourceNotFoundException("No such nb event: " + nbeventId); + QAEvent qaevent = qaEventService.findEventByEventId(qaeventId); + if (qaevent == null) { + throw new ResourceNotFoundException("No such qa event: " + qaeventId); } - if (nbevent.getRelated() != null) { - nbevent.setRelated(null); - nbEventService.store(context, nbevent); + if (qaevent.getRelated() != null) { + qaevent.setRelated(null); + qaEventService.store(context, qaevent); context.complete(); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/NBEventConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/QAEventConverter.java similarity index 76% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/NBEventConverter.java rename to dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/QAEventConverter.java index 1acbbf51bbb9..06c83d0d4e87 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/NBEventConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/QAEventConverter.java @@ -13,36 +13,36 @@ import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.json.JsonMapper; -import org.dspace.app.nbevent.service.dto.NBMessageDTO; -import org.dspace.app.nbevent.service.dto.OpenaireMessageDTO; -import org.dspace.app.rest.model.NBEventMessageRest; -import org.dspace.app.rest.model.NBEventRest; -import org.dspace.app.rest.model.OpenaireNBEventMessageRest; +import org.dspace.app.rest.model.OpenaireQAEventMessageRest; +import org.dspace.app.rest.model.QAEventMessageRest; +import org.dspace.app.rest.model.QAEventRest; import org.dspace.app.rest.projection.Projection; -import org.dspace.content.NBEvent; +import org.dspace.content.QAEvent; +import org.dspace.qaevent.service.dto.OpenaireMessageDTO; +import org.dspace.qaevent.service.dto.QAMessageDTO; import org.springframework.stereotype.Component; /** - * Implementation of {@link DSpaceConverter} that converts {@link NBEvent} to - * {@link NBEventRest}. + * Implementation of {@link DSpaceConverter} that converts {@link QAEvent} to + * {@link QAEventRest}. * * @author Andrea Bollini (andrea.bollini at 4science.it) * */ @Component -public class NBEventConverter implements DSpaceConverter { +public class QAEventConverter implements DSpaceConverter { private ObjectMapper jsonMapper; - public NBEventConverter() { + public QAEventConverter() { super(); jsonMapper = new JsonMapper(); jsonMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); } @Override - public NBEventRest convert(NBEvent modelObject, Projection projection) { - NBEventRest rest = new NBEventRest(); + public QAEventRest convert(QAEvent modelObject, Projection projection) { + QAEventRest rest = new QAEventRest(); rest.setId(modelObject.getEventId()); try { rest.setMessage(convertMessage(jsonMapper.readValue(modelObject.getMessage(), @@ -56,15 +56,15 @@ public NBEventRest convert(NBEvent modelObject, Projection projection) { rest.setTopic(modelObject.getTopic()); rest.setEventDate(modelObject.getLastUpdate()); rest.setTrust(new DecimalFormat("0.000").format(modelObject.getTrust())); - // right now only the pending status can be found in persisted nb events + // right now only the pending status can be found in persisted qa events rest.setStatus(modelObject.getStatus()); return rest; } - private NBEventMessageRest convertMessage(NBMessageDTO dto) { + private QAEventMessageRest convertMessage(QAMessageDTO dto) { if (dto instanceof OpenaireMessageDTO) { OpenaireMessageDTO openaireDto = (OpenaireMessageDTO) dto; - OpenaireNBEventMessageRest message = new OpenaireNBEventMessageRest(); + OpenaireQAEventMessageRest message = new OpenaireQAEventMessageRest(); message.setAbstractValue(openaireDto.getAbstracts()); message.setOpenaireId(openaireDto.getOpenaireId()); message.setAcronym(openaireDto.getAcronym()); @@ -82,8 +82,8 @@ private NBEventMessageRest convertMessage(NBMessageDTO dto) { } @Override - public Class getModelClass() { - return NBEvent.class; + public Class getModelClass() { + return QAEvent.class; } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/NBSourceConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/QASourceConverter.java similarity index 66% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/NBSourceConverter.java rename to dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/QASourceConverter.java index a1b496df048d..6c1bc0d66cb7 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/NBSourceConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/QASourceConverter.java @@ -7,29 +7,29 @@ */ package org.dspace.app.rest.converter; -import org.dspace.app.nbevent.NBSource; -import org.dspace.app.rest.model.NBSourceRest; +import org.dspace.app.rest.model.QASourceRest; import org.dspace.app.rest.projection.Projection; +import org.dspace.qaevent.QASource; import org.springframework.stereotype.Component; /** - * Implementation of {@link DSpaceConverter} that converts {@link NBSource} to - * {@link NBSourceRest}. + * Implementation of {@link DSpaceConverter} that converts {@link QASource} to + * {@link QASourceRest}. * * @author Luca Giamminonni (luca.giamminonni at 4science.it) * */ @Component -public class NBSourceConverter implements DSpaceConverter { +public class QASourceConverter implements DSpaceConverter { @Override - public Class getModelClass() { - return NBSource.class; + public Class getModelClass() { + return QASource.class; } @Override - public NBSourceRest convert(NBSource modelObject, Projection projection) { - NBSourceRest rest = new NBSourceRest(); + public QASourceRest convert(QASource modelObject, Projection projection) { + QASourceRest rest = new QASourceRest(); rest.setProjection(projection); rest.setId(modelObject.getName()); rest.setLastEvent(modelObject.getLastEvent()); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/NBTopicConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/QATopicConverter.java similarity index 68% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/NBTopicConverter.java rename to dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/QATopicConverter.java index f9ab34da89b6..efa32baba224 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/NBTopicConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/QATopicConverter.java @@ -7,29 +7,29 @@ */ package org.dspace.app.rest.converter; -import org.dspace.app.nbevent.NBTopic; -import org.dspace.app.rest.model.NBTopicRest; +import org.dspace.app.rest.model.QATopicRest; import org.dspace.app.rest.projection.Projection; +import org.dspace.qaevent.QATopic; import org.springframework.stereotype.Component; /** - * Implementation of {@link DSpaceConverter} that converts {@link NBTopic} to - * {@link NBTopicRest}. + * Implementation of {@link DSpaceConverter} that converts {@link QATopic} to + * {@link QATopicRest}. * * @author Andrea Bollini (andrea.bollini at 4science.it) * */ @Component -public class NBTopicConverter implements DSpaceConverter { +public class QATopicConverter implements DSpaceConverter { @Override - public Class getModelClass() { - return NBTopic.class; + public Class getModelClass() { + return QATopic.class; } @Override - public NBTopicRest convert(NBTopic modelObject, Projection projection) { - NBTopicRest rest = new NBTopicRest(); + public QATopicRest convert(QATopic modelObject, Projection projection) { + QATopicRest rest = new QATopicRest(); rest.setProjection(projection); rest.setId(modelObject.getKey().replace("/", "!")); rest.setName(modelObject.getKey()); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/OpenaireNBEventMessageRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/OpenaireQAEventMessageRest.java similarity index 94% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/model/OpenaireNBEventMessageRest.java rename to dspace-server-webapp/src/main/java/org/dspace/app/rest/model/OpenaireQAEventMessageRest.java index ca6ee5d06d42..c8c4c889090a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/OpenaireNBEventMessageRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/OpenaireQAEventMessageRest.java @@ -10,12 +10,12 @@ import com.fasterxml.jackson.annotation.JsonProperty; /** - * Implementation of {@link NBEventMessageRest} related to OPENAIRE events. + * Implementation of {@link QAEventMessageRest} related to OPENAIRE events. * * @author Luca Giamminonni (luca.giamminonni at 4science.it) * */ -public class OpenaireNBEventMessageRest implements NBEventMessageRest { +public class OpenaireQAEventMessageRest implements QAEventMessageRest { // pids private String type; private String value; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/NBEventMessageRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/QAEventMessageRest.java similarity index 88% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/model/NBEventMessageRest.java rename to dspace-server-webapp/src/main/java/org/dspace/app/rest/model/QAEventMessageRest.java index df6187651c2a..cc460f604c68 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/NBEventMessageRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/QAEventMessageRest.java @@ -8,11 +8,11 @@ package org.dspace.app.rest.model; /** - * Interface for classes that model a message with the details of a NB event. + * Interface for classes that model a message with the details of a QA event. * * @author Luca Giamminonni (luca.giamminonni at 4science.it) * */ -public interface NBEventMessageRest { +public interface QAEventMessageRest { } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/NBEventRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/QAEventRest.java similarity index 90% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/model/NBEventRest.java rename to dspace-server-webapp/src/main/java/org/dspace/app/rest/model/QAEventRest.java index 0ccc1a55da3b..e02755d2ecbf 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/NBEventRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/QAEventRest.java @@ -12,7 +12,7 @@ import org.dspace.app.rest.RestResourceController; /** - * NB event Rest object. + * QA event Rest object. * * @author Andrea Bollini (andrea.bollini at 4science.it) * @@ -23,10 +23,10 @@ @LinkRest(name = "target", method = "getTarget"), @LinkRest(name = "related", method = "getRelated") }) -public class NBEventRest extends BaseObjectRest { +public class QAEventRest extends BaseObjectRest { private static final long serialVersionUID = -5001130073350654793L; - public static final String NAME = "nbevent"; + public static final String NAME = "qaevent"; public static final String CATEGORY = RestAddressableModel.INTEGRATION; public static final String TOPIC = "topic"; @@ -38,7 +38,7 @@ public class NBEventRest extends BaseObjectRest { private String topic; private String trust; private Date eventDate; - private NBEventMessageRest message; + private QAEventMessageRest message; private String status; @Override @@ -104,11 +104,11 @@ public void setEventDate(Date eventDate) { this.eventDate = eventDate; } - public NBEventMessageRest getMessage() { + public QAEventMessageRest getMessage() { return message; } - public void setMessage(NBEventMessageRest message) { + public void setMessage(QAEventMessageRest message) { this.message = message; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/NBSourceRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/QASourceRest.java similarity index 88% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/model/NBSourceRest.java rename to dspace-server-webapp/src/main/java/org/dspace/app/rest/model/QASourceRest.java index 69e230f37835..15c8096e02dd 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/NBSourceRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/QASourceRest.java @@ -12,16 +12,16 @@ import org.dspace.app.rest.RestResourceController; /** - * REST Representation of a notification broker source + * REST Representation of a quality assurance broker source * * @author Luca Giamminonni (luca.giamminonni at 4Science) * */ -public class NBSourceRest extends BaseObjectRest { +public class QASourceRest extends BaseObjectRest { private static final long serialVersionUID = -7455358581579629244L; - public static final String NAME = "nbsource"; + public static final String NAME = "qasource"; public static final String CATEGORY = RestAddressableModel.INTEGRATION; private String id; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/NBTopicRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/QATopicRest.java similarity index 89% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/model/NBTopicRest.java rename to dspace-server-webapp/src/main/java/org/dspace/app/rest/model/QATopicRest.java index 4bebce27e803..34d5655eb76a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/NBTopicRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/QATopicRest.java @@ -12,16 +12,16 @@ import org.dspace.app.rest.RestResourceController; /** - * REST Representation of a notification broker topic + * REST Representation of a quality assurance broker topic * * @author Andrea Bollini (andrea.bollini at 4science.it) * */ -public class NBTopicRest extends BaseObjectRest { +public class QATopicRest extends BaseObjectRest { private static final long serialVersionUID = -7455358581579629244L; - public static final String NAME = "nbtopic"; + public static final String NAME = "qatopic"; public static final String CATEGORY = RestAddressableModel.INTEGRATION; private String id; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/NBEventResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/QAEventResource.java similarity index 67% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/NBEventResource.java rename to dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/QAEventResource.java index b052d3d4da07..43e1584a2545 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/NBEventResource.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/QAEventResource.java @@ -7,20 +7,20 @@ */ package org.dspace.app.rest.model.hateoas; -import org.dspace.app.rest.model.NBEventRest; +import org.dspace.app.rest.model.QAEventRest; import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource; import org.dspace.app.rest.utils.Utils; /** - * NB event Rest resource. + * QA event Rest resource. * * @author Andrea Bollini (andrea.bollini at 4science.it) * */ -@RelNameDSpaceResource(NBEventRest.NAME) -public class NBEventResource extends DSpaceResource { +@RelNameDSpaceResource(QAEventRest.NAME) +public class QAEventResource extends DSpaceResource { - public NBEventResource(NBEventRest data, Utils utils) { + public QAEventResource(QAEventRest data, Utils utils) { super(data, utils); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/NBSourceResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/QASourceResource.java similarity index 67% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/NBSourceResource.java rename to dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/QASourceResource.java index 55db5d6343d0..860e16cee3cc 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/NBSourceResource.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/QASourceResource.java @@ -7,20 +7,20 @@ */ package org.dspace.app.rest.model.hateoas; -import org.dspace.app.rest.model.NBSourceRest; +import org.dspace.app.rest.model.QASourceRest; import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource; import org.dspace.app.rest.utils.Utils; /** - * NB source Rest resource. + * QA source Rest resource. * * @author Luca Giamminonni (luca.giamminonni at 4Science) * */ -@RelNameDSpaceResource(NBSourceRest.NAME) -public class NBSourceResource extends DSpaceResource { +@RelNameDSpaceResource(QASourceRest.NAME) +public class QASourceResource extends DSpaceResource { - public NBSourceResource(NBSourceRest data, Utils utils) { + public QASourceResource(QASourceRest data, Utils utils) { super(data, utils); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/NBTopicResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/QATopicResource.java similarity index 67% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/NBTopicResource.java rename to dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/QATopicResource.java index 78af04a7645b..139d1e59ebc6 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/NBTopicResource.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/QATopicResource.java @@ -7,20 +7,20 @@ */ package org.dspace.app.rest.model.hateoas; -import org.dspace.app.rest.model.NBTopicRest; +import org.dspace.app.rest.model.QATopicRest; import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource; import org.dspace.app.rest.utils.Utils; /** - * NB topic Rest resource. + * QA topic Rest resource. * * @author Andrea Bollini (andrea.bollini at 4science.it) * */ -@RelNameDSpaceResource(NBTopicRest.NAME) -public class NBTopicResource extends DSpaceResource { +@RelNameDSpaceResource(QATopicRest.NAME) +public class QATopicResource extends DSpaceResource { - public NBTopicResource(NBTopicRest data, Utils utils) { + public QATopicResource(QATopicRest data, Utils utils) { super(data, utils); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NBTopicRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NBTopicRestRepository.java deleted file mode 100644 index 53b1a4be6cd8..000000000000 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NBTopicRestRepository.java +++ /dev/null @@ -1,75 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.app.rest.repository; - -import java.util.List; - -import org.dspace.app.nbevent.NBTopic; -import org.dspace.app.nbevent.service.NBEventService; -import org.dspace.app.rest.Parameter; -import org.dspace.app.rest.SearchRestMethod; -import org.dspace.app.rest.model.NBTopicRest; -import org.dspace.core.Context; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.stereotype.Component; - -/** - * Rest repository that handle NB topics. - * - * @author Andrea Bollini (andrea.bollini at 4science.it) - * - */ -@Component(NBTopicRest.CATEGORY + "." + NBTopicRest.NAME) -public class NBTopicRestRepository extends DSpaceRestRepository { - - @Autowired - private NBEventService nbEventService; - - @Override - @PreAuthorize("hasAuthority('ADMIN')") - public NBTopicRest findOne(Context context, String id) { - NBTopic nbTopic = nbEventService.findTopicByTopicId(id); - if (nbTopic == null) { - return null; - } - return converter.toRest(nbTopic, utils.obtainProjection()); - } - - @Override - @PreAuthorize("hasAuthority('ADMIN')") - public Page findAll(Context context, Pageable pageable) { - List nbTopics = nbEventService.findAllTopics(pageable.getOffset(), pageable.getPageSize()); - long count = nbEventService.countTopics(); - if (nbTopics == null) { - return null; - } - return converter.toRestPage(nbTopics, pageable, count, utils.obtainProjection()); - } - - @SearchRestMethod(name = "bySource") - @PreAuthorize("hasAuthority('ADMIN')") - public Page findBySource(Context context, - @Parameter(value = "source", required = true) String source, Pageable pageable) { - List nbTopics = nbEventService.findAllTopicsBySource(source, - pageable.getOffset(), pageable.getPageSize()); - long count = nbEventService.countTopicsBySource(source); - if (nbTopics == null) { - return null; - } - return converter.toRestPage(nbTopics, pageable, count, utils.obtainProjection()); - } - - @Override - public Class getDomainClass() { - return NBTopicRest.class; - } - -} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NBEventRelatedLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventRelatedLinkRepository.java similarity index 72% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NBEventRelatedLinkRepository.java rename to dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventRelatedLinkRepository.java index 901d600c1083..c711d2ec376a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NBEventRelatedLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventRelatedLinkRepository.java @@ -12,14 +12,14 @@ import javax.annotation.Nullable; import javax.servlet.http.HttpServletRequest; -import org.dspace.app.nbevent.service.NBEventService; import org.dspace.app.rest.model.ItemRest; -import org.dspace.app.rest.model.NBEventRest; +import org.dspace.app.rest.model.QAEventRest; import org.dspace.app.rest.projection.Projection; import org.dspace.content.Item; -import org.dspace.content.NBEvent; +import org.dspace.content.QAEvent; import org.dspace.content.service.ItemService; import org.dspace.core.Context; +import org.dspace.qaevent.service.QAEventService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Pageable; import org.springframework.data.rest.webmvc.ResourceNotFoundException; @@ -27,42 +27,42 @@ import org.springframework.stereotype.Component; /** - * Link repository for "related" subresource of a nb event. + * Link repository for "related" subresource of a qa event. * * @author Andrea Bollini (andrea.bollini at 4science.it) * */ -@Component(NBEventRest.CATEGORY + "." + NBEventRest.NAME + "." + NBEventRest.RELATED) -public class NBEventRelatedLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { +@Component(QAEventRest.CATEGORY + "." + QAEventRest.NAME + "." + QAEventRest.RELATED) +public class QAEventRelatedLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { @Autowired - private NBEventService nbEventService; + private QAEventService qaEventService; @Autowired private ItemService itemService; /** - * Returns the item related to the nb event with the given id. This is another + * Returns the item related to the qa event with the given id. This is another * item that should be linked to the target item as part of the correction * * @param request the http servlet request - * @param id the nb event id + * @param id the qa event id * @param pageable the optional pageable * @param projection the projection object - * @return the item rest representation of the secondary item related to nb event + * @return the item rest representation of the secondary item related to qa event */ @PreAuthorize("hasAuthority('ADMIN')") public ItemRest getRelated(@Nullable HttpServletRequest request, String id, @Nullable Pageable pageable, Projection projection) { Context context = obtainContext(); - NBEvent nbEvent = nbEventService.findEventByEventId(id); - if (nbEvent == null) { - throw new ResourceNotFoundException("No nb event with ID: " + id); + QAEvent qaEvent = qaEventService.findEventByEventId(id); + if (qaEvent == null) { + throw new ResourceNotFoundException("No qa event with ID: " + id); } - if (nbEvent.getRelated() == null) { + if (qaEvent.getRelated() == null) { return null; } - UUID itemUuid = UUID.fromString(nbEvent.getRelated()); + UUID itemUuid = UUID.fromString(qaEvent.getRelated()); Item item; try { item = itemService.find(context, itemUuid); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NBEventRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventRestRepository.java similarity index 62% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NBEventRestRepository.java rename to dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventRestRepository.java index f173ebebc92f..110ebe0e1374 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NBEventRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventRestRepository.java @@ -12,21 +12,21 @@ import java.util.UUID; import javax.servlet.http.HttpServletRequest; -import org.dspace.app.nbevent.dao.NBEventsDao; -import org.dspace.app.nbevent.service.NBEventService; import org.dspace.app.rest.Parameter; import org.dspace.app.rest.SearchRestMethod; import org.dspace.app.rest.exception.RepositoryMethodNotImplementedException; -import org.dspace.app.rest.model.NBEventRest; +import org.dspace.app.rest.model.QAEventRest; import org.dspace.app.rest.model.patch.Patch; import org.dspace.app.rest.repository.patch.ResourcePatch; import org.dspace.authorize.AuthorizeException; import org.dspace.content.Item; -import org.dspace.content.NBEvent; +import org.dspace.content.QAEvent; import org.dspace.content.service.ItemService; import org.dspace.core.Context; import org.dspace.eperson.EPerson; import org.dspace.eperson.service.EPersonService; +import org.dspace.qaevent.dao.QAEventsDao; +import org.dspace.qaevent.service.QAEventService; import org.slf4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; @@ -36,21 +36,21 @@ import org.springframework.stereotype.Component; /** - * Rest repository that handle NB events. + * Rest repository that handle QA events. * * @author Andrea Bollini (andrea.bollini at 4science.it) * */ -@Component(NBEventRest.CATEGORY + "." + NBEventRest.NAME) -public class NBEventRestRepository extends DSpaceRestRepository { +@Component(QAEventRest.CATEGORY + "." + QAEventRest.NAME) +public class QAEventRestRepository extends DSpaceRestRepository { final static String ORDER_FIELD = "trust"; @Autowired - private NBEventService nbEventService; + private QAEventService qaEventService; @Autowired - private NBEventsDao nbEventDao; + private QAEventsDao qaEventDao; @Autowired private ItemService itemService; @@ -59,43 +59,43 @@ public class NBEventRestRepository extends DSpaceRestRepository resourcePatch; + private ResourcePatch resourcePatch; - private Logger log = org.slf4j.LoggerFactory.getLogger(NBEventRestRepository.class); + private Logger log = org.slf4j.LoggerFactory.getLogger(QAEventRestRepository.class); @Override @PreAuthorize("hasAuthority('ADMIN')") - public NBEventRest findOne(Context context, String id) { - NBEvent nbEvent = nbEventService.findEventByEventId(id); - if (nbEvent == null) { + public QAEventRest findOne(Context context, String id) { + QAEvent qaEvent = qaEventService.findEventByEventId(id); + if (qaEvent == null) { // HACK check if this request is part of a patch flow - nbEvent = (NBEvent) requestService.getCurrentRequest().getAttribute("patchedNotificationEvent"); - if (nbEvent != null && nbEvent.getEventId().contentEquals(id)) { - return converter.toRest(nbEvent, utils.obtainProjection()); + qaEvent = (QAEvent) requestService.getCurrentRequest().getAttribute("patchedNotificationEvent"); + if (qaEvent != null && qaEvent.getEventId().contentEquals(id)) { + return converter.toRest(qaEvent, utils.obtainProjection()); } else { return null; } } - return converter.toRest(nbEvent, utils.obtainProjection()); + return converter.toRest(qaEvent, utils.obtainProjection()); } @SearchRestMethod(name = "findByTopic") @PreAuthorize("hasAuthority('ADMIN')") - public Page findByTopic(Context context, @Parameter(value = "topic", required = true) String topic, + public Page findByTopic(Context context, @Parameter(value = "topic", required = true) String topic, Pageable pageable) { - List nbEvents = null; + List qaEvents = null; Long count = 0L; boolean ascending = false; if (pageable.getSort() != null && pageable.getSort().getOrderFor(ORDER_FIELD) != null) { ascending = pageable.getSort().getOrderFor(ORDER_FIELD).getDirection() == Direction.ASC; } - nbEvents = nbEventService.findEventsByTopicAndPage(topic, + qaEvents = qaEventService.findEventsByTopicAndPage(topic, pageable.getOffset(), pageable.getPageSize(), ORDER_FIELD, ascending); - count = nbEventService.countEventsByTopic(topic); - if (nbEvents == null) { + count = qaEventService.countEventsByTopic(topic); + if (qaEvents == null) { return null; } - return converter.toRestPage(nbEvents, pageable, count, utils.obtainProjection()); + return converter.toRestPage(qaEvents, pageable, count, utils.obtainProjection()); } @Override @@ -104,29 +104,29 @@ protected void delete(Context context, String id) throws AuthorizeException { try { item = itemService.find(context, UUID.fromString(id)); EPerson eperson = context.getCurrentUser(); - nbEventService.deleteEventByEventId(id); - nbEventDao.storeEvent(context, id, eperson, item); + qaEventService.deleteEventByEventId(id); + qaEventDao.storeEvent(context, id, eperson, item); } catch (SQLException e) { - throw new RuntimeException("Unable to delete NBEvent " + id, e); + throw new RuntimeException("Unable to delete QAEvent " + id, e); } } @Override - public Page findAll(Context context, Pageable pageable) { - throw new RepositoryMethodNotImplementedException(NBEventRest.NAME, "findAll"); + public Page findAll(Context context, Pageable pageable) { + throw new RepositoryMethodNotImplementedException(QAEventRest.NAME, "findAll"); } @Override @PreAuthorize("hasAuthority('ADMIN')") protected void patch(Context context, HttpServletRequest request, String apiCategory, String model, String id, Patch patch) throws SQLException, AuthorizeException { - NBEvent nbEvent = nbEventService.findEventByEventId(id); - resourcePatch.patch(context, nbEvent, patch.getOperations()); + QAEvent qaEvent = qaEventService.findEventByEventId(id); + resourcePatch.patch(context, qaEvent, patch.getOperations()); } @Override - public Class getDomainClass() { - return NBEventRest.class; + public Class getDomainClass() { + return QAEventRest.class; } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NBEventTargetLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventTargetLinkRepository.java similarity index 70% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NBEventTargetLinkRepository.java rename to dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventTargetLinkRepository.java index 76584c41795c..2df3836b9bdf 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NBEventTargetLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventTargetLinkRepository.java @@ -12,14 +12,14 @@ import javax.annotation.Nullable; import javax.servlet.http.HttpServletRequest; -import org.dspace.app.nbevent.service.NBEventService; import org.dspace.app.rest.model.ItemRest; -import org.dspace.app.rest.model.NBEventRest; +import org.dspace.app.rest.model.QAEventRest; import org.dspace.app.rest.projection.Projection; import org.dspace.content.Item; -import org.dspace.content.NBEvent; +import org.dspace.content.QAEvent; import org.dspace.content.service.ItemService; import org.dspace.core.Context; +import org.dspace.qaevent.service.QAEventService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Pageable; import org.springframework.data.rest.webmvc.ResourceNotFoundException; @@ -27,38 +27,38 @@ import org.springframework.stereotype.Component; /** - * Link repository for "target" subresource of a nb event. + * Link repository for "target" subresource of a qa event. * * @author Andrea Bollini (andrea.bollini at 4science.it) * */ -@Component(NBEventRest.CATEGORY + "." + NBEventRest.NAME + "." + NBEventRest.TARGET) -public class NBEventTargetLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { +@Component(QAEventRest.CATEGORY + "." + QAEventRest.NAME + "." + QAEventRest.TARGET) +public class QAEventTargetLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { @Autowired - private NBEventService nbEventService; + private QAEventService qaEventService; @Autowired private ItemService itemService; /** - * Returns the item target of the nb event with the given id. + * Returns the item target of the qa event with the given id. * * @param request the http servlet request - * @param id the nb event id + * @param id the qa event id * @param pageable the optional pageable * @param projection the projection object - * @return the item rest representation of the nb event target + * @return the item rest representation of the qa event target */ @PreAuthorize("hasAuthority('ADMIN')") public ItemRest getTarget(@Nullable HttpServletRequest request, String id, @Nullable Pageable pageable, Projection projection) { Context context = obtainContext(); - NBEvent nbEvent = nbEventService.findEventByEventId(id); - if (nbEvent == null) { - throw new ResourceNotFoundException("No nb event with ID: " + id); + QAEvent qaEvent = qaEventService.findEventByEventId(id); + if (qaEvent == null) { + throw new ResourceNotFoundException("No qa event with ID: " + id); } - UUID itemUuid = UUID.fromString(nbEvent.getTarget()); + UUID itemUuid = UUID.fromString(qaEvent.getTarget()); Item item; try { item = itemService.find(context, itemUuid); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NBEventTopicLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventTopicLinkRepository.java similarity index 61% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NBEventTopicLinkRepository.java rename to dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventTopicLinkRepository.java index 9b3ac3fd5c45..f9ed48442872 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NBEventTopicLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventTopicLinkRepository.java @@ -10,13 +10,13 @@ import javax.annotation.Nullable; import javax.servlet.http.HttpServletRequest; -import org.dspace.app.nbevent.NBTopic; -import org.dspace.app.nbevent.service.NBEventService; -import org.dspace.app.rest.model.NBEventRest; -import org.dspace.app.rest.model.NBTopicRest; +import org.dspace.app.rest.model.QAEventRest; +import org.dspace.app.rest.model.QATopicRest; import org.dspace.app.rest.projection.Projection; -import org.dspace.content.NBEvent; +import org.dspace.content.QAEvent; import org.dspace.core.Context; +import org.dspace.qaevent.QATopic; +import org.dspace.qaevent.service.QAEventService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Pageable; import org.springframework.data.rest.webmvc.ResourceNotFoundException; @@ -24,35 +24,35 @@ import org.springframework.stereotype.Component; /** - * Link repository for "topic" subresource of a nb event. + * Link repository for "topic" subresource of a qa event. * * @author Andrea Bollini (andrea.bollini at 4science.it) * */ -@Component(NBEventRest.CATEGORY + "." + NBEventRest.NAME + "." + NBEventRest.TOPIC) -public class NBEventTopicLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { +@Component(QAEventRest.CATEGORY + "." + QAEventRest.NAME + "." + QAEventRest.TOPIC) +public class QAEventTopicLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { @Autowired - private NBEventService nbEventService; + private QAEventService qaEventService; /** - * Returns the topic of the nb event with the given id. + * Returns the topic of the qa event with the given id. * * @param request the http servlet request - * @param id the nb event id + * @param id the qa event id * @param pageable the optional pageable * @param projection the projection object - * @return the nb topic rest representation + * @return the qa topic rest representation */ @PreAuthorize("hasAuthority('ADMIN')") - public NBTopicRest getTopic(@Nullable HttpServletRequest request, String id, @Nullable Pageable pageable, + public QATopicRest getTopic(@Nullable HttpServletRequest request, String id, @Nullable Pageable pageable, Projection projection) { Context context = obtainContext(); - NBEvent nbEvent = nbEventService.findEventByEventId(id); - if (nbEvent == null) { - throw new ResourceNotFoundException("No nb event with ID: " + id); + QAEvent qaEvent = qaEventService.findEventByEventId(id); + if (qaEvent == null) { + throw new ResourceNotFoundException("No qa event with ID: " + id); } - NBTopic topic = nbEventService.findTopicByTopicId(nbEvent.getTopic().replace("/", "!")); + QATopic topic = qaEventService.findTopicByTopicId(qaEvent.getTopic().replace("/", "!")); if (topic == null) { throw new ResourceNotFoundException("No topic found with id : " + id); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NBSourceRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QASourceRestRepository.java similarity index 50% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NBSourceRestRepository.java rename to dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QASourceRestRepository.java index 872aa4d43971..dad2310a77c0 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/NBSourceRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QASourceRestRepository.java @@ -9,10 +9,10 @@ import java.util.List; -import org.dspace.app.nbevent.NBSource; -import org.dspace.app.nbevent.service.NBEventService; -import org.dspace.app.rest.model.NBSourceRest; +import org.dspace.app.rest.model.QASourceRest; import org.dspace.core.Context; +import org.dspace.qaevent.QASource; +import org.dspace.qaevent.service.QAEventService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -20,39 +20,39 @@ import org.springframework.stereotype.Component; /** - * Rest repository that handle NB soufces. + * Rest repository that handle QA sources. * * @author Luca Giamminonni (luca.giamminonni at 4science.it) * */ -@Component(NBSourceRest.CATEGORY + "." + NBSourceRest.NAME) -public class NBSourceRestRepository extends DSpaceRestRepository { +@Component(QASourceRest.CATEGORY + "." + QASourceRest.NAME) +public class QASourceRestRepository extends DSpaceRestRepository { @Autowired - private NBEventService nbEventService; + private QAEventService qaEventService; @Override @PreAuthorize("hasAuthority('ADMIN')") - public NBSourceRest findOne(Context context, String id) { - NBSource nbSource = nbEventService.findSource(id); - if (nbSource == null) { + public QASourceRest findOne(Context context, String id) { + QASource qaSource = qaEventService.findSource(id); + if (qaSource == null) { return null; } - return converter.toRest(nbSource, utils.obtainProjection()); + return converter.toRest(qaSource, utils.obtainProjection()); } @Override @PreAuthorize("hasAuthority('ADMIN')") - public Page findAll(Context context, Pageable pageable) { - List nbSources = nbEventService.findAllSources(pageable.getOffset(), pageable.getPageSize()); - long count = nbEventService.countSources(); - return converter.toRestPage(nbSources, pageable, count, utils.obtainProjection()); + public Page findAll(Context context, Pageable pageable) { + List qaSources = qaEventService.findAllSources(pageable.getOffset(), pageable.getPageSize()); + long count = qaEventService.countSources(); + return converter.toRestPage(qaSources, pageable, count, utils.obtainProjection()); } @Override - public Class getDomainClass() { - return NBSourceRest.class; + public Class getDomainClass() { + return QASourceRest.class; } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QATopicRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QATopicRestRepository.java new file mode 100644 index 000000000000..a279cac83a4e --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QATopicRestRepository.java @@ -0,0 +1,75 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.repository; + +import java.util.List; + +import org.dspace.app.rest.Parameter; +import org.dspace.app.rest.SearchRestMethod; +import org.dspace.app.rest.model.QATopicRest; +import org.dspace.core.Context; +import org.dspace.qaevent.QATopic; +import org.dspace.qaevent.service.QAEventService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Component; + +/** + * Rest repository that handle QA topics. + * + * @author Andrea Bollini (andrea.bollini at 4science.it) + * + */ +@Component(QATopicRest.CATEGORY + "." + QATopicRest.NAME) +public class QATopicRestRepository extends DSpaceRestRepository { + + @Autowired + private QAEventService qaEventService; + + @Override + @PreAuthorize("hasAuthority('ADMIN')") + public QATopicRest findOne(Context context, String id) { + QATopic topic = qaEventService.findTopicByTopicId(id); + if (topic == null) { + return null; + } + return converter.toRest(topic, utils.obtainProjection()); + } + + @Override + @PreAuthorize("hasAuthority('ADMIN')") + public Page findAll(Context context, Pageable pageable) { + List topics = qaEventService.findAllTopics(pageable.getOffset(), pageable.getPageSize()); + long count = qaEventService.countTopics(); + if (topics == null) { + return null; + } + return converter.toRestPage(topics, pageable, count, utils.obtainProjection()); + } + + @SearchRestMethod(name = "bySource") + @PreAuthorize("hasAuthority('ADMIN')") + public Page findBySource(Context context, + @Parameter(value = "source", required = true) String source, Pageable pageable) { + List topics = qaEventService.findAllTopicsBySource(source, + pageable.getOffset(), pageable.getPageSize()); + long count = qaEventService.countTopicsBySource(source); + if (topics == null) { + return null; + } + return converter.toRestPage(topics, pageable, count, utils.obtainProjection()); + } + + @Override + public Class getDomainClass() { + return QATopicRest.class; + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/NBEventStatusReplaceOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/QAEventStatusReplaceOperation.java similarity index 60% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/NBEventStatusReplaceOperation.java rename to dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/QAEventStatusReplaceOperation.java index 55bfe3d2f13c..5f58f014ab6c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/NBEventStatusReplaceOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/QAEventStatusReplaceOperation.java @@ -10,51 +10,51 @@ import java.sql.SQLException; import org.apache.commons.lang3.StringUtils; -import org.dspace.app.nbevent.NBEventActionService; import org.dspace.app.rest.model.patch.Operation; -import org.dspace.content.NBEvent; +import org.dspace.content.QAEvent; import org.dspace.core.Context; +import org.dspace.qaevent.QAEventActionService; import org.dspace.services.RequestService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; /** - * Replace operation related to the {@link NBEvent} status. + * Replace operation related to the {@link QAEvent} status. * * @author Andrea Bollini (andrea.bollini at 4science.it) * */ @Component -public class NBEventStatusReplaceOperation extends PatchOperation { +public class QAEventStatusReplaceOperation extends PatchOperation { @Autowired private RequestService requestService; @Autowired - private NBEventActionService nbEventActionService; + private QAEventActionService qaEventActionService; @Override - public NBEvent perform(Context context, NBEvent nbevent, Operation operation) throws SQLException { + public QAEvent perform(Context context, QAEvent qaevent, Operation operation) throws SQLException { String value = (String) operation.getValue(); - if (StringUtils.equalsIgnoreCase(value, NBEvent.ACCEPTED)) { - nbEventActionService.accept(context, nbevent); - } else if (StringUtils.equalsIgnoreCase(value, NBEvent.REJECTED)) { - nbEventActionService.reject(context, nbevent); - } else if (StringUtils.equalsIgnoreCase(value, NBEvent.DISCARDED)) { - nbEventActionService.discard(context, nbevent); + if (StringUtils.equalsIgnoreCase(value, QAEvent.ACCEPTED)) { + qaEventActionService.accept(context, qaevent); + } else if (StringUtils.equalsIgnoreCase(value, QAEvent.REJECTED)) { + qaEventActionService.reject(context, qaevent); + } else if (StringUtils.equalsIgnoreCase(value, QAEvent.DISCARDED)) { + qaEventActionService.discard(context, qaevent); } else { throw new IllegalArgumentException( "The received operation is not valid: " + operation.getPath() + " - " + value); } - nbevent.setStatus(value.toUpperCase()); + qaevent.setStatus(value.toUpperCase()); // HACK, we need to store the temporary object in the request so that a subsequent find would get it - requestService.getCurrentRequest().setAttribute("patchedNotificationEvent", nbevent); - return nbevent; + requestService.getCurrentRequest().setAttribute("patchedNotificationEvent", qaevent); + return qaevent; } @Override public boolean supports(Object objectToMatch, Operation operation) { - return StringUtils.equals(operation.getOp(), "replace") && objectToMatch instanceof NBEvent && StringUtils - .containsAny(operation.getValue().toString().toLowerCase(), NBEvent.ACCEPTED, NBEvent.DISCARDED, - NBEvent.REJECTED); + return StringUtils.equals(operation.getOp(), "replace") && objectToMatch instanceof QAEvent && StringUtils + .containsAny(operation.getValue().toString().toLowerCase(), QAEvent.ACCEPTED, QAEvent.DISCARDED, + QAEvent.REJECTED); } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/NBEventRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/QAEventRestRepositoryIT.java similarity index 80% rename from dspace-server-webapp/src/test/java/org/dspace/app/rest/NBEventRestRepositoryIT.java rename to dspace-server-webapp/src/test/java/org/dspace/app/rest/QAEventRestRepositoryIT.java index a9f5d294279e..5ccbc98d95e3 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/NBEventRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/QAEventRestRepositoryIT.java @@ -25,7 +25,7 @@ import javax.ws.rs.core.MediaType; import org.dspace.app.rest.matcher.ItemMatcher; -import org.dspace.app.rest.matcher.NBEventMatcher; +import org.dspace.app.rest.matcher.QAEventMatcher; import org.dspace.app.rest.model.patch.Operation; import org.dspace.app.rest.model.patch.ReplaceOperation; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; @@ -33,30 +33,30 @@ import org.dspace.builder.CommunityBuilder; import org.dspace.builder.EntityTypeBuilder; import org.dspace.builder.ItemBuilder; -import org.dspace.builder.NBEventBuilder; +import org.dspace.builder.QAEventBuilder; import org.dspace.builder.RelationshipTypeBuilder; import org.dspace.content.Collection; import org.dspace.content.EntityType; import org.dspace.content.Item; -import org.dspace.content.NBEvent; +import org.dspace.content.QAEvent; import org.hamcrest.Matchers; import org.junit.Test; /** - * Integration tests for {@link NBEventRestRepository}. + * Integration tests for {@link QAEventRestRepository}. * * @author Andrea Bollini (andrea.bollini at 4science.it) * */ -public class NBEventRestRepositoryIT extends AbstractControllerIntegrationTest { +public class QAEventRestRepositoryIT extends AbstractControllerIntegrationTest { @Test public void findAllNotImplementedTest() throws Exception { String adminToken = getAuthToken(admin.getEmail(), password); - getClient(adminToken).perform(get("/api/integration/nbevents")).andExpect(status().isMethodNotAllowed()); + getClient(adminToken).perform(get("/api/integration/qaevents")).andExpect(status().isMethodNotAllowed()); String epersonToken = getAuthToken(admin.getEmail(), password); - getClient(epersonToken).perform(get("/api/integration/nbevents")).andExpect(status().isMethodNotAllowed()); - getClient().perform(get("/api/integration/nbevents")).andExpect(status().isMethodNotAllowed()); + getClient(epersonToken).perform(get("/api/integration/qaevents")).andExpect(status().isMethodNotAllowed()); + getClient().perform(get("/api/integration/qaevents")).andExpect(status().isMethodNotAllowed()); } @Test @@ -64,18 +64,18 @@ public void findOneTest() throws Exception { context.turnOffAuthorisationSystem(); parentCommunity = CommunityBuilder.createCommunity(context).withName("Parent Community").build(); Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build(); - NBEvent event1 = NBEventBuilder.createTarget(context, col1, "Science and Freedom") + QAEvent event1 = QAEventBuilder.createTarget(context, col1, "Science and Freedom") .withTopic("ENRICH/MISSING/PID") .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144300\"}").build(); - NBEvent event4 = NBEventBuilder.createTarget(context, col1, "Science and Freedom 4") + QAEvent event4 = QAEventBuilder.createTarget(context, col1, "Science and Freedom 4") .withTopic("ENRICH/MISSING/ABSTRACT") .withMessage("{\"abstracts[0]\": \"Descrizione delle caratteristiche...\"}").build(); context.restoreAuthSystemState(); String authToken = getAuthToken(admin.getEmail(), password); - getClient(authToken).perform(get("/api/integration/nbevents/" + event1.getEventId())).andExpect(status().isOk()) - .andExpect(jsonPath("$", NBEventMatcher.matchNBEventEntry(event1))); - getClient(authToken).perform(get("/api/integration/nbevents/" + event4.getEventId())).andExpect(status().isOk()) - .andExpect(jsonPath("$", NBEventMatcher.matchNBEventEntry(event4))); + getClient(authToken).perform(get("/api/integration/qaevents/" + event1.getEventId())).andExpect(status().isOk()) + .andExpect(jsonPath("$", QAEventMatcher.matchQAEventEntry(event1))); + getClient(authToken).perform(get("/api/integration/qaevents/" + event4.getEventId())).andExpect(status().isOk()) + .andExpect(jsonPath("$", QAEventMatcher.matchQAEventEntry(event4))); } @Test @@ -83,10 +83,10 @@ public void findOneWithProjectionTest() throws Exception { context.turnOffAuthorisationSystem(); parentCommunity = CommunityBuilder.createCommunity(context).withName("Parent Community").build(); Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build(); - NBEvent event1 = NBEventBuilder.createTarget(context, col1, "Science and Freedom") + QAEvent event1 = QAEventBuilder.createTarget(context, col1, "Science and Freedom") .withTopic("ENRICH/MISSING/PID") .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144300\"}").build(); - NBEvent event5 = NBEventBuilder.createTarget(context, col1, "Science and Freedom 5") + QAEvent event5 = QAEventBuilder.createTarget(context, col1, "Science and Freedom 5") .withTopic("ENRICH/MISSING/PROJECT") .withMessage( "{\"projects[0].acronym\":\"PAThs\"," @@ -103,13 +103,13 @@ public void findOneWithProjectionTest() throws Exception { context.restoreAuthSystemState(); String authToken = getAuthToken(admin.getEmail(), password); getClient(authToken) - .perform(get("/api/integration/nbevents/" + event1.getEventId()).param("projection", "full")) + .perform(get("/api/integration/qaevents/" + event1.getEventId()).param("projection", "full")) .andExpect(status().isOk()) - .andExpect(jsonPath("$", NBEventMatcher.matchNBEventFullEntry(event1))); + .andExpect(jsonPath("$", QAEventMatcher.matchQAEventFullEntry(event1))); getClient(authToken) - .perform(get("/api/integration/nbevents/" + event5.getEventId()).param("projection", "full")) + .perform(get("/api/integration/qaevents/" + event5.getEventId()).param("projection", "full")) .andExpect(status().isOk()) - .andExpect(jsonPath("$", NBEventMatcher.matchNBEventFullEntry(event5))); + .andExpect(jsonPath("$", QAEventMatcher.matchQAEventFullEntry(event5))); } @Test @@ -117,11 +117,11 @@ public void findOneUnauthorizedTest() throws Exception { context.turnOffAuthorisationSystem(); parentCommunity = CommunityBuilder.createCommunity(context).withName("Parent Community").build(); Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build(); - NBEvent event1 = NBEventBuilder.createTarget(context, col1, "Science and Freedom") + QAEvent event1 = QAEventBuilder.createTarget(context, col1, "Science and Freedom") .withTopic("ENRICH/MISSING/PID") .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144300\"}").build(); context.restoreAuthSystemState(); - getClient().perform(get("/api/integration/nbevents/" + event1.getEventId())) + getClient().perform(get("/api/integration/qaevents/" + event1.getEventId())) .andExpect(status().isUnauthorized()); } @@ -130,12 +130,12 @@ public void findOneForbiddenTest() throws Exception { context.turnOffAuthorisationSystem(); parentCommunity = CommunityBuilder.createCommunity(context).withName("Parent Community").build(); Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build(); - NBEvent event1 = NBEventBuilder.createTarget(context, col1, "Science and Freedom") + QAEvent event1 = QAEventBuilder.createTarget(context, col1, "Science and Freedom") .withTopic("ENRICH/MISSING/PID") .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144300\"}").build(); context.restoreAuthSystemState(); String authToken = getAuthToken(eperson.getEmail(), password); - getClient(authToken).perform(get("/api/integration/nbevents/" + event1.getEventId())) + getClient(authToken).perform(get("/api/integration/qaevents/" + event1.getEventId())) .andExpect(status().isForbidden()); } @@ -144,34 +144,34 @@ public void findByTopicTest() throws Exception { context.turnOffAuthorisationSystem(); parentCommunity = CommunityBuilder.createCommunity(context).withName("Parent Community").build(); Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build(); - NBEvent event1 = NBEventBuilder.createTarget(context, col1, "Science and Freedom") + QAEvent event1 = QAEventBuilder.createTarget(context, col1, "Science and Freedom") .withTopic("ENRICH/MISSING/PID") .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144300\"}").build(); - NBEvent event2 = NBEventBuilder.createTarget(context, col1, "Science and Freedom 2") + QAEvent event2 = QAEventBuilder.createTarget(context, col1, "Science and Freedom 2") .withTopic("ENRICH/MISSING/PID") .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144301\"}").build(); - NBEvent event3 = NBEventBuilder.createTarget(context, col1, "Science and Freedom 3") + QAEvent event3 = QAEventBuilder.createTarget(context, col1, "Science and Freedom 3") .withTopic("ENRICH/MORE/PID") .withMessage("{\"pids[0].type\":\"pmid\",\"pids[0].value\":\"10.2307/2144302\"}").build(); - NBEvent event4 = NBEventBuilder.createTarget(context, col1, "Science and Freedom 4") + QAEvent event4 = QAEventBuilder.createTarget(context, col1, "Science and Freedom 4") .withTopic("ENRICH/MISSING/ABSTRACT") .withMessage("{\"abstracts[0]\": \"Descrizione delle caratteristiche...\"}").build(); context.restoreAuthSystemState(); String authToken = getAuthToken(admin.getEmail(), password); getClient(authToken) - .perform(get("/api/integration/nbevents/search/findByTopic").param("topic", "ENRICH!MISSING!PID")) - .andExpect(status().isOk()).andExpect(jsonPath("$._embedded.nbevents", Matchers.hasSize(2))) - .andExpect(jsonPath("$._embedded.nbevents", - Matchers.containsInAnyOrder(NBEventMatcher.matchNBEventEntry(event1), - NBEventMatcher.matchNBEventEntry(event2)))) + .perform(get("/api/integration/qaevents/search/findByTopic").param("topic", "ENRICH!MISSING!PID")) + .andExpect(status().isOk()).andExpect(jsonPath("$._embedded.qaevents", Matchers.hasSize(2))) + .andExpect(jsonPath("$._embedded.qaevents", + Matchers.containsInAnyOrder(QAEventMatcher.matchQAEventEntry(event1), + QAEventMatcher.matchQAEventEntry(event2)))) .andExpect(jsonPath("$.page.size", is(20))).andExpect(jsonPath("$.page.totalElements", is(2))); getClient(authToken) - .perform(get("/api/integration/nbevents/search/findByTopic").param("topic", "ENRICH!MISSING!ABSTRACT")) - .andExpect(status().isOk()).andExpect(jsonPath("$._embedded.nbevents", Matchers.hasSize(1))) - .andExpect(jsonPath("$._embedded.nbevents", - Matchers.containsInAnyOrder(NBEventMatcher.matchNBEventEntry(event4)))) + .perform(get("/api/integration/qaevents/search/findByTopic").param("topic", "ENRICH!MISSING!ABSTRACT")) + .andExpect(status().isOk()).andExpect(jsonPath("$._embedded.qaevents", Matchers.hasSize(1))) + .andExpect(jsonPath("$._embedded.qaevents", + Matchers.containsInAnyOrder(QAEventMatcher.matchQAEventEntry(event4)))) .andExpect(jsonPath("$.page.size", is(20))).andExpect(jsonPath("$.page.totalElements", is(1))); - getClient(authToken).perform(get("/api/integration/nbevents/search/findByTopic").param("topic", "not-existing")) + getClient(authToken).perform(get("/api/integration/qaevents/search/findByTopic").param("topic", "not-existing")) .andExpect(status().isOk()).andExpect(jsonPath("$.page.size", is(20))) .andExpect(jsonPath("$.page.totalElements", is(0))); } @@ -181,49 +181,49 @@ public void findByTopicPaginatedTest() throws Exception { context.turnOffAuthorisationSystem(); parentCommunity = CommunityBuilder.createCommunity(context).withName("Parent Community").build(); Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build(); - NBEvent event1 = NBEventBuilder.createTarget(context, col1, "Science and Freedom") + QAEvent event1 = QAEventBuilder.createTarget(context, col1, "Science and Freedom") .withTopic("ENRICH/MISSING/PID") .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144300\"}").build(); - NBEvent event2 = NBEventBuilder.createTarget(context, col1, "Science and Freedom 2") + QAEvent event2 = QAEventBuilder.createTarget(context, col1, "Science and Freedom 2") .withTopic("ENRICH/MISSING/PID") .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144301\"}").build(); - NBEvent event3 = NBEventBuilder.createTarget(context, col1, "Science and Freedom 3") + QAEvent event3 = QAEventBuilder.createTarget(context, col1, "Science and Freedom 3") .withTopic("ENRICH/MISSING/PID") .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144302\"}").build(); - NBEvent event4 = NBEventBuilder.createTarget(context, col1, "Science and Freedom 4") + QAEvent event4 = QAEventBuilder.createTarget(context, col1, "Science and Freedom 4") .withTopic("ENRICH/MISSING/PID") .withMessage("{\"pids[0].type\":\"pmc\",\"pids[0].value\":\"2144303\"}").build(); - NBEvent event5 = NBEventBuilder.createTarget(context, col1, "Science and Freedom 5") + QAEvent event5 = QAEventBuilder.createTarget(context, col1, "Science and Freedom 5") .withTopic("ENRICH/MISSING/PID") .withMessage("{\"pids[0].type\":\"pmid\",\"pids[0].value\":\"2144304\"}").build(); context.restoreAuthSystemState(); String authToken = getAuthToken(admin.getEmail(), password); getClient(authToken) - .perform(get("/api/integration/nbevents/search/findByTopic").param("topic", "ENRICH!MISSING!PID") + .perform(get("/api/integration/qaevents/search/findByTopic").param("topic", "ENRICH!MISSING!PID") .param("size", "2")) - .andExpect(status().isOk()).andExpect(jsonPath("$._embedded.nbevents", Matchers.hasSize(2))) - .andExpect(jsonPath("$._embedded.nbevents", + .andExpect(status().isOk()).andExpect(jsonPath("$._embedded.qaevents", Matchers.hasSize(2))) + .andExpect(jsonPath("$._embedded.qaevents", Matchers.containsInAnyOrder( - NBEventMatcher.matchNBEventEntry(event1), - NBEventMatcher.matchNBEventEntry(event2)))) + QAEventMatcher.matchQAEventEntry(event1), + QAEventMatcher.matchQAEventEntry(event2)))) .andExpect(jsonPath("$._links.self.href", Matchers.allOf( - Matchers.containsString("/api/integration/nbevents/search/findByTopic?"), + Matchers.containsString("/api/integration/qaevents/search/findByTopic?"), Matchers.containsString("topic=ENRICH!MISSING!PID"), Matchers.containsString("size=2")))) .andExpect(jsonPath("$._links.next.href", Matchers.allOf( - Matchers.containsString("/api/integration/nbevents/search/findByTopic?"), + Matchers.containsString("/api/integration/qaevents/search/findByTopic?"), Matchers.containsString("topic=ENRICH!MISSING!PID"), Matchers.containsString("page=1"), Matchers.containsString("size=2")))) .andExpect(jsonPath("$._links.last.href", Matchers.allOf( - Matchers.containsString("/api/integration/nbevents/search/findByTopic?"), + Matchers.containsString("/api/integration/qaevents/search/findByTopic?"), Matchers.containsString("topic=ENRICH!MISSING!PID"), Matchers.containsString("page=2"), Matchers.containsString("size=2")))) .andExpect(jsonPath("$._links.first.href", Matchers.allOf( - Matchers.containsString("/api/integration/nbevents/search/findByTopic?"), + Matchers.containsString("/api/integration/qaevents/search/findByTopic?"), Matchers.containsString("topic=ENRICH!MISSING!PID"), Matchers.containsString("page=0"), Matchers.containsString("size=2")))) .andExpect(jsonPath("$._links.prev.href").doesNotExist()) @@ -232,36 +232,36 @@ public void findByTopicPaginatedTest() throws Exception { .andExpect(jsonPath("$.page.totalElements", is(5))); getClient(authToken) - .perform(get("/api/integration/nbevents/search/findByTopic").param("topic", "ENRICH!MISSING!PID") + .perform(get("/api/integration/qaevents/search/findByTopic").param("topic", "ENRICH!MISSING!PID") .param("size", "2").param("page", "1")) - .andExpect(status().isOk()).andExpect(jsonPath("$._embedded.nbevents", Matchers.hasSize(2))) - .andExpect(jsonPath("$._embedded.nbevents", + .andExpect(status().isOk()).andExpect(jsonPath("$._embedded.qaevents", Matchers.hasSize(2))) + .andExpect(jsonPath("$._embedded.qaevents", Matchers.containsInAnyOrder( - NBEventMatcher.matchNBEventEntry(event3), - NBEventMatcher.matchNBEventEntry(event4)))) + QAEventMatcher.matchQAEventEntry(event3), + QAEventMatcher.matchQAEventEntry(event4)))) .andExpect(jsonPath("$._links.self.href", Matchers.allOf( - Matchers.containsString("/api/integration/nbevents/search/findByTopic?"), + Matchers.containsString("/api/integration/qaevents/search/findByTopic?"), Matchers.containsString("topic=ENRICH!MISSING!PID"), Matchers.containsString("page=1"), Matchers.containsString("size=2")))) .andExpect(jsonPath("$._links.next.href", Matchers.allOf( - Matchers.containsString("/api/integration/nbevents/search/findByTopic?"), + Matchers.containsString("/api/integration/qaevents/search/findByTopic?"), Matchers.containsString("topic=ENRICH!MISSING!PID"), Matchers.containsString("page=2"), Matchers.containsString("size=2")))) .andExpect(jsonPath("$._links.last.href", Matchers.allOf( - Matchers.containsString("/api/integration/nbevents/search/findByTopic?"), + Matchers.containsString("/api/integration/qaevents/search/findByTopic?"), Matchers.containsString("topic=ENRICH!MISSING!PID"), Matchers.containsString("page=2"), Matchers.containsString("size=2")))) .andExpect(jsonPath("$._links.first.href", Matchers.allOf( - Matchers.containsString("/api/integration/nbevents/search/findByTopic?"), + Matchers.containsString("/api/integration/qaevents/search/findByTopic?"), Matchers.containsString("topic=ENRICH!MISSING!PID"), Matchers.containsString("page=0"), Matchers.containsString("size=2")))) .andExpect(jsonPath("$._links.prev.href", Matchers.allOf( - Matchers.containsString("/api/integration/nbevents/search/findByTopic?"), + Matchers.containsString("/api/integration/qaevents/search/findByTopic?"), Matchers.containsString("topic=ENRICH!MISSING!PID"), Matchers.containsString("page=0"), Matchers.containsString("size=2")))) .andExpect(jsonPath("$.page.size", is(2))) @@ -269,31 +269,31 @@ public void findByTopicPaginatedTest() throws Exception { .andExpect(jsonPath("$.page.totalElements", is(5))); getClient(authToken) - .perform(get("/api/integration/nbevents/search/findByTopic").param("topic", "ENRICH!MISSING!PID") + .perform(get("/api/integration/qaevents/search/findByTopic").param("topic", "ENRICH!MISSING!PID") .param("size", "2").param("page", "2")) - .andExpect(status().isOk()).andExpect(jsonPath("$._embedded.nbevents", Matchers.hasSize(1))) - .andExpect(jsonPath("$._embedded.nbevents", + .andExpect(status().isOk()).andExpect(jsonPath("$._embedded.qaevents", Matchers.hasSize(1))) + .andExpect(jsonPath("$._embedded.qaevents", Matchers.containsInAnyOrder( - NBEventMatcher.matchNBEventEntry(event5)))) + QAEventMatcher.matchQAEventEntry(event5)))) .andExpect(jsonPath("$._links.self.href", Matchers.allOf( - Matchers.containsString("/api/integration/nbevents/search/findByTopic?"), + Matchers.containsString("/api/integration/qaevents/search/findByTopic?"), Matchers.containsString("topic=ENRICH!MISSING!PID"), Matchers.containsString("page=2"), Matchers.containsString("size=2")))) .andExpect(jsonPath("$._links.next.href").doesNotExist()) .andExpect(jsonPath("$._links.last.href", Matchers.allOf( - Matchers.containsString("/api/integration/nbevents/search/findByTopic?"), + Matchers.containsString("/api/integration/qaevents/search/findByTopic?"), Matchers.containsString("topic=ENRICH!MISSING!PID"), Matchers.containsString("page=2"), Matchers.containsString("size=2")))) .andExpect(jsonPath("$._links.first.href", Matchers.allOf( - Matchers.containsString("/api/integration/nbevents/search/findByTopic?"), + Matchers.containsString("/api/integration/qaevents/search/findByTopic?"), Matchers.containsString("topic=ENRICH!MISSING!PID"), Matchers.containsString("page=0"), Matchers.containsString("size=2")))) .andExpect(jsonPath("$._links.prev.href", Matchers.allOf( - Matchers.containsString("/api/integration/nbevents/search/findByTopic?"), + Matchers.containsString("/api/integration/qaevents/search/findByTopic?"), Matchers.containsString("topic=ENRICH!MISSING!PID"), Matchers.containsString("page=1"), Matchers.containsString("size=2")))) .andExpect(jsonPath("$.page.size", is(2))) @@ -307,20 +307,20 @@ public void findByTopicUnauthorizedTest() throws Exception { context.turnOffAuthorisationSystem(); parentCommunity = CommunityBuilder.createCommunity(context).withName("Parent Community").build(); Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build(); - NBEvent event1 = NBEventBuilder.createTarget(context, col1, "Science and Freedom") + QAEvent event1 = QAEventBuilder.createTarget(context, col1, "Science and Freedom") .withTopic("ENRICH/MISSING/PID") .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144300\"}").build(); - NBEvent event2 = NBEventBuilder.createTarget(context, col1, "Science and Freedom 2") + QAEvent event2 = QAEventBuilder.createTarget(context, col1, "Science and Freedom 2") .withTopic("ENRICH/MISSING/PID") .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144301\"}").build(); - NBEvent event3 = NBEventBuilder.createTarget(context, col1, "Science and Freedom 3") + QAEvent event3 = QAEventBuilder.createTarget(context, col1, "Science and Freedom 3") .withTopic("ENRICH/MORE/PID") .withMessage("{\"pids[0].type\":\"pmid\",\"pids[0].value\":\"10.2307/2144302\"}").build(); - NBEvent event4 = NBEventBuilder.createTarget(context, col1, "Science and Freedom 4") + QAEvent event4 = QAEventBuilder.createTarget(context, col1, "Science and Freedom 4") .withTopic("ENRICH/MISSING/ABSTRACT") .withMessage("{\"abstracts[0]\": \"Descrizione delle caratteristiche...\"}").build(); context.restoreAuthSystemState(); - getClient().perform(get("/api/integration/nbevents/search/findByTopic").param("topic", "ENRICH!MISSING!PID")) + getClient().perform(get("/api/integration/qaevents/search/findByTopic").param("topic", "ENRICH!MISSING!PID")) .andExpect(status().isUnauthorized()); } @@ -329,22 +329,22 @@ public void findByTopicForbiddenTest() throws Exception { context.turnOffAuthorisationSystem(); parentCommunity = CommunityBuilder.createCommunity(context).withName("Parent Community").build(); Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build(); - NBEvent event1 = NBEventBuilder.createTarget(context, col1, "Science and Freedom") + QAEvent event1 = QAEventBuilder.createTarget(context, col1, "Science and Freedom") .withTopic("ENRICH/MISSING/PID") .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144300\"}").build(); - NBEvent event2 = NBEventBuilder.createTarget(context, col1, "Science and Freedom 2") + QAEvent event2 = QAEventBuilder.createTarget(context, col1, "Science and Freedom 2") .withTopic("ENRICH/MISSING/PID") .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144301\"}").build(); - NBEvent event3 = NBEventBuilder.createTarget(context, col1, "Science and Freedom 3") + QAEvent event3 = QAEventBuilder.createTarget(context, col1, "Science and Freedom 3") .withTopic("ENRICH/MORE/PID") .withMessage("{\"pids[0].type\":\"pmid\",\"pids[0].value\":\"10.2307/2144302\"}").build(); - NBEvent event4 = NBEventBuilder.createTarget(context, col1, "Science and Freedom 4") + QAEvent event4 = QAEventBuilder.createTarget(context, col1, "Science and Freedom 4") .withTopic("ENRICH/MISSING/ABSTRACT") .withMessage("{\"abstracts[0]\": \"Descrizione delle caratteristiche...\"}").build(); context.restoreAuthSystemState(); String epersonToken = getAuthToken(eperson.getEmail(), password); getClient(epersonToken) - .perform(get("/api/integration/nbevents/search/findByTopic").param("topic", "ENRICH!MISSING!PID")) + .perform(get("/api/integration/qaevents/search/findByTopic").param("topic", "ENRICH!MISSING!PID")) .andExpect(status().isForbidden()); } @@ -353,21 +353,21 @@ public void findByTopicBadRequestTest() throws Exception { context.turnOffAuthorisationSystem(); parentCommunity = CommunityBuilder.createCommunity(context).withName("Parent Community").build(); Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build(); - NBEvent event1 = NBEventBuilder.createTarget(context, col1, "Science and Freedom") + QAEvent event1 = QAEventBuilder.createTarget(context, col1, "Science and Freedom") .withTopic("ENRICH/MISSING/PID") .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144300\"}").build(); - NBEvent event2 = NBEventBuilder.createTarget(context, col1, "Science and Freedom 2") + QAEvent event2 = QAEventBuilder.createTarget(context, col1, "Science and Freedom 2") .withTopic("ENRICH/MISSING/PID") .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144301\"}").build(); - NBEvent event3 = NBEventBuilder.createTarget(context, col1, "Science and Freedom 3") + QAEvent event3 = QAEventBuilder.createTarget(context, col1, "Science and Freedom 3") .withTopic("ENRICH/MORE/PID") .withMessage("{\"pids[0].type\":\"pmid\",\"pids[0].value\":\"10.2307/2144302\"}").build(); - NBEvent event4 = NBEventBuilder.createTarget(context, col1, "Science and Freedom 4") + QAEvent event4 = QAEventBuilder.createTarget(context, col1, "Science and Freedom 4") .withTopic("ENRICH/MISSING/ABSTRACT") .withMessage("{\"abstracts[0]\": \"Descrizione delle caratteristiche...\"}").build(); context.restoreAuthSystemState(); String adminToken = getAuthToken(admin.getEmail(), password); - getClient(adminToken).perform(get("/api/integration/nbevents/search/findByTopic")) + getClient(adminToken).perform(get("/api/integration/qaevents/search/findByTopic")) .andExpect(status().isBadRequest()); } @@ -388,7 +388,7 @@ public void recordDecisionTest() throws Exception { .withEntityType("Project").build(); Item funding = ItemBuilder.createItem(context, colFunding).withTitle("Tracking Papyrus and Parchment Paths") .build(); - NBEvent eventProjectBound = NBEventBuilder.createTarget(context, col1, "Science and Freedom with project") + QAEvent eventProjectBound = QAEventBuilder.createTarget(context, col1, "Science and Freedom with project") .withTopic("ENRICH/MISSING/PROJECT") .withMessage( "{\"projects[0].acronym\":\"PAThs\"," @@ -403,7 +403,7 @@ public void recordDecisionTest() throws Exception { + "Dissemination and Storage\"}") .withRelatedItem(funding.getID().toString()) .build(); - NBEvent eventProjectNoBound = NBEventBuilder + QAEvent eventProjectNoBound = QAEventBuilder .createTarget(context, col1, "Science and Freedom with unrelated project") .withTopic("ENRICH/MISSING/PROJECT") .withMessage( @@ -415,36 +415,36 @@ public void recordDecisionTest() throws Exception { + "\"projects[0].openaireId\":\"newProjectID\"," + "\"projects[0].title\":\"A new project\"}") .build(); - NBEvent eventMissingPID1 = NBEventBuilder.createTarget(context, col1, "Science and Freedom") + QAEvent eventMissingPID1 = QAEventBuilder.createTarget(context, col1, "Science and Freedom") .withTopic("ENRICH/MISSING/PID") .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144300\"}").build(); - NBEvent eventMissingPID2 = NBEventBuilder.createTarget(context, col1, "Science and Freedom 2") + QAEvent eventMissingPID2 = QAEventBuilder.createTarget(context, col1, "Science and Freedom 2") .withTopic("ENRICH/MISSING/PID") .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144301\"}").build(); - NBEvent eventMissingUnknownPID = NBEventBuilder.createTarget(context, col1, "Science and Freedom URN PID") + QAEvent eventMissingUnknownPID = QAEventBuilder.createTarget(context, col1, "Science and Freedom URN PID") .withTopic("ENRICH/MISSING/PID") .withMessage( "{\"pids[0].type\":\"urn\",\"pids[0].value\":\"http://thesis2.sba.units.it/store/handle/item/12937\"}") .build(); - NBEvent eventMorePID = NBEventBuilder.createTarget(context, col1, "Science and Freedom 3") + QAEvent eventMorePID = QAEventBuilder.createTarget(context, col1, "Science and Freedom 3") .withTopic("ENRICH/MORE/PID") .withMessage("{\"pids[0].type\":\"pmid\",\"pids[0].value\":\"2144302\"}").build(); - NBEvent eventAbstract = NBEventBuilder.createTarget(context, col1, "Science and Freedom 4") + QAEvent eventAbstract = QAEventBuilder.createTarget(context, col1, "Science and Freedom 4") .withTopic("ENRICH/MISSING/ABSTRACT") .withMessage("{\"abstracts[0]\": \"An abstract to add...\"}").build(); - NBEvent eventAbstractToDiscard = NBEventBuilder.createTarget(context, col1, "Science and Freedom 7") + QAEvent eventAbstractToDiscard = QAEventBuilder.createTarget(context, col1, "Science and Freedom 7") .withTopic("ENRICH/MISSING/ABSTRACT") .withMessage("{\"abstracts[0]\": \"Abstract to discard...\"}").build(); context.restoreAuthSystemState(); // prepare the different patches for our decisions List acceptOp = new ArrayList(); - acceptOp.add(new ReplaceOperation("/status", NBEvent.ACCEPTED)); + acceptOp.add(new ReplaceOperation("/status", QAEvent.ACCEPTED)); List acceptOpUppercase = new ArrayList(); - acceptOpUppercase.add(new ReplaceOperation("/status", NBEvent.ACCEPTED)); + acceptOpUppercase.add(new ReplaceOperation("/status", QAEvent.ACCEPTED)); List discardOp = new ArrayList(); - discardOp.add(new ReplaceOperation("/status", NBEvent.DISCARDED)); + discardOp.add(new ReplaceOperation("/status", QAEvent.DISCARDED)); List rejectOp = new ArrayList(); - rejectOp.add(new ReplaceOperation("/status", NBEvent.REJECTED)); + rejectOp.add(new ReplaceOperation("/status", QAEvent.REJECTED)); String patchAccept = getPatchContent(acceptOp); String patchAcceptUppercase = getPatchContent(acceptOpUppercase); String patchDiscard = getPatchContent(discardOp); @@ -452,44 +452,44 @@ public void recordDecisionTest() throws Exception { String authToken = getAuthToken(admin.getEmail(), password); // accept pid1, unknownPID, morePID, the two projects and abstract - eventMissingPID1.setStatus(NBEvent.ACCEPTED); - eventMorePID.setStatus(NBEvent.ACCEPTED); - eventMissingUnknownPID.setStatus(NBEvent.ACCEPTED); - eventMissingUnknownPID.setStatus(NBEvent.ACCEPTED); - eventProjectBound.setStatus(NBEvent.ACCEPTED); - eventProjectNoBound.setStatus(NBEvent.ACCEPTED); - eventAbstract.setStatus(NBEvent.ACCEPTED); + eventMissingPID1.setStatus(QAEvent.ACCEPTED); + eventMorePID.setStatus(QAEvent.ACCEPTED); + eventMissingUnknownPID.setStatus(QAEvent.ACCEPTED); + eventMissingUnknownPID.setStatus(QAEvent.ACCEPTED); + eventProjectBound.setStatus(QAEvent.ACCEPTED); + eventProjectNoBound.setStatus(QAEvent.ACCEPTED); + eventAbstract.setStatus(QAEvent.ACCEPTED); - getClient(authToken).perform(patch("/api/integration/nbevents/" + eventMissingPID1.getEventId()) + getClient(authToken).perform(patch("/api/integration/qaevents/" + eventMissingPID1.getEventId()) .content(patchAccept) .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) .andExpect(status().isOk()) - .andExpect(jsonPath("$", NBEventMatcher.matchNBEventEntry(eventMissingPID1))); - getClient(authToken).perform(patch("/api/integration/nbevents/" + eventMorePID.getEventId()) + .andExpect(jsonPath("$", QAEventMatcher.matchQAEventEntry(eventMissingPID1))); + getClient(authToken).perform(patch("/api/integration/qaevents/" + eventMorePID.getEventId()) .content(patchAcceptUppercase) .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) .andExpect(status().isOk()) - .andExpect(jsonPath("$", NBEventMatcher.matchNBEventEntry(eventMorePID))); - getClient(authToken).perform(patch("/api/integration/nbevents/" + eventMissingUnknownPID.getEventId()) + .andExpect(jsonPath("$", QAEventMatcher.matchQAEventEntry(eventMorePID))); + getClient(authToken).perform(patch("/api/integration/qaevents/" + eventMissingUnknownPID.getEventId()) .content(patchAccept) .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) .andExpect(status().isOk()) - .andExpect(jsonPath("$", NBEventMatcher.matchNBEventEntry(eventMissingUnknownPID))); - getClient(authToken).perform(patch("/api/integration/nbevents/" + eventProjectBound.getEventId()) + .andExpect(jsonPath("$", QAEventMatcher.matchQAEventEntry(eventMissingUnknownPID))); + getClient(authToken).perform(patch("/api/integration/qaevents/" + eventProjectBound.getEventId()) .content(patchAccept) .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) .andExpect(status().isOk()) - .andExpect(jsonPath("$", NBEventMatcher.matchNBEventEntry(eventProjectBound))); - getClient(authToken).perform(patch("/api/integration/nbevents/" + eventProjectNoBound.getEventId()) + .andExpect(jsonPath("$", QAEventMatcher.matchQAEventEntry(eventProjectBound))); + getClient(authToken).perform(patch("/api/integration/qaevents/" + eventProjectNoBound.getEventId()) .content(patchAccept) .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) .andExpect(status().isOk()) - .andExpect(jsonPath("$", NBEventMatcher.matchNBEventEntry(eventProjectNoBound))); - getClient(authToken).perform(patch("/api/integration/nbevents/" + eventAbstract.getEventId()) + .andExpect(jsonPath("$", QAEventMatcher.matchQAEventEntry(eventProjectNoBound))); + getClient(authToken).perform(patch("/api/integration/qaevents/" + eventAbstract.getEventId()) .content(patchAccept) .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) .andExpect(status().isOk()) - .andExpect(jsonPath("$", NBEventMatcher.matchNBEventEntry(eventAbstract))); + .andExpect(jsonPath("$", QAEventMatcher.matchQAEventEntry(eventAbstract))); // check if the item has been updated getClient(authToken).perform(get("/api/core/items/" + eventMissingPID1.getTarget()) .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) @@ -524,31 +524,31 @@ public void recordDecisionTest() throws Exception { .andExpect(jsonPath("$", hasJsonPath("$.metadata['dc.description.abstract'][0].value", is("An abstract to add...")))); // reject pid2 - eventMissingPID2.setStatus(NBEvent.REJECTED); - getClient(authToken).perform(patch("/api/integration/nbevents/" + eventMissingPID2.getEventId()) + eventMissingPID2.setStatus(QAEvent.REJECTED); + getClient(authToken).perform(patch("/api/integration/qaevents/" + eventMissingPID2.getEventId()) .content(patchReject) .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) .andExpect(status().isOk()) - .andExpect(jsonPath("$", NBEventMatcher.matchNBEventEntry(eventMissingPID2))); + .andExpect(jsonPath("$", QAEventMatcher.matchQAEventEntry(eventMissingPID2))); getClient(authToken).perform(get("/api/core/items/" + eventMissingPID2.getTarget()) .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) .andExpect(status().isOk()) .andExpect(jsonPath("$", hasNoJsonPath("$.metadata['dc.identifier.other']"))); // discard abstractToDiscard - eventAbstractToDiscard.setStatus(NBEvent.DISCARDED); - getClient(authToken).perform(patch("/api/integration/nbevents/" + eventAbstractToDiscard.getEventId()) + eventAbstractToDiscard.setStatus(QAEvent.DISCARDED); + getClient(authToken).perform(patch("/api/integration/qaevents/" + eventAbstractToDiscard.getEventId()) .content(patchDiscard) .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) .andExpect(status().isOk()) - .andExpect(jsonPath("$", NBEventMatcher.matchNBEventEntry(eventAbstractToDiscard))); + .andExpect(jsonPath("$", QAEventMatcher.matchQAEventEntry(eventAbstractToDiscard))); getClient(authToken).perform(get("/api/core/items/" + eventMissingPID2.getTarget()) .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) .andExpect(status().isOk()) .andExpect(jsonPath("$", hasNoJsonPath("$.metadata['dc.description.abstract']"))); - // no pending nb events should be longer available - getClient(authToken).perform(get("/api/integration/nbtopics")).andExpect(status().isOk()) + // no pending qa events should be longer available + getClient(authToken).perform(get("/api/integration/qatopics")).andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(status().isOk()) .andExpect(jsonPath("$.page.size", is(20))).andExpect(jsonPath("$.page.totalElements", is(0))); @@ -562,7 +562,7 @@ public void setRelatedTest() throws Exception { Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build(); Collection colFunding = CollectionBuilder.createCollection(context, parentCommunity) .withName("Collection Fundings").build(); - NBEvent event = NBEventBuilder.createTarget(context, col1, "Science and Freedom 5") + QAEvent event = QAEventBuilder.createTarget(context, col1, "Science and Freedom 5") .withTopic("ENRICH/MISSING/PROJECT") .withMessage( "{\"projects[0].acronym\":\"PAThs\"," @@ -581,23 +581,23 @@ public void setRelatedTest() throws Exception { context.restoreAuthSystemState(); String authToken = getAuthToken(admin.getEmail(), password); getClient(authToken) - .perform(get("/api/integration/nbevents/" + event.getEventId()).param("projection", "full")) + .perform(get("/api/integration/qaevents/" + event.getEventId()).param("projection", "full")) .andExpect(status().isOk()) - .andExpect(jsonPath("$", NBEventMatcher.matchNBEventFullEntry(event))); + .andExpect(jsonPath("$", QAEventMatcher.matchQAEventFullEntry(event))); getClient(authToken) - .perform(post("/api/integration/nbevents/" + event.getEventId() + "/related").param("item", + .perform(post("/api/integration/qaevents/" + event.getEventId() + "/related").param("item", funding.getID().toString())) .andExpect(status().isCreated()) .andExpect(jsonPath("$", ItemMatcher.matchItemProperties(funding))); // update our local event copy to reflect the association with the related item event.setRelated(funding.getID().toString()); getClient(authToken) - .perform(get("/api/integration/nbevents/" + event.getEventId()).param("projection", "full")) + .perform(get("/api/integration/qaevents/" + event.getEventId()).param("projection", "full")) .andExpect(status().isOk()) - .andExpect(jsonPath("$", NBEventMatcher.matchNBEventFullEntry(event))); + .andExpect(jsonPath("$", QAEventMatcher.matchQAEventFullEntry(event))); getClient(authToken) - .perform(get("/api/integration/nbevents/" + event.getEventId() + "/related")) + .perform(get("/api/integration/qaevents/" + event.getEventId() + "/related")) .andExpect(status().isOk()) .andExpect(jsonPath("$", ItemMatcher.matchItemProperties(funding))); } @@ -611,7 +611,7 @@ public void unsetRelatedTest() throws Exception { .withName("Collection Fundings").build(); Item funding = ItemBuilder.createItem(context, colFunding).withTitle("Tracking Papyrus and Parchment Paths") .build(); - NBEvent event = NBEventBuilder.createTarget(context, col1, "Science and Freedom 5") + QAEvent event = QAEventBuilder.createTarget(context, col1, "Science and Freedom 5") .withTopic("ENRICH/MISSING/PROJECT") .withMessage( "{\"projects[0].acronym\":\"PAThs\"," @@ -629,21 +629,21 @@ public void unsetRelatedTest() throws Exception { context.restoreAuthSystemState(); String authToken = getAuthToken(admin.getEmail(), password); getClient(authToken) - .perform(get("/api/integration/nbevents/" + event.getEventId()).param("projection", "full")) + .perform(get("/api/integration/qaevents/" + event.getEventId()).param("projection", "full")) .andExpect(status().isOk()) - .andExpect(jsonPath("$", NBEventMatcher.matchNBEventFullEntry(event))); + .andExpect(jsonPath("$", QAEventMatcher.matchQAEventFullEntry(event))); getClient(authToken) - .perform(delete("/api/integration/nbevents/" + event.getEventId() + "/related")) + .perform(delete("/api/integration/qaevents/" + event.getEventId() + "/related")) .andExpect(status().isNoContent()); // update our local event copy to reflect the association with the related item event.setRelated(null); getClient(authToken) - .perform(get("/api/integration/nbevents/" + event.getEventId()).param("projection", "full")) + .perform(get("/api/integration/qaevents/" + event.getEventId()).param("projection", "full")) .andExpect(status().isOk()) - .andExpect(jsonPath("$", NBEventMatcher.matchNBEventFullEntry(event))); + .andExpect(jsonPath("$", QAEventMatcher.matchQAEventFullEntry(event))); getClient(authToken) - .perform(get("/api/integration/nbevents/" + event.getEventId() + "/related")) + .perform(get("/api/integration/qaevents/" + event.getEventId() + "/related")) .andExpect(status().isNoContent()); } @@ -654,7 +654,7 @@ public void setInvalidRelatedTest() throws Exception { Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build(); Collection colFunding = CollectionBuilder.createCollection(context, parentCommunity) .withName("Collection Fundings").build(); - NBEvent event = NBEventBuilder.createTarget(context, col1, "Science and Freedom") + QAEvent event = QAEventBuilder.createTarget(context, col1, "Science and Freedom") .withTopic("ENRICH/MISSING/PID") .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144300\"}").build(); Item funding = ItemBuilder.createItem(context, colFunding).withTitle("Tracking Papyrus and Parchment Paths") @@ -662,19 +662,19 @@ public void setInvalidRelatedTest() throws Exception { context.restoreAuthSystemState(); String authToken = getAuthToken(admin.getEmail(), password); getClient(authToken) - .perform(get("/api/integration/nbevents/" + event.getEventId()).param("projection", "full")) + .perform(get("/api/integration/qaevents/" + event.getEventId()).param("projection", "full")) .andExpect(status().isOk()) - .andExpect(jsonPath("$", NBEventMatcher.matchNBEventFullEntry(event))); + .andExpect(jsonPath("$", QAEventMatcher.matchQAEventFullEntry(event))); getClient(authToken) - .perform(post("/api/integration/nbevents/" + event.getEventId() + "/related").param("item", + .perform(post("/api/integration/qaevents/" + event.getEventId() + "/related").param("item", funding.getID().toString())) .andExpect(status().isBadRequest()); // check that no related item has been added to our event getClient(authToken) - .perform(get("/api/integration/nbevents/" + event.getEventId()).param("projection", "full")) + .perform(get("/api/integration/qaevents/" + event.getEventId()).param("projection", "full")) .andExpect(status().isOk()) - .andExpect(jsonPath("$", NBEventMatcher.matchNBEventFullEntry(event))); + .andExpect(jsonPath("$", QAEventMatcher.matchQAEventFullEntry(event))); } @Test @@ -682,20 +682,20 @@ public void deleteItemWithEventTest() throws Exception { context.turnOffAuthorisationSystem(); parentCommunity = CommunityBuilder.createCommunity(context).withName("Parent Community").build(); Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build(); - NBEvent event1 = NBEventBuilder.createTarget(context, col1, "Science and Freedom") + QAEvent event1 = QAEventBuilder.createTarget(context, col1, "Science and Freedom") .withTopic("ENRICH/MISSING/PID") .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144300\"}").build(); - NBEvent event2 = NBEventBuilder.createTarget(context, col1, "Science and Freedom 2") + QAEvent event2 = QAEventBuilder.createTarget(context, col1, "Science and Freedom 2") .withTopic("ENRICH/MISSING/PID") .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144301\"}").build(); context.restoreAuthSystemState(); String authToken = getAuthToken(admin.getEmail(), password); getClient(authToken) - .perform(get("/api/integration/nbevents/search/findByTopic").param("topic", "ENRICH!MISSING!PID")) - .andExpect(status().isOk()).andExpect(jsonPath("$._embedded.nbevents", Matchers.hasSize(2))) - .andExpect(jsonPath("$._embedded.nbevents", - Matchers.containsInAnyOrder(NBEventMatcher.matchNBEventEntry(event1), - NBEventMatcher.matchNBEventEntry(event2)))) + .perform(get("/api/integration/qaevents/search/findByTopic").param("topic", "ENRICH!MISSING!PID")) + .andExpect(status().isOk()).andExpect(jsonPath("$._embedded.qaevents", Matchers.hasSize(2))) + .andExpect(jsonPath("$._embedded.qaevents", + Matchers.containsInAnyOrder(QAEventMatcher.matchQAEventEntry(event1), + QAEventMatcher.matchQAEventEntry(event2)))) .andExpect(jsonPath("$.page.size", is(20))).andExpect(jsonPath("$.page.totalElements", is(2))); getClient(authToken).perform(delete("/api/core/items/" + event1.getTarget())) @@ -705,11 +705,11 @@ public void deleteItemWithEventTest() throws Exception { .andExpect(status().is(404)); getClient(authToken) - .perform(get("/api/integration/nbevents/search/findByTopic").param("topic", "ENRICH!MISSING!PID")) - .andExpect(status().isOk()).andExpect(jsonPath("$._embedded.nbevents", Matchers.hasSize(1))) - .andExpect(jsonPath("$._embedded.nbevents", + .perform(get("/api/integration/qaevents/search/findByTopic").param("topic", "ENRICH!MISSING!PID")) + .andExpect(status().isOk()).andExpect(jsonPath("$._embedded.qaevents", Matchers.hasSize(1))) + .andExpect(jsonPath("$._embedded.qaevents", Matchers.containsInAnyOrder( - NBEventMatcher.matchNBEventEntry(event2)))) + QAEventMatcher.matchQAEventEntry(event2)))) .andExpect(jsonPath("$.page.size", is(20))).andExpect(jsonPath("$.page.totalElements", is(1))); } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/NBSourceRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/QASourceRestRepositoryIT.java similarity index 78% rename from dspace-server-webapp/src/test/java/org/dspace/app/rest/NBSourceRestRepositoryIT.java rename to dspace-server-webapp/src/test/java/org/dspace/app/rest/QASourceRestRepositoryIT.java index 2af54b74da49..1fdcd5e0df6a 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/NBSourceRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/QASourceRestRepositoryIT.java @@ -7,7 +7,7 @@ */ package org.dspace.app.rest; -import static org.dspace.app.rest.matcher.NBSourceMatcher.matchNBSourceEntry; +import static org.dspace.app.rest.matcher.QASourceMatcher.matchQASourceEntry; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.is; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; @@ -19,22 +19,22 @@ import org.dspace.builder.CollectionBuilder; import org.dspace.builder.CommunityBuilder; import org.dspace.builder.ItemBuilder; -import org.dspace.builder.NBEventBuilder; +import org.dspace.builder.QAEventBuilder; import org.dspace.content.Collection; import org.dspace.content.Item; -import org.dspace.content.NBEvent; +import org.dspace.content.QAEvent; import org.dspace.services.ConfigurationService; import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; /** - * Integration tests for {@link NBSourceRestRepository}. + * Integration tests for {@link QASourceRestRepository}. * * @author Luca Giamminonni (luca.giamminonni at 4science.it) * */ -public class NBSourceRestRepositoryIT extends AbstractControllerIntegrationTest { +public class QASourceRestRepositoryIT extends AbstractControllerIntegrationTest { @Autowired private ConfigurationService configurationService; @@ -60,7 +60,7 @@ public void setup() { context.restoreAuthSystemState(); - configurationService.setProperty("nbevent.sources", + configurationService.setProperty("qaevent.sources", new String[] { "openaire", "test-source", "test-source-2" }); } @@ -80,13 +80,13 @@ public void testFindAll() throws Exception { context.restoreAuthSystemState(); String authToken = getAuthToken(admin.getEmail(), password); - getClient(authToken).perform(get("/api/integration/nbsources")) + getClient(authToken).perform(get("/api/integration/qasources")) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) - .andExpect(jsonPath("$._embedded.nbsources", contains( - matchNBSourceEntry("openaire", 3), - matchNBSourceEntry("test-source", 2), - matchNBSourceEntry("test-source-2", 0)))) + .andExpect(jsonPath("$._embedded.qasources", contains( + matchQASourceEntry("openaire", 3), + matchQASourceEntry("test-source", 2), + matchQASourceEntry("test-source-2", 0)))) .andExpect(jsonPath("$.page.size", is(20))) .andExpect(jsonPath("$.page.totalElements", is(3))); @@ -103,7 +103,7 @@ public void testFindAllForbidden() throws Exception { context.restoreAuthSystemState(); String token = getAuthToken(eperson.getEmail(), password); - getClient(token).perform(get("/api/integration/nbsources")) + getClient(token).perform(get("/api/integration/qasources")) .andExpect(status().isForbidden()); } @@ -118,7 +118,7 @@ public void testFindAllUnauthorized() throws Exception { context.restoreAuthSystemState(); - getClient().perform(get("/api/integration/nbsources")) + getClient().perform(get("/api/integration/qasources")) .andExpect(status().isUnauthorized()); } @@ -138,22 +138,22 @@ public void testFindOne() throws Exception { context.restoreAuthSystemState(); String authToken = getAuthToken(admin.getEmail(), password); - getClient(authToken).perform(get("/api/integration/nbsources/openaire")) + getClient(authToken).perform(get("/api/integration/qasources/openaire")) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) - .andExpect(jsonPath("$", matchNBSourceEntry("openaire", 3))); + .andExpect(jsonPath("$", matchQASourceEntry("openaire", 3))); - getClient(authToken).perform(get("/api/integration/nbsources/test-source")) + getClient(authToken).perform(get("/api/integration/qasources/test-source")) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) - .andExpect(jsonPath("$", matchNBSourceEntry("test-source", 2))); + .andExpect(jsonPath("$", matchQASourceEntry("test-source", 2))); - getClient(authToken).perform(get("/api/integration/nbsources/test-source-2")) + getClient(authToken).perform(get("/api/integration/qasources/test-source-2")) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) - .andExpect(jsonPath("$", matchNBSourceEntry("test-source-2", 0))); + .andExpect(jsonPath("$", matchQASourceEntry("test-source-2", 0))); - getClient(authToken).perform(get("/api/integration/nbsources/unknown-test-source")) + getClient(authToken).perform(get("/api/integration/qasources/unknown-test-source")) .andExpect(status().isNotFound()); } @@ -169,7 +169,7 @@ public void testFindOneForbidden() throws Exception { context.restoreAuthSystemState(); String token = getAuthToken(eperson.getEmail(), password); - getClient(token).perform(get("/api/integration/nbsources/openaire")) + getClient(token).perform(get("/api/integration/qasources/openaire")) .andExpect(status().isForbidden()); } @@ -184,13 +184,13 @@ public void testFindOneUnauthorized() throws Exception { context.restoreAuthSystemState(); - getClient().perform(get("/api/integration/nbsources/openaire")) + getClient().perform(get("/api/integration/qasources/openaire")) .andExpect(status().isUnauthorized()); } - private NBEvent createEvent(String source, String topic, String title) { - return NBEventBuilder.createTarget(context, target) + private QAEvent createEvent(String source, String topic, String title) { + return QAEventBuilder.createTarget(context, target) .withSource(source) .withTopic(topic) .withTitle(title) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/NBTopicRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/QATopicRestRepositoryIT.java similarity index 73% rename from dspace-server-webapp/src/test/java/org/dspace/app/rest/NBTopicRestRepositoryIT.java rename to dspace-server-webapp/src/test/java/org/dspace/app/rest/QATopicRestRepositoryIT.java index 7fe9dbc8b2d8..d510d713a003 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/NBTopicRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/QATopicRestRepositoryIT.java @@ -13,12 +13,12 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import org.dspace.app.rest.matcher.NBTopicMatcher; -import org.dspace.app.rest.repository.NBTopicRestRepository; +import org.dspace.app.rest.matcher.QATopicMatcher; +import org.dspace.app.rest.repository.QATopicRestRepository; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.builder.CollectionBuilder; import org.dspace.builder.CommunityBuilder; -import org.dspace.builder.NBEventBuilder; +import org.dspace.builder.QAEventBuilder; import org.dspace.content.Collection; import org.dspace.services.ConfigurationService; import org.hamcrest.Matchers; @@ -26,12 +26,12 @@ import org.springframework.beans.factory.annotation.Autowired; /** - * Integration tests for {@link NBTopicRestRepository}. + * Integration tests for {@link QATopicRestRepository}. * * @author Andrea Bollini (andrea.bollini at 4science.it) * */ -public class NBTopicRestRepositoryIT extends AbstractControllerIntegrationTest { +public class QATopicRestRepositoryIT extends AbstractControllerIntegrationTest { @Autowired private ConfigurationService configurationService; @@ -43,41 +43,41 @@ public void findAllTest() throws Exception { .withName("Parent Community") .build(); Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build(); - NBEventBuilder.createTarget(context, col1, "Science and Freedom") + QAEventBuilder.createTarget(context, col1, "Science and Freedom") .withTopic("ENRICH/MISSING/PID") .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144300\"}").build(); - NBEventBuilder.createTarget(context, col1, "Science and Freedom 2") + QAEventBuilder.createTarget(context, col1, "Science and Freedom 2") .withTopic("ENRICH/MISSING/PID") .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144301\"}").build(); - NBEventBuilder.createTarget(context, col1, "Science and Freedom 3") + QAEventBuilder.createTarget(context, col1, "Science and Freedom 3") .withTopic("ENRICH/MORE/PID") .withMessage("{\"pids[0].type\":\"pmid\",\"pids[0].value\":\"10.2307/2144302\"}").build(); - NBEventBuilder.createTarget(context, col1, "Science and Freedom 4") + QAEventBuilder.createTarget(context, col1, "Science and Freedom 4") .withTopic("ENRICH/MISSING/ABSTRACT") .withMessage( "{\"abstracts[0]\": \"Descrizione delle caratteristiche...\"}") .build(); context.restoreAuthSystemState(); String authToken = getAuthToken(admin.getEmail(), password); - getClient(authToken).perform(get("/api/integration/nbtopics")).andExpect(status().isOk()) + getClient(authToken).perform(get("/api/integration/qatopics")).andExpect(status().isOk()) .andExpect(content().contentType(contentType)) - .andExpect(jsonPath("$._embedded.nbtopics", - Matchers.containsInAnyOrder(NBTopicMatcher.matchNBTopicEntry("ENRICH/MISSING/PID", 2), - NBTopicMatcher.matchNBTopicEntry("ENRICH/MISSING/ABSTRACT", 1), - NBTopicMatcher.matchNBTopicEntry("ENRICH/MORE/PID", 1)))) + .andExpect(jsonPath("$._embedded.qatopics", + Matchers.containsInAnyOrder(QATopicMatcher.matchQATopicEntry("ENRICH/MISSING/PID", 2), + QATopicMatcher.matchQATopicEntry("ENRICH/MISSING/ABSTRACT", 1), + QATopicMatcher.matchQATopicEntry("ENRICH/MORE/PID", 1)))) .andExpect(jsonPath("$.page.size", is(20))).andExpect(jsonPath("$.page.totalElements", is(3))); } @Test public void findAllUnauthorizedTest() throws Exception { - getClient().perform(get("/api/integration/nbtopics")).andExpect(status().isUnauthorized()); + getClient().perform(get("/api/integration/qatopics")).andExpect(status().isUnauthorized()); } @Test public void findAllForbiddenTest() throws Exception { String authToken = getAuthToken(eperson.getEmail(), password); - getClient(authToken).perform(get("/api/integration/nbtopics")).andExpect(status().isForbidden()); + getClient(authToken).perform(get("/api/integration/qatopics")).andExpect(status().isForbidden()); } @Test @@ -88,31 +88,31 @@ public void findAllPaginationTest() throws Exception { .build(); //create collection Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build(); - NBEventBuilder.createTarget(context, col1, "Science and Freedom") + QAEventBuilder.createTarget(context, col1, "Science and Freedom") .withTopic("ENRICH/MISSING/PID") .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144300\"}").build(); - NBEventBuilder.createTarget(context, col1, "Science and Freedom 2") + QAEventBuilder.createTarget(context, col1, "Science and Freedom 2") .withTopic("ENRICH/MISSING/PID") .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144301\"}").build(); - NBEventBuilder.createTarget(context, col1, "Science and Freedom 3") + QAEventBuilder.createTarget(context, col1, "Science and Freedom 3") .withTopic("ENRICH/MORE/PID") .withMessage("{\"pids[0].type\":\"pmid\",\"pids[0].value\":\"10.2307/2144302\"}").build(); - NBEventBuilder.createTarget(context, col1, "Science and Freedom 4") + QAEventBuilder.createTarget(context, col1, "Science and Freedom 4") .withTopic("ENRICH/MISSING/ABSTRACT") .withMessage( "{\"abstracts[0]\": \"Descrizione delle caratteristiche...\"}") .build(); context.restoreAuthSystemState(); String authToken = getAuthToken(admin.getEmail(), password); - getClient(authToken).perform(get("/api/integration/nbtopics").param("size", "2")) + getClient(authToken).perform(get("/api/integration/qatopics").param("size", "2")) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) - .andExpect(jsonPath("$._embedded.nbtopics", Matchers.hasSize(2))) + .andExpect(jsonPath("$._embedded.qatopics", Matchers.hasSize(2))) .andExpect(jsonPath("$.page.size", is(2))).andExpect(jsonPath("$.page.totalElements", is(3))); - getClient(authToken).perform(get("/api/integration/nbtopics").param("size", "2").param("page", "1")) + getClient(authToken).perform(get("/api/integration/qatopics").param("size", "2").param("page", "1")) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) - .andExpect(jsonPath("$._embedded.nbtopics", Matchers.hasSize(1))) + .andExpect(jsonPath("$._embedded.qatopics", Matchers.hasSize(1))) .andExpect(jsonPath("$.page.size", is(2))).andExpect(jsonPath("$.page.totalElements", is(3))); } @@ -123,26 +123,26 @@ public void findOneTest() throws Exception { .withName("Parent Community") .build(); Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build(); - NBEventBuilder.createTarget(context, col1, "Science and Freedom") + QAEventBuilder.createTarget(context, col1, "Science and Freedom") .withTopic("ENRICH/MISSING/PID") .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144300\"}").build(); - NBEventBuilder.createTarget(context, col1, "Science and Freedom 2") + QAEventBuilder.createTarget(context, col1, "Science and Freedom 2") .withTopic("ENRICH/MISSING/PID") .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144301\"}").build(); - NBEventBuilder.createTarget(context, col1, "Science and Freedom 3") + QAEventBuilder.createTarget(context, col1, "Science and Freedom 3") .withTopic("ENRICH/MORE/PID") .withMessage("{\"pids[0].type\":\"pmid\",\"pids[0].value\":\"10.2307/2144302\"}").build(); - NBEventBuilder.createTarget(context, col1, "Science and Freedom 4") + QAEventBuilder.createTarget(context, col1, "Science and Freedom 4") .withTopic("ENRICH/MISSING/ABSTRACT") .withMessage( "{\"abstracts[0]\": \"Descrizione delle caratteristiche...\"}") .build(); context.restoreAuthSystemState(); String authToken = getAuthToken(admin.getEmail(), password); - getClient(authToken).perform(get("/api/integration/nbtopics/ENRICH!MISSING!PID")) - .andExpect(jsonPath("$", NBTopicMatcher.matchNBTopicEntry("ENRICH/MISSING/PID", 2))); - getClient(authToken).perform(get("/api/integration/nbtopics/ENRICH!MISSING!ABSTRACT")) - .andExpect(jsonPath("$", NBTopicMatcher.matchNBTopicEntry("ENRICH/MISSING/ABSTRACT", 1))); + getClient(authToken).perform(get("/api/integration/qatopics/ENRICH!MISSING!PID")) + .andExpect(jsonPath("$", QATopicMatcher.matchQATopicEntry("ENRICH/MISSING/PID", 2))); + getClient(authToken).perform(get("/api/integration/qatopics/ENRICH!MISSING!ABSTRACT")) + .andExpect(jsonPath("$", QATopicMatcher.matchQATopicEntry("ENRICH/MISSING/ABSTRACT", 1))); } @Test @@ -152,11 +152,11 @@ public void findOneUnauthorizedTest() throws Exception { .withName("Parent Community") .build(); Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build(); - NBEventBuilder.createTarget(context, col1, "Science and Freedom") + QAEventBuilder.createTarget(context, col1, "Science and Freedom") .withTopic("ENRICH/MISSING/PID").build(); context.restoreAuthSystemState(); - getClient().perform(get("/api/integration/nbtopics/ENRICH!MISSING!PID")).andExpect(status().isUnauthorized()); - getClient().perform(get("/api/integration/nbtopics/ENRICH!MISSING!ABSTRACT")) + getClient().perform(get("/api/integration/qatopics/ENRICH!MISSING!PID")).andExpect(status().isUnauthorized()); + getClient().perform(get("/api/integration/qatopics/ENRICH!MISSING!ABSTRACT")) .andExpect(status().isUnauthorized()); } @@ -167,75 +167,75 @@ public void findOneForbiddenTest() throws Exception { .withName("Parent Community") .build(); Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build(); - NBEventBuilder.createTarget(context, col1, "Science and Freedom") + QAEventBuilder.createTarget(context, col1, "Science and Freedom") .withTopic("ENRICH/MISSING/PID").build(); context.restoreAuthSystemState(); String authToken = getAuthToken(eperson.getEmail(), password); - getClient(authToken).perform(get("/api/integration/nbtopics/ENRICH!MISSING!PID")) + getClient(authToken).perform(get("/api/integration/qatopics/ENRICH!MISSING!PID")) .andExpect(status().isForbidden()); - getClient(authToken).perform(get("/api/integration/nbtopics/ENRICH!MISSING!ABSTRACT")) + getClient(authToken).perform(get("/api/integration/qatopics/ENRICH!MISSING!ABSTRACT")) .andExpect(status().isForbidden()); } @Test public void findBySourceTest() throws Exception { context.turnOffAuthorisationSystem(); - configurationService.setProperty("nbevent.sources", + configurationService.setProperty("qaevent.sources", new String[] { "openaire", "test-source", "test-source-2" }); parentCommunity = CommunityBuilder.createCommunity(context) .withName("Parent Community") .build(); Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build(); - NBEventBuilder.createTarget(context, col1, "Science and Freedom") + QAEventBuilder.createTarget(context, col1, "Science and Freedom") .withTopic("ENRICH/MISSING/PID") .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144300\"}").build(); - NBEventBuilder.createTarget(context, col1, "Science and Freedom 2") + QAEventBuilder.createTarget(context, col1, "Science and Freedom 2") .withTopic("ENRICH/MISSING/PID") .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144301\"}").build(); - NBEventBuilder.createTarget(context, col1, "Science and Freedom 3") + QAEventBuilder.createTarget(context, col1, "Science and Freedom 3") .withTopic("ENRICH/MORE/PID") .withMessage("{\"pids[0].type\":\"pmid\",\"pids[0].value\":\"10.2307/2144302\"}").build(); - NBEventBuilder.createTarget(context, col1, "Science and Freedom 4") + QAEventBuilder.createTarget(context, col1, "Science and Freedom 4") .withTopic("ENRICH/MISSING/ABSTRACT") .withMessage( "{\"abstracts[0]\": \"Descrizione delle caratteristiche...\"}") .build(); - NBEventBuilder.createTarget(context, col1, "Science and Freedom 5") + QAEventBuilder.createTarget(context, col1, "Science and Freedom 5") .withTopic("TEST/TOPIC") .withSource("test-source") .build(); - NBEventBuilder.createTarget(context, col1, "Science and Freedom 6") + QAEventBuilder.createTarget(context, col1, "Science and Freedom 6") .withTopic("TEST/TOPIC") .withSource("test-source") .build(); - NBEventBuilder.createTarget(context, col1, "Science and Freedom 7") + QAEventBuilder.createTarget(context, col1, "Science and Freedom 7") .withTopic("TEST/TOPIC/2") .withSource("test-source") .build(); context.restoreAuthSystemState(); String authToken = getAuthToken(admin.getEmail(), password); - getClient(authToken).perform(get("/api/integration/nbtopics/search/bySource") + getClient(authToken).perform(get("/api/integration/qatopics/search/bySource") .param("source", "openaire")) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) - .andExpect(jsonPath("$._embedded.nbtopics", - Matchers.containsInAnyOrder(NBTopicMatcher.matchNBTopicEntry("ENRICH/MISSING/PID", 2), - NBTopicMatcher.matchNBTopicEntry("ENRICH/MISSING/ABSTRACT", 1), - NBTopicMatcher.matchNBTopicEntry("ENRICH/MORE/PID", 1)))) + .andExpect(jsonPath("$._embedded.qatopics", + Matchers.containsInAnyOrder(QATopicMatcher.matchQATopicEntry("ENRICH/MISSING/PID", 2), + QATopicMatcher.matchQATopicEntry("ENRICH/MISSING/ABSTRACT", 1), + QATopicMatcher.matchQATopicEntry("ENRICH/MORE/PID", 1)))) .andExpect(jsonPath("$.page.size", is(20))).andExpect(jsonPath("$.page.totalElements", is(3))); - getClient(authToken).perform(get("/api/integration/nbtopics/search/bySource") + getClient(authToken).perform(get("/api/integration/qatopics/search/bySource") .param("source", "test-source")) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) - .andExpect(jsonPath("$._embedded.nbtopics", - Matchers.containsInAnyOrder(NBTopicMatcher.matchNBTopicEntry("TEST/TOPIC/2", 1), - NBTopicMatcher.matchNBTopicEntry("TEST/TOPIC", 2)))) + .andExpect(jsonPath("$._embedded.qatopics", + Matchers.containsInAnyOrder(QATopicMatcher.matchQATopicEntry("TEST/TOPIC/2", 1), + QATopicMatcher.matchQATopicEntry("TEST/TOPIC", 2)))) .andExpect(jsonPath("$.page.size", is(20))).andExpect(jsonPath("$.page.totalElements", is(2))); - getClient(authToken).perform(get("/api/integration/nbtopics/search/bySource") + getClient(authToken).perform(get("/api/integration/qatopics/search/bySource") .param("source", "test-source-2")) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) - .andExpect(jsonPath("$._embedded.nbtopics").doesNotExist()) + .andExpect(jsonPath("$._embedded.qatopics").doesNotExist()) .andExpect(jsonPath("$.page.size", is(20))).andExpect(jsonPath("$.page.totalElements", is(0))); } @@ -246,10 +246,10 @@ public void findBySourceUnauthorizedTest() throws Exception { .withName("Parent Community") .build(); Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build(); - NBEventBuilder.createTarget(context, col1, "Science and Freedom") + QAEventBuilder.createTarget(context, col1, "Science and Freedom") .withTopic("ENRICH/MISSING/PID").build(); context.restoreAuthSystemState(); - getClient().perform(get("/api/integration/nbtopics/search/bySource") + getClient().perform(get("/api/integration/qatopics/search/bySource") .param("source", "openaire")) .andExpect(status().isUnauthorized()); } @@ -261,11 +261,11 @@ public void findBySourceForbiddenTest() throws Exception { .withName("Parent Community") .build(); Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build(); - NBEventBuilder.createTarget(context, col1, "Science and Freedom") + QAEventBuilder.createTarget(context, col1, "Science and Freedom") .withTopic("ENRICH/MISSING/PID").build(); context.restoreAuthSystemState(); String authToken = getAuthToken(eperson.getEmail(), password); - getClient(authToken).perform(get("/api/integration/nbtopics/search/bySource") + getClient(authToken).perform(get("/api/integration/qatopics/search/bySource") .param("source", "openaire")) .andExpect(status().isForbidden()); } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/NBEventMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/QAEventMatcher.java similarity index 88% rename from dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/NBEventMatcher.java rename to dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/QAEventMatcher.java index f0a0a1219430..ab696aa33805 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/NBEventMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/QAEventMatcher.java @@ -18,26 +18,26 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.json.JsonMapper; import org.apache.commons.lang3.StringUtils; -import org.dspace.app.nbevent.service.dto.OpenaireMessageDTO; -import org.dspace.content.NBEvent; +import org.dspace.content.QAEvent; +import org.dspace.qaevent.service.dto.OpenaireMessageDTO; import org.hamcrest.Matcher; import org.hamcrest.Matchers; import org.hamcrest.core.IsAnything; /** - * Matcher related to {@link NBEventResource}. + * Matcher related to {@link QAEventResource}. * * @author Andrea Bollini (andrea.bollini at 4science.it) * */ -public class NBEventMatcher { +public class QAEventMatcher { - private NBEventMatcher() { + private QAEventMatcher() { } - public static Matcher matchNBEventFullEntry(NBEvent event) { + public static Matcher matchQAEventFullEntry(QAEvent event) { return allOf( - matchNBEventEntry(event), + matchQAEventEntry(event), hasJsonPath("$._embedded.topic.name", is(event.getTopic())), hasJsonPath("$._embedded.target.id", is(event.getTarget())), event.getRelated() != null ? @@ -46,7 +46,7 @@ public static Matcher matchNBEventFullEntry(NBEvent event) { ); } - public static Matcher matchNBEventEntry(NBEvent event) { + public static Matcher matchQAEventEntry(QAEvent event) { try { ObjectMapper jsonMapper = new JsonMapper(); return allOf(hasJsonPath("$.id", is(event.getEventId())), @@ -60,7 +60,7 @@ public static Matcher matchNBEventEntry(NBEvent event) { hasJsonPath("$._links.target.href", Matchers.endsWith(event.getEventId() + "/target")), hasJsonPath("$._links.related.href", Matchers.endsWith(event.getEventId() + "/related")), hasJsonPath("$._links.topic.href", Matchers.endsWith(event.getEventId() + "/topic")), - hasJsonPath("$.type", is("nbevent"))); + hasJsonPath("$.type", is("qaevent"))); } catch (JsonProcessingException e) { throw new RuntimeException(e); } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/NBSourceMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/QASourceMatcher.java similarity index 66% rename from dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/NBSourceMatcher.java rename to dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/QASourceMatcher.java index 35031202f0a7..0340315600f7 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/NBSourceMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/QASourceMatcher.java @@ -11,31 +11,31 @@ import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.is; -import org.dspace.app.rest.model.hateoas.NBSourceResource; +import org.dspace.app.rest.model.hateoas.QASourceResource; import org.hamcrest.Matcher; /** - * Matcher related to {@link NBSourceResource}. + * Matcher related to {@link QASourceResource}. * * @author Luca Giamminonni (luca.giamminonni at 4science.it) * */ -public class NBSourceMatcher { +public class QASourceMatcher { - private NBSourceMatcher() { } + private QASourceMatcher() { } - public static Matcher matchNBSourceEntry(String key, int totalEvents) { + public static Matcher matchQASourceEntry(String key, int totalEvents) { return allOf( - hasJsonPath("$.type", is("nbsource")), + hasJsonPath("$.type", is("qasource")), hasJsonPath("$.id", is(key)), hasJsonPath("$.totalEvents", is(totalEvents)) ); } - public static Matcher matchNBSourceEntry(String key) { + public static Matcher matchQASourceEntry(String key) { return allOf( - hasJsonPath("$.type", is("nbsource")), + hasJsonPath("$.type", is("qasource")), hasJsonPath("$.id", is(key)) ); } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/NBTopicMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/QATopicMatcher.java similarity index 69% rename from dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/NBTopicMatcher.java rename to dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/QATopicMatcher.java index 7ad6972b1e30..26ef1e92e920 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/NBTopicMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/QATopicMatcher.java @@ -11,22 +11,22 @@ import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.is; -import org.dspace.app.rest.model.hateoas.NBTopicResource; +import org.dspace.app.rest.model.hateoas.QATopicResource; import org.hamcrest.Matcher; /** - * Matcher related to {@link NBTopicResource}. + * Matcher related to {@link QATopicResource}. * * @author Andrea Bollini (andrea.bollini at 4science.it) * */ -public class NBTopicMatcher { +public class QATopicMatcher { - private NBTopicMatcher() { } + private QATopicMatcher() { } - public static Matcher matchNBTopicEntry(String key, int totalEvents) { + public static Matcher matchQATopicEntry(String key, int totalEvents) { return allOf( - hasJsonPath("$.type", is("nbtopic")), + hasJsonPath("$.type", is("qatopic")), hasJsonPath("$.name", is(key)), hasJsonPath("$.id", is(key.replace("/", "!"))), hasJsonPath("$.totalEvents", is(totalEvents)) @@ -34,9 +34,9 @@ public static Matcher matchNBTopicEntry(String key, int totalEve } - public static Matcher matchNBTopicEntry(String key) { + public static Matcher matchQATopicEntry(String key) { return allOf( - hasJsonPath("$.type", is("nbtopic")), + hasJsonPath("$.type", is("qatopic")), hasJsonPath("$.name", is(key)), hasJsonPath("$.id", is(key.replace("/", "/"))) ); diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index c5bb2207bd92..b2ff6b05815b 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -757,7 +757,7 @@ event.dispatcher.default.class = org.dspace.event.BasicDispatcher # Add rdf here, if you are using dspace-rdf to export your repository content as RDF. # Add iiif here, if you are using dspace-iiif. # Add orcidqueue here, if the integration with ORCID is configured and wish to enable the synchronization queue functionality -event.dispatcher.default.consumers = versioning, discovery, eperson, nbeventsdelete +event.dispatcher.default.consumers = versioning, discovery, eperson, qaeventsdelete # The noindex dispatcher will not create search or browse indexes (useful for batch item imports) event.dispatcher.noindex.class = org.dspace.event.BasicDispatcher @@ -783,9 +783,9 @@ event.consumer.rdf.filters = Community|Collection|Item|Bundle|Bitstream|Site+Add #event.consumer.test.class = org.dspace.event.TestConsumer #event.consumer.test.filters = All+All -# nbevents consumer to delete events related to deleted items -event.consumer.nbeventsdelete.class = org.dspace.app.nbevent.NBEventsDeleteCascadeConsumer -event.consumer.nbeventsdelete.filters = Item+Delete +# qaevents consumer to delete events related to deleted items +event.consumer.qaeventsdelete.class = org.dspace.qaevent.QAEventsDeleteCascadeConsumer +event.consumer.qaeventsdelete.filters = Item+Delete # consumer to maintain versions event.consumer.versioning.class = org.dspace.versioning.VersioningConsumer @@ -1590,7 +1590,7 @@ include = ${module_dir}/irus-statistics.cfg include = ${module_dir}/oai.cfg include = ${module_dir}/openaire-client.cfg include = ${module_dir}/orcid.cfg -include = ${module_dir}/nbevents.cfg +include = ${module_dir}/qaevents.cfg include = ${module_dir}/rdf.cfg include = ${module_dir}/rest.cfg include = ${module_dir}/iiif.cfg diff --git a/dspace/config/hibernate.cfg.xml b/dspace/config/hibernate.cfg.xml index 24d9f04d9071..f8b1c2d56ddd 100644 --- a/dspace/config/hibernate.cfg.xml +++ b/dspace/config/hibernate.cfg.xml @@ -61,7 +61,7 @@ - + diff --git a/dspace/config/modules/nbevents.cfg b/dspace/config/modules/qaevents.cfg similarity index 51% rename from dspace/config/modules/nbevents.cfg rename to dspace/config/modules/qaevents.cfg index d2b5dd12f9a8..79870b197941 100644 --- a/dspace/config/modules/nbevents.cfg +++ b/dspace/config/modules/qaevents.cfg @@ -1,21 +1,21 @@ #---------------------------------------------------------------# -#-------OAIRE Notification Broker Events CONFIGURATIONS---------# +#------- Quality Assurance Broker Events CONFIGURATIONS --------# #---------------------------------------------------------------# # Configuration properties used by data correction service # #---------------------------------------------------------------# -nbevents.solr.server = ${solr.server}/${solr.multicorePrefix}nbevent -# A POST to these url(s) will be done to notify oaire of decision taken for each nbevents -nbevents.openaire.acknowledge-url = https://beta.api-broker.openaire.eu/feedback/events -#nbevents.openaire.acknowledge-url +qaevents.solr.server = ${solr.server}/${solr.multicorePrefix}qaevent +# A POST to these url(s) will be done to notify oaire of decision taken for each qaevents +qaevents.openaire.acknowledge-url = https://beta.api-broker.openaire.eu/feedback/events +#qaevents.openaire.acknowledge-url -# The list of the supported events incoming from openaire (see also dspace/config/spring/api/nbevents.xml) +# The list of the supported events incoming from openaire (see also dspace/config/spring/api/qaevents.xml) # add missing abstract suggestion -nbevents.openaire.import.topic = ENRICH/MISSING/ABSTRACT +qaevents.openaire.import.topic = ENRICH/MISSING/ABSTRACT # add missing publication id suggestion -nbevents.openaire.import.topic = ENRICH/MISSING/PID +qaevents.openaire.import.topic = ENRICH/MISSING/PID # add more publication id suggestion -nbevents.openaire.import.topic = ENRICH/MORE/PID +qaevents.openaire.import.topic = ENRICH/MORE/PID # add missing project suggestion -nbevents.openaire.import.topic = ENRICH/MISSING/PROJECT +qaevents.openaire.import.topic = ENRICH/MISSING/PROJECT # add more project suggestion -nbevents.openaire.import.topic = ENRICH/MORE/PROJECT \ No newline at end of file +qaevents.openaire.import.topic = ENRICH/MORE/PROJECT \ No newline at end of file diff --git a/dspace/config/spring/api/nbevents.xml b/dspace/config/spring/api/qaevents.xml similarity index 78% rename from dspace/config/spring/api/nbevents.xml rename to dspace/config/spring/api/qaevents.xml index 90acf62b1850..8818b8a9cc5d 100644 --- a/dspace/config/spring/api/nbevents.xml +++ b/dspace/config/spring/api/qaevents.xml @@ -9,17 +9,13 @@ - - - + - + + org.dspace.qaevent.QAEventAction interface --> @@ -31,14 +27,14 @@ - + - @@ -49,11 +45,11 @@ - + - + - + + diff --git a/dspace/config/spring/rest/scripts.xml b/dspace/config/spring/rest/scripts.xml index e71072314c46..93f6b93fafed 100644 --- a/dspace/config/spring/rest/scripts.xml +++ b/dspace/config/spring/rest/scripts.xml @@ -8,9 +8,9 @@ - - - + + + diff --git a/dspace/solr/nbevent/conf/protwords.txt b/dspace/solr/qaevent/conf/protwords.txt similarity index 100% rename from dspace/solr/nbevent/conf/protwords.txt rename to dspace/solr/qaevent/conf/protwords.txt diff --git a/dspace/solr/nbevent/conf/schema.xml b/dspace/solr/qaevent/conf/schema.xml similarity index 100% rename from dspace/solr/nbevent/conf/schema.xml rename to dspace/solr/qaevent/conf/schema.xml diff --git a/dspace/solr/nbevent/conf/solrconfig.xml b/dspace/solr/qaevent/conf/solrconfig.xml similarity index 99% rename from dspace/solr/nbevent/conf/solrconfig.xml rename to dspace/solr/qaevent/conf/solrconfig.xml index 0565e56df4de..76f17a3ef308 100644 --- a/dspace/solr/nbevent/conf/solrconfig.xml +++ b/dspace/solr/qaevent/conf/solrconfig.xml @@ -17,7 +17,7 @@ --> diff --git a/dspace-api/src/test/java/org/dspace/AbstractIntegrationTestWithDatabase.java b/dspace-api/src/test/java/org/dspace/AbstractIntegrationTestWithDatabase.java index e27fb19a68eb..6884b949a66a 100644 --- a/dspace-api/src/test/java/org/dspace/AbstractIntegrationTestWithDatabase.java +++ b/dspace-api/src/test/java/org/dspace/AbstractIntegrationTestWithDatabase.java @@ -29,6 +29,8 @@ import org.dspace.eperson.service.EPersonService; import org.dspace.eperson.service.GroupService; import org.dspace.kernel.ServiceManager; +import org.dspace.qaevent.MockQAEventService; +import org.dspace.qaevent.service.QAEventService; import org.dspace.services.factory.DSpaceServicesFactory; import org.dspace.statistics.MockSolrLoggerServiceImpl; import org.dspace.statistics.MockSolrStatisticsCore; @@ -196,6 +198,10 @@ public void destroy() throws Exception { .getServiceByName(AuthoritySearchService.class.getName(), MockAuthoritySolrServiceImpl.class); authorityService.reset(); + MockQAEventService qaEventService = serviceManager + .getServiceByName(QAEventService.class.getName(), MockQAEventService.class); + qaEventService.reset(); + // Reload our ConfigurationService (to reset configs to defaults again) DSpaceServicesFactory.getInstance().getConfigurationService().reloadConfig(); diff --git a/dspace-api/src/test/java/org/dspace/app/scripts/handler/impl/TestDSpaceRunnableHandler.java b/dspace-api/src/test/java/org/dspace/app/scripts/handler/impl/TestDSpaceRunnableHandler.java index aced81cbdfdb..7cc1e8cb45d7 100644 --- a/dspace-api/src/test/java/org/dspace/app/scripts/handler/impl/TestDSpaceRunnableHandler.java +++ b/dspace-api/src/test/java/org/dspace/app/scripts/handler/impl/TestDSpaceRunnableHandler.java @@ -61,6 +61,12 @@ public void logError(String message) { errorMessages.add(message); } + @Override + public void logError(String message, Throwable throwable) { + super.logError(message, throwable); + errorMessages.add(message); + } + public List getInfoMessages() { return infoMessages; } diff --git a/dspace-api/src/test/java/org/dspace/matcher/QAEventMatcher.java b/dspace-api/src/test/java/org/dspace/matcher/QAEventMatcher.java new file mode 100644 index 000000000000..1e53bff4f9dd --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/matcher/QAEventMatcher.java @@ -0,0 +1,104 @@ +/** + * 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.matcher; + +import static org.dspace.content.QAEvent.OPENAIRE_SOURCE; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.nullValue; + +import org.dspace.content.Item; +import org.dspace.content.QAEvent; +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hamcrest.TypeSafeMatcher; + +/** + * Implementation of {@link org.hamcrest.Matcher} to match a QAEvent by all its + * attributes. + * + * @author Luca Giamminonni (luca.giamminonni at 4science.it) + * + */ +public class QAEventMatcher extends TypeSafeMatcher { + + private Matcher eventIdMatcher; + + private Matcher originalIdMatcher; + + private Matcher relatedMatcher; + + private Matcher sourceMatcher; + + private Matcher statusMatcher; + + private Matcher targetMatcher; + + private Matcher titleMatcher; + + private Matcher messageMatcher; + + private Matcher topicMatcher; + + private Matcher trustMatcher; + + private QAEventMatcher(Matcher eventIdMatcher, Matcher originalIdMatcher, + Matcher relatedMatcher, Matcher sourceMatcher, Matcher statusMatcher, + Matcher targetMatcher, Matcher titleMatcher, Matcher messageMatcher, + Matcher topicMatcher, Matcher trustMatcher) { + this.eventIdMatcher = eventIdMatcher; + this.originalIdMatcher = originalIdMatcher; + this.relatedMatcher = relatedMatcher; + this.sourceMatcher = sourceMatcher; + this.statusMatcher = statusMatcher; + this.targetMatcher = targetMatcher; + this.titleMatcher = titleMatcher; + this.messageMatcher = messageMatcher; + this.topicMatcher = topicMatcher; + this.trustMatcher = trustMatcher; + } + + public static QAEventMatcher pendingOpenaireEventWith(String originalId, Item target, + String title, String message, String topic, Double trust) { + + return new QAEventMatcher(notNullValue(String.class), is(originalId), nullValue(String.class), + is(OPENAIRE_SOURCE), is("PENDING"), is(target.getID().toString()), is(title), is(message), is(topic), + is(trust)); + + } + + @Override + public boolean matchesSafely(QAEvent event) { + return eventIdMatcher.matches(event.getEventId()) + && originalIdMatcher.matches(event.getOriginalId()) + && relatedMatcher.matches(event.getRelated()) + && sourceMatcher.matches(event.getSource()) + && statusMatcher.matches(event.getStatus()) + && targetMatcher.matches(event.getTarget()) + && titleMatcher.matches(event.getTitle()) + && messageMatcher.matches(event.getMessage()) + && topicMatcher.matches(event.getTopic()) + && trustMatcher.matches(event.getTrust()); + } + + @Override + public void describeTo(Description description) { + description.appendText("a QA event with the following attributes:") + .appendText(" event id ").appendDescriptionOf(eventIdMatcher) + .appendText(", original id ").appendDescriptionOf(originalIdMatcher) + .appendText(", related ").appendDescriptionOf(relatedMatcher) + .appendText(", source ").appendDescriptionOf(sourceMatcher) + .appendText(", status ").appendDescriptionOf(statusMatcher) + .appendText(", target ").appendDescriptionOf(targetMatcher) + .appendText(", title ").appendDescriptionOf(titleMatcher) + .appendText(", message ").appendDescriptionOf(messageMatcher) + .appendText(", topic ").appendDescriptionOf(topicMatcher) + .appendText(" and trust ").appendDescriptionOf(trustMatcher); + } + +} diff --git a/dspace-api/src/test/java/org/dspace/qaevent/script/OpenaireEventsRunnableIT.java b/dspace-api/src/test/java/org/dspace/qaevent/script/OpenaireEventsRunnableIT.java new file mode 100644 index 000000000000..0392efd3b14f --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/qaevent/script/OpenaireEventsRunnableIT.java @@ -0,0 +1,247 @@ +/** + * 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.qaevent.script; + +import static org.dspace.app.matcher.LambdaMatcher.matches; +import static org.dspace.content.QAEvent.OPENAIRE_SOURCE; +import static org.dspace.matcher.QAEventMatcher.pendingOpenaireEventWith; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.is; + +import java.io.File; +import java.net.URL; +import java.util.List; + +import org.dspace.AbstractIntegrationTestWithDatabase; +import org.dspace.app.launcher.ScriptLauncher; +import org.dspace.app.scripts.handler.impl.TestDSpaceRunnableHandler; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.ItemBuilder; +import org.dspace.content.Collection; +import org.dspace.content.Item; +import org.dspace.qaevent.QASource; +import org.dspace.qaevent.QATopic; +import org.dspace.qaevent.service.QAEventService; +import org.dspace.utils.DSpace; +import org.junit.Before; +import org.junit.Test; + +/** + * Integration tests for {@link OpenaireEventsRunnable}. + * + * @author Luca Giamminonni (luca.giamminonni at 4science.it) + * + */ +public class OpenaireEventsRunnableIT extends AbstractIntegrationTestWithDatabase { + + private static final String BASE_JSON_DIR_PATH = "org/dspace/app/openaire-events/"; + + private QAEventService qaEventService = new DSpace().getSingletonService(QAEventService.class); + + private Collection collection; + + @Before + public void setup() { + + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + collection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection") + .build(); + + context.restoreAuthSystemState(); + } + + @Test + @SuppressWarnings("unchecked") + public void testManyEventsImport() throws Exception { + + context.turnOffAuthorisationSystem(); + + Item firstItem = ItemBuilder.createItem(context, collection) + .withTitle("Egypt, crossroad of translations and literary interweavings") + .withHandle("123456789/99998") + .build(); + + Item secondItem = ItemBuilder.createItem(context, collection) + .withTitle("Test item") + .withHandle("123456789/99999") + .build(); + + context.restoreAuthSystemState(); + + TestDSpaceRunnableHandler handler = new TestDSpaceRunnableHandler(); + + String[] args = new String[] { "import-openaire-events", "-f", getFileLocation("events.json") }; + ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl), handler, kernelImpl); + + assertThat(handler.getErrorMessages(), empty()); + assertThat(handler.getWarningMessages(), empty()); + assertThat(handler.getInfoMessages(), contains("Found 2 events to store")); + + List sources = qaEventService.findAllSources(0, 20); + assertThat(sources, hasSize(1)); + + assertThat(sources.get(0).getName(), is(OPENAIRE_SOURCE)); + assertThat(sources.get(0).getTotalEvents(), is(2L)); + + List topics = qaEventService.findAllTopics(0, 20); + assertThat(topics, hasSize(2)); + assertThat(topics, containsInAnyOrder( + matches(topic -> topic.getKey().equals("ENRICH/MORE/PROJECT") && topic.getTotalEvents() == 1L), + matches(topic -> topic.getKey().equals("ENRICH/MISSING/ABSTRACT") && topic.getTotalEvents() == 1L))); + + String projectMessage = "{\"projects[0].acronym\":\"PAThs\",\"projects[0].code\":\"687567\"," + + "\"projects[0].funder\":\"EC\",\"projects[0].fundingProgram\":\"H2020\"," + + "\"projects[0].jurisdiction\":\"EU\"," + + "\"projects[0].openaireId\":\"40|corda__h2020::6e32f5eb912688f2424c68b851483ea4\"," + + "\"projects[0].title\":\"Tracking Papyrus and Parchment Paths\"}"; + + assertThat(qaEventService.findEventsByTopic("ENRICH/MORE/PROJECT"), contains( + pendingOpenaireEventWith("oai:www.openstarts.units.it:123456789/99998", firstItem, + "Egypt, crossroad of translations and literary interweavings", projectMessage, + "ENRICH/MORE/PROJECT", 1.00d))); + + String abstractMessage = "{\"abstracts[0]\":\"Missing Abstract\"}"; + + assertThat(qaEventService.findEventsByTopic("ENRICH/MISSING/ABSTRACT"), contains( + pendingOpenaireEventWith("oai:www.openstarts.units.it:123456789/99999", secondItem, "Test Publication", + abstractMessage, "ENRICH/MISSING/ABSTRACT", 1.00d))); + + } + + @Test + public void testManyEventsImportWithUnknownHandle() throws Exception { + + context.turnOffAuthorisationSystem(); + + Item item = ItemBuilder.createItem(context, collection) + .withTitle("Test item") + .withHandle("123456789/99999") + .build(); + + context.restoreAuthSystemState(); + + TestDSpaceRunnableHandler handler = new TestDSpaceRunnableHandler(); + + String[] args = new String[] { "import-openaire-events", "-f", getFileLocation("events.json") }; + ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl), handler, kernelImpl); + + assertThat(handler.getErrorMessages(), empty()); + assertThat(handler.getWarningMessages(), + contains("IllegalArgumentException: Skipped event b4e09c71312cd7c397969f56c900823f" + + " related to the oai record oai:www.openstarts.units.it:123456789/99998 as the record was not found")); + assertThat(handler.getInfoMessages(), contains("Found 2 events to store")); + + List sources = qaEventService.findAllSources(0, 20); + assertThat(sources, hasSize(1)); + + assertThat(sources.get(0).getName(), is(OPENAIRE_SOURCE)); + assertThat(sources.get(0).getTotalEvents(), is(1L)); + + List topics = qaEventService.findAllTopics(0, 20); + assertThat(topics, hasSize(1)); + QATopic topic = topics.get(0); + assertThat(topic.getKey(), is("ENRICH/MISSING/ABSTRACT")); + assertThat(topic.getTotalEvents(), is(1L)); + + String abstractMessage = "{\"abstracts[0]\":\"Missing Abstract\"}"; + + assertThat(qaEventService.findEventsByTopic("ENRICH/MISSING/ABSTRACT"), contains( + pendingOpenaireEventWith("oai:www.openstarts.units.it:123456789/99999", item, "Test Publication", + abstractMessage, "ENRICH/MISSING/ABSTRACT", 1.00d))); + + } + + @Test + public void testManyEventsImportWithUnknownTopic() throws Exception { + + context.turnOffAuthorisationSystem(); + + ItemBuilder.createItem(context, collection) + .withTitle("Egypt, crossroad of translations and literary interweavings") + .withHandle("123456789/99998") + .build(); + + Item secondItem = ItemBuilder.createItem(context, collection) + .withTitle("Test item") + .withHandle("123456789/99999") + .build(); + + context.restoreAuthSystemState(); + + TestDSpaceRunnableHandler handler = new TestDSpaceRunnableHandler(); + + String[] args = new String[] { "import-openaire-events", "-f", getFileLocation("unknown-topic-events.json") }; + ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl), handler, kernelImpl); + + assertThat(handler.getErrorMessages(), empty()); + assertThat(handler.getWarningMessages(), + contains("Event for topic ENRICH/MORE/UNKNOWN is not allowed in the qaevents.cfg")); + assertThat(handler.getInfoMessages(), contains("Found 2 events to store")); + + List sources = qaEventService.findAllSources(0, 20); + assertThat(sources, hasSize(1)); + + assertThat(sources.get(0).getName(), is(OPENAIRE_SOURCE)); + assertThat(sources.get(0).getTotalEvents(), is(1L)); + + List topics = qaEventService.findAllTopics(0, 20); + assertThat(topics, hasSize(1)); + QATopic topic = topics.get(0); + assertThat(topic.getKey(), is("ENRICH/MISSING/ABSTRACT")); + assertThat(topic.getTotalEvents(), is(1L)); + + String abstractMessage = "{\"abstracts[0]\":\"Missing Abstract\"}"; + + assertThat(qaEventService.findEventsByTopic("ENRICH/MISSING/ABSTRACT"), contains( + pendingOpenaireEventWith("oai:www.openstarts.units.it:123456789/99999", secondItem, "Test Publication", + abstractMessage, "ENRICH/MISSING/ABSTRACT", 1.00d))); + + } + + @Test + public void testWithoutEvents() throws Exception { + + TestDSpaceRunnableHandler handler = new TestDSpaceRunnableHandler(); + + String[] args = new String[] { "import-openaire-events", "-f", getFileLocation("empty-events.json") }; + ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl), handler, kernelImpl); + + assertThat(handler.getErrorMessages(), + contains(containsString("A not recoverable error occurs during OPENAIRE events import."))); + assertThat(handler.getWarningMessages(),empty()); + assertThat(handler.getInfoMessages(), empty()); + + List sources = qaEventService.findAllSources(0, 20); + assertThat(sources, hasSize(1)); + + assertThat(sources.get(0).getName(), is(OPENAIRE_SOURCE)); + assertThat(sources.get(0).getTotalEvents(), is(0L)); + + assertThat(qaEventService.findAllTopics(0, 20), empty()); + } + + private String getFileLocation(String fileName) throws Exception { + URL resource = getClass().getClassLoader().getResource(BASE_JSON_DIR_PATH + fileName); + if (resource == null) { + throw new IllegalStateException("No resource found named " + BASE_JSON_DIR_PATH + fileName); + } + return new File(resource.getFile()).getAbsolutePath(); + } +} diff --git a/dspace-api/src/test/resources/org/dspace/app/openaire-events/empty-events.json b/dspace-api/src/test/resources/org/dspace/app/openaire-events/empty-events.json new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/dspace-api/src/test/resources/org/dspace/app/openaire-events/events.json b/dspace-api/src/test/resources/org/dspace/app/openaire-events/events.json new file mode 100644 index 000000000000..7d8dbd37f16a --- /dev/null +++ b/dspace-api/src/test/resources/org/dspace/app/openaire-events/events.json @@ -0,0 +1,29 @@ +[ + + { + "originalId": "oai:www.openstarts.units.it:123456789/99998", + "title": "Egypt, crossroad of translations and literary interweavings", + "topic": "ENRICH/MORE/PROJECT", + "trust": 1.0, + "message": { + "projects[0].acronym": "PAThs", + "projects[0].code": "687567", + "projects[0].funder": "EC", + "projects[0].fundingProgram": "H2020", + "projects[0].jurisdiction": "EU", + "projects[0].openaireId": "40|corda__h2020::6e32f5eb912688f2424c68b851483ea4", + "projects[0].title": "Tracking Papyrus and Parchment Paths" + } + }, + + { + "originalId": "oai:www.openstarts.units.it:123456789/99999", + "title": "Test Publication", + "topic": "ENRICH/MISSING/ABSTRACT", + "trust": 1.0, + "message": { + "abstracts[0]": "Missing Abstract" + } + } + +] \ No newline at end of file diff --git a/dspace-api/src/test/resources/org/dspace/app/openaire-events/unknown-topic-events.json b/dspace-api/src/test/resources/org/dspace/app/openaire-events/unknown-topic-events.json new file mode 100644 index 000000000000..d281db450f0c --- /dev/null +++ b/dspace-api/src/test/resources/org/dspace/app/openaire-events/unknown-topic-events.json @@ -0,0 +1,20 @@ +[ + + { + "originalId": "oai:www.openstarts.units.it:123456789/99998", + "title": "Egypt, crossroad of translations and literary interweavings (3rd-6th centuries). A reconsideration of earlier Coptic literature", + "topic": "ENRICH/MORE/UNKNOWN", + "trust": 1.0 + }, + + { + "originalId": "oai:www.openstarts.units.it:123456789/99999", + "title": "Test Publication", + "topic": "ENRICH/MISSING/ABSTRACT", + "trust": 1.0, + "message": { + "abstracts[0]": "Missing Abstract" + } + } + +] \ No newline at end of file diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/QAEventStatusReplaceOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/QAEventStatusReplaceOperation.java index 5f58f014ab6c..5f6ff02accdb 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/QAEventStatusReplaceOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/QAEventStatusReplaceOperation.java @@ -13,7 +13,7 @@ import org.dspace.app.rest.model.patch.Operation; import org.dspace.content.QAEvent; import org.dspace.core.Context; -import org.dspace.qaevent.QAEventActionService; +import org.dspace.qaevent.service.QAEventActionService; import org.dspace.services.RequestService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; diff --git a/dspace/config/spring/api/qaevents.xml b/dspace/config/spring/api/qaevents.xml index 8818b8a9cc5d..c292cb4beb27 100644 --- a/dspace/config/spring/api/qaevents.xml +++ b/dspace/config/spring/api/qaevents.xml @@ -11,7 +11,7 @@ - + - + @@ -45,11 +45,11 @@ - + - + - + - + From 6f8e722c030acf4f2950c157a9f6d06538747fc0 Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Tue, 12 Jul 2022 09:34:44 +0200 Subject: [PATCH 017/510] [CST-5249] Fixed OpenaireEventsRunnable test --- .../test/java/org/dspace/qaevent/MockQAEventService.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/dspace-api/src/test/java/org/dspace/qaevent/MockQAEventService.java b/dspace-api/src/test/java/org/dspace/qaevent/MockQAEventService.java index 443e6f8d39a8..3d460015f7e0 100644 --- a/dspace-api/src/test/java/org/dspace/qaevent/MockQAEventService.java +++ b/dspace-api/src/test/java/org/dspace/qaevent/MockQAEventService.java @@ -7,6 +7,9 @@ */ package org.dspace.qaevent; +import java.io.IOException; + +import org.apache.solr.client.solrj.SolrServerException; import org.dspace.qaevent.service.impl.QAEventServiceImpl; import org.dspace.solr.MockSolrServer; import org.springframework.beans.factory.DisposableBean; @@ -29,6 +32,11 @@ public void afterPropertiesSet() throws Exception { /** Clear all records from the search core. */ public void reset() { mockSolrServer.reset(); + try { + mockSolrServer.getSolrServer().commit(); + } catch (SolrServerException | IOException e) { + throw new RuntimeException(e); + } } @Override From edb7282b56055add0859862dc692c672f309fde2 Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Tue, 12 Jul 2022 09:43:14 +0200 Subject: [PATCH 018/510] [CST-5249] Fixed LGTM alerts --- .../java/org/dspace/qaevent/service/QAEventService.java | 6 +----- .../org/dspace/qaevent/service/impl/QAEventServiceImpl.java | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/qaevent/service/QAEventService.java b/dspace-api/src/main/java/org/dspace/qaevent/service/QAEventService.java index a7519aa7b08d..dc3ca914e6ed 100644 --- a/dspace-api/src/main/java/org/dspace/qaevent/service/QAEventService.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/service/QAEventService.java @@ -26,7 +26,6 @@ public interface QAEventService { /** * Find all the event's topics. * - * @param context the DSpace context * @param offset the offset to apply * @param pageSize the page size * @return the topics list @@ -36,7 +35,6 @@ public interface QAEventService { /** * Find all the event's topics related to the given source. * - * @param context the DSpace context * @param source the source to search for * @param offset the offset to apply * @param pageSize the page size @@ -47,7 +45,6 @@ public interface QAEventService { /** * Count all the event's topics. * - * @param context the DSpace context * @return the count result */ public long countTopics(); @@ -55,7 +52,6 @@ public interface QAEventService { /** * Count all the event's topics related to the given source. * - * @param context the DSpace context * @param source the source to search for * @return the count result */ @@ -116,7 +112,7 @@ public List findEventsByTopicAndPage(String topic, long offset, int pag /** * Delete events by the given target id. * - * @param id the id of the target id + * @param targetId the id of the target id */ public void deleteEventsByTargetId(UUID targetId); diff --git a/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventServiceImpl.java b/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventServiceImpl.java index d536c8a1e93e..bbb6990bb6b3 100644 --- a/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventServiceImpl.java @@ -429,7 +429,7 @@ private String getResourceUUID(Context context, String originalId) throws Except // oai:www.openstarts.units.it:10077/21486 private String getHandleFromOriginalId(String originalId) { - Integer startPosition = originalId.lastIndexOf(':'); + int startPosition = originalId.lastIndexOf(':'); if (startPosition != -1) { return originalId.substring(startPosition + 1, originalId.length()); } else { From c0e71ba26339d3fb9056b5b74d27e76e449d64ca Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Tue, 12 Jul 2022 10:21:31 +0200 Subject: [PATCH 019/510] [CST-5249] Fixed LGTM alerts --- .../java/org/dspace/qaevent/service/QAEventService.java | 8 ++++---- .../dspace/app/rest/repository/QAEventRestRepository.java | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/qaevent/service/QAEventService.java b/dspace-api/src/main/java/org/dspace/qaevent/service/QAEventService.java index dc3ca914e6ed..ea923251b826 100644 --- a/dspace-api/src/main/java/org/dspace/qaevent/service/QAEventService.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/service/QAEventService.java @@ -35,10 +35,10 @@ public interface QAEventService { /** * Find all the event's topics related to the given source. * - * @param source the source to search for - * @param offset the offset to apply - * @param pageSize the page size - * @return the topics list + * @param source the source to search for + * @param offset the offset to apply + * @param count the page size + * @return the topics list */ public List findAllTopicsBySource(String source, long offset, long count); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventRestRepository.java index 431c236de78d..bd9b31e14cad 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventRestRepository.java @@ -77,7 +77,7 @@ public QAEventRest findOne(Context context, String id) { public Page findByTopic(Context context, @Parameter(value = "topic", required = true) String topic, Pageable pageable) { List qaEvents = null; - Long count = 0L; + long count = 0L; boolean ascending = false; if (pageable.getSort() != null && pageable.getSort().getOrderFor(ORDER_FIELD) != null) { ascending = pageable.getSort().getOrderFor(ORDER_FIELD).getDirection() == Direction.ASC; From dd46e54b811f31e29b9634e0b1507ae074f896c3 Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Tue, 12 Jul 2022 12:53:01 +0200 Subject: [PATCH 020/510] [CST-5247] Renamed OpenaireEventsRunnable to OpenaireEventsImport --- dspace-api/pom.xml | 6 ++++++ ...aireEventsRunnable.java => OpenaireEventsImport.java} | 9 +++++---- ...entsRunnableCli.java => OpenaireEventsImportCli.java} | 8 ++++---- ...a => OpenaireEventsImportCliScriptConfiguration.java} | 6 +++--- ...java => OpenaireEventsImportScriptConfiguration.java} | 4 ++-- .../test/data/dspaceFolder/config/spring/api/scripts.xml | 4 ++-- ...EventsRunnableIT.java => OpenaireEventsImportIT.java} | 4 ++-- dspace/config/spring/api/scripts.xml | 4 ++-- dspace/config/spring/rest/scripts.xml | 4 ++-- 9 files changed, 28 insertions(+), 21 deletions(-) rename dspace-api/src/main/java/org/dspace/qaevent/script/{OpenaireEventsRunnable.java => OpenaireEventsImport.java} (94%) rename dspace-api/src/main/java/org/dspace/qaevent/script/{OpenaireEventsRunnableCli.java => OpenaireEventsImportCli.java} (79%) rename dspace-api/src/main/java/org/dspace/qaevent/script/{OpenaireEventsCliScriptConfiguration.java => OpenaireEventsImportCliScriptConfiguration.java} (74%) rename dspace-api/src/main/java/org/dspace/qaevent/script/{OpenaireEventsScriptConfiguration.java => OpenaireEventsImportScriptConfiguration.java} (91%) rename dspace-api/src/test/java/org/dspace/qaevent/script/{OpenaireEventsRunnableIT.java => OpenaireEventsImportIT.java} (98%) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index cf37158fdc85..dc67ae8d7130 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -877,6 +877,12 @@ funders-model 2.0.0 + + + eu.openaire + broker-client + 1.1.1 + org.mock-server diff --git a/dspace-api/src/main/java/org/dspace/qaevent/script/OpenaireEventsRunnable.java b/dspace-api/src/main/java/org/dspace/qaevent/script/OpenaireEventsImport.java similarity index 94% rename from dspace-api/src/main/java/org/dspace/qaevent/script/OpenaireEventsRunnable.java rename to dspace-api/src/main/java/org/dspace/qaevent/script/OpenaireEventsImport.java index ecc6cef1167e..d0d21d2e4fe1 100644 --- a/dspace-api/src/main/java/org/dspace/qaevent/script/OpenaireEventsRunnable.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/script/OpenaireEventsImport.java @@ -54,7 +54,8 @@ * @author Alessandro Martelli (alessandro.martelli at 4science.it) * */ -public class OpenaireEventsRunnable extends DSpaceRunnable> { +public class OpenaireEventsImport + extends DSpaceRunnable> { private QAEventService qaEventService; @@ -68,9 +69,9 @@ public class OpenaireEventsRunnable extends DSpaceRunnable - extends OpenaireEventsScriptConfiguration { +public class OpenaireEventsImportCliScriptConfiguration + extends OpenaireEventsImportScriptConfiguration { @Override public Options getOptions() { diff --git a/dspace-api/src/main/java/org/dspace/qaevent/script/OpenaireEventsScriptConfiguration.java b/dspace-api/src/main/java/org/dspace/qaevent/script/OpenaireEventsImportScriptConfiguration.java similarity index 91% rename from dspace-api/src/main/java/org/dspace/qaevent/script/OpenaireEventsScriptConfiguration.java rename to dspace-api/src/main/java/org/dspace/qaevent/script/OpenaireEventsImportScriptConfiguration.java index fc32aa6818ca..1bf5ea85d879 100644 --- a/dspace-api/src/main/java/org/dspace/qaevent/script/OpenaireEventsScriptConfiguration.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/script/OpenaireEventsImportScriptConfiguration.java @@ -23,7 +23,7 @@ * @author Alessandro Martelli (alessandro.martelli at 4science.it) * */ -public class OpenaireEventsScriptConfiguration extends ScriptConfiguration { +public class OpenaireEventsImportScriptConfiguration extends ScriptConfiguration { @Autowired private AuthorizeService authorizeService; @@ -37,7 +37,7 @@ public Class getDspaceRunnableClass() { /** * Generic setter for the dspaceRunnableClass - * @param dspaceRunnableClass The dspaceRunnableClass to be set on this OpenaireEventsScriptConfiguration + * @param dspaceRunnableClass The dspaceRunnableClass to be set on this OpenaireEventsImportScriptConfiguration */ @Override public void setDspaceRunnableClass(Class dspaceRunnableClass) { diff --git a/dspace-api/src/test/data/dspaceFolder/config/spring/api/scripts.xml b/dspace-api/src/test/data/dspaceFolder/config/spring/api/scripts.xml index 55b21da4f281..512c34aa26f3 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/spring/api/scripts.xml +++ b/dspace-api/src/test/data/dspaceFolder/config/spring/api/scripts.xml @@ -65,9 +65,9 @@ - + - + diff --git a/dspace-api/src/test/java/org/dspace/qaevent/script/OpenaireEventsRunnableIT.java b/dspace-api/src/test/java/org/dspace/qaevent/script/OpenaireEventsImportIT.java similarity index 98% rename from dspace-api/src/test/java/org/dspace/qaevent/script/OpenaireEventsRunnableIT.java rename to dspace-api/src/test/java/org/dspace/qaevent/script/OpenaireEventsImportIT.java index 0392efd3b14f..258ede4ef0ed 100644 --- a/dspace-api/src/test/java/org/dspace/qaevent/script/OpenaireEventsRunnableIT.java +++ b/dspace-api/src/test/java/org/dspace/qaevent/script/OpenaireEventsImportIT.java @@ -38,12 +38,12 @@ import org.junit.Test; /** - * Integration tests for {@link OpenaireEventsRunnable}. + * Integration tests for {@link OpenaireEventsImport}. * * @author Luca Giamminonni (luca.giamminonni at 4science.it) * */ -public class OpenaireEventsRunnableIT extends AbstractIntegrationTestWithDatabase { +public class OpenaireEventsImportIT extends AbstractIntegrationTestWithDatabase { private static final String BASE_JSON_DIR_PATH = "org/dspace/app/openaire-events/"; diff --git a/dspace/config/spring/api/scripts.xml b/dspace/config/spring/api/scripts.xml index 132dd347c8dc..de4072b44f81 100644 --- a/dspace/config/spring/api/scripts.xml +++ b/dspace/config/spring/api/scripts.xml @@ -4,9 +4,9 @@ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"> - + - + diff --git a/dspace/config/spring/rest/scripts.xml b/dspace/config/spring/rest/scripts.xml index b86427bb8b12..39f55fc2f713 100644 --- a/dspace/config/spring/rest/scripts.xml +++ b/dspace/config/spring/rest/scripts.xml @@ -8,9 +8,9 @@ - + - + From ca36903cdee953461327792d4ff7cdde5c61c38b Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Tue, 12 Jul 2022 18:17:23 +0200 Subject: [PATCH 021/510] [CST-5247] Added OPENAIRE broker client to directly download events --- .../qaevent/script/OpenaireEventsImport.java | 197 ++++++++-- ...enaireEventsImportScriptConfiguration.java | 10 +- .../qaevent/service/BrokerClientFactory.java | 31 ++ .../service/impl/BrokerClientFactoryImpl.java | 35 ++ .../org/dspace/matcher/QAEventMatcher.java | 13 + .../org/dspace/matcher/QASourceMatcher.java | 58 +++ .../org/dspace/matcher/QATopicMatcher.java | 58 +++ .../script/OpenaireEventsImportIT.java | 368 ++++++++++++++---- .../openaire-events/empty-events-list.json | 1 + .../{empty-events.json => empty-file.json} | 0 .../openaire-events/unknown-topic-events.json | 4 +- dspace/config/modules/qaevents.cfg | 5 +- dspace/config/spring/api/qaevents.xml | 4 + 13 files changed, 670 insertions(+), 114 deletions(-) create mode 100644 dspace-api/src/main/java/org/dspace/qaevent/service/BrokerClientFactory.java create mode 100644 dspace-api/src/main/java/org/dspace/qaevent/service/impl/BrokerClientFactoryImpl.java create mode 100644 dspace-api/src/test/java/org/dspace/matcher/QASourceMatcher.java create mode 100644 dspace-api/src/test/java/org/dspace/matcher/QATopicMatcher.java create mode 100644 dspace-api/src/test/resources/org/dspace/app/openaire-events/empty-events-list.json rename dspace-api/src/test/resources/org/dspace/app/openaire-events/{empty-events.json => empty-file.json} (100%) diff --git a/dspace-api/src/main/java/org/dspace/qaevent/script/OpenaireEventsImport.java b/dspace-api/src/main/java/org/dspace/qaevent/script/OpenaireEventsImport.java index d0d21d2e4fe1..e45dc3e159c7 100644 --- a/dspace-api/src/main/java/org/dspace/qaevent/script/OpenaireEventsImport.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/script/OpenaireEventsImport.java @@ -7,15 +7,24 @@ */ package org.dspace.qaevent.script; -import static org.apache.commons.lang3.exception.ExceptionUtils.getRootCauseMessage; +import static org.apache.commons.lang3.StringUtils.isNotBlank; +import static org.apache.commons.lang3.StringUtils.substringAfter; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; import java.sql.SQLException; +import java.util.List; import java.util.UUID; +import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.json.JsonMapper; +import eu.dnetlib.broker.BrokerClient; import org.apache.commons.cli.ParseException; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.exception.ExceptionUtils; @@ -23,9 +32,11 @@ import org.dspace.core.Context; import org.dspace.eperson.EPerson; import org.dspace.eperson.factory.EPersonServiceFactory; +import org.dspace.qaevent.service.BrokerClientFactory; import org.dspace.qaevent.service.QAEventService; import org.dspace.scripts.DSpaceRunnable; import org.dspace.services.ConfigurationService; +import org.dspace.services.factory.DSpaceServicesFactory; import org.dspace.utils.DSpace; /** @@ -52,6 +63,7 @@ * * * @author Alessandro Martelli (alessandro.martelli at 4science.it) + * @author Luca Giamminonni (luca.giamminonni at 4Science.it) * */ public class OpenaireEventsImport @@ -63,8 +75,16 @@ public class OpenaireEventsImport private ConfigurationService configurationService; + private BrokerClient brokerClient; + + private ObjectMapper jsonMapper; + + private URL openaireBrokerURL; + private String fileLocation; + private String email; + private Context context; @Override @@ -77,81 +97,100 @@ public OpenaireEventsImportScriptConfiguration getScriptConfiguration() { @Override public void setup() throws ParseException { - DSpace dspace = new DSpace(); - qaEventService = dspace.getSingletonService(QAEventService.class); - if (qaEventService == null) { - throw new IllegalStateException("qaEventService is NULL. Error in spring configuration"); - } + jsonMapper = new JsonMapper(); + jsonMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - configurationService = dspace.getConfigurationService(); + qaEventService = new DSpace().getSingletonService(QAEventService.class); + configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); + brokerClient = BrokerClientFactory.getInstance().getBrokerClient(); topicsToImport = configurationService.getArrayProperty("qaevents.openaire.import.topic"); + openaireBrokerURL = getOpenaireBrokerUri(); fileLocation = commandLine.getOptionValue("f"); + email = commandLine.getOptionValue("e"); } @Override public void internalRun() throws Exception { - if (StringUtils.isEmpty(fileLocation)) { - throw new IllegalArgumentException("No file location was entered"); + if (StringUtils.isAllBlank(fileLocation, email)) { + throw new IllegalArgumentException("One parameter between the location of the file and the email " + + "must be entered to proceed with the import."); + } + + if (StringUtils.isNoneBlank(fileLocation, email)) { + throw new IllegalArgumentException("Only one parameter between the location of the file and the email " + + "must be entered to proceed with the import."); } context = new Context(); assignCurrentUserInContext(); try { - runOpenaireEventsImport(); + importOpenaireEvents(); } catch (Exception ex) { - handler.logError("A not recoverable error occurs during OPENAIRE events import. " - + ExceptionUtils.getRootCauseMessage(ex), ex); + handler.logError("A not recoverable error occurs during OPENAIRE events import: " + getMessage(ex), ex); throw ex; } } /** - * Read the OPENAIRE events from the given JSON file and try to store them. + * Read the OPENAIRE events from the given JSON file or directly from the + * OPENAIRE broker and try to store them. */ - private void runOpenaireEventsImport() { + private void importOpenaireEvents() throws Exception { + + if (StringUtils.isNotBlank(fileLocation)) { + handler.logInfo("Trying to read the QA events from the provided file"); + importOpenaireEventsFromFile(); + } else { + handler.logInfo("Trying to read the QA events from the OPENAIRE broker"); + importOpenaireEventsFromBroker(); + } - QAEvent[] qaEvents = readOpenaireQAEventsFromJsonFile(); - handler.logInfo("Found " + qaEvents.length + " events to store"); + } - for (QAEvent event : qaEvents) { - try { - storeOpenaireQAEvent(event); - } catch (RuntimeException e) { - handler.logWarning(getRootCauseMessage(e)); - } - } + /** + * Read the OPENAIRE events from the given file location and try to store them. + */ + private void importOpenaireEventsFromFile() throws Exception { + + InputStream eventsFileInputStream = getQAEventsFileInputStream(); + List qaEvents = readOpenaireQAEventsFromJson(eventsFileInputStream); + + handler.logInfo("Found " + qaEvents.size() + " events in the given file"); + + storeOpenaireQAEvents(qaEvents); } /** - * Read all the QAEvent present in the given file. - * - * @return the QA events to be imported - * @throws Exception if an oerror occurs during the file reading + * Import the OPENAIRE events from the Broker using the subscription related to + * the given email and try to store them. */ - private QAEvent[] readOpenaireQAEventsFromJsonFile() { - try { + private void importOpenaireEventsFromBroker() { - ObjectMapper jsonMapper = new JsonMapper(); - jsonMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - return jsonMapper.readValue(getQAEventsFileInputStream(), QAEvent[].class); + List subscriptionIds = listEmailSubscriptions(); + + handler.logInfo("Found " + subscriptionIds.size() + " subscriptions related to the given email"); + + for (String subscriptionId : subscriptionIds) { + + List events = readOpenaireQAEventsFromBroker(subscriptionId); + + handler.logInfo("Found " + events.size() + " events from the subscription " + subscriptionId); + + storeOpenaireQAEvents(events); - } catch (Exception ex) { - throw new IllegalArgumentException("An error occurs parsing the OPENAIRE QA events json", ex); } } /** * Obtain an InputStream from the runnable instance. - * @return - * @throws Exception */ private InputStream getQAEventsFileInputStream() throws Exception { return handler.getFileStream(context, fileLocation) @@ -159,6 +198,50 @@ private InputStream getQAEventsFileInputStream() throws Exception { + "found for filename: " + fileLocation)); } + /** + * Read all the QAEvent from the OPENAIRE Broker related to the subscription + * with the given id. + */ + private List readOpenaireQAEventsFromBroker(String subscriptionId) { + + try { + InputStream eventsInputStream = getEventsBySubscriptions(subscriptionId); + return readOpenaireQAEventsFromJson(eventsInputStream); + } catch (Exception ex) { + handler.logError("An error occurs downloading the events related to the subscription " + + subscriptionId + ": " + getMessage(ex), ex); + } + + return List.of(); + + } + + /** + * Read all the QAEvent present in the given input stream. + * + * @return the QA events to be imported + */ + private List readOpenaireQAEventsFromJson(InputStream inputStream) throws Exception { + return jsonMapper.readValue(inputStream, new TypeReference>() { + }); + } + + /** + * Store the given QAEvents. + * + * @param events the event to be stored + */ + private void storeOpenaireQAEvents(List events) { + for (QAEvent event : events) { + try { + storeOpenaireQAEvent(event); + } catch (RuntimeException e) { + handler.logWarning("An error occurs storing the event with id " + + event.getEventId() + ": " + getMessage(e)); + } + } + } + /** * Store the given QAEvent, skipping it if it is not supported. * @@ -177,6 +260,48 @@ private void storeOpenaireQAEvent(QAEvent event) { } + /** + * Download the events related to the given subscription from the OPENAIRE broker. + * + * @param subscriptionId the subscription id + * @return an input stream from which to read the events in json format + */ + private InputStream getEventsBySubscriptions(String subscriptionId) throws Exception { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + brokerClient.downloadEvents(openaireBrokerURL, subscriptionId, outputStream); + return new ByteArrayInputStream(outputStream.toByteArray()); + } + + /** + * Takes all the subscription related to the given email from the OPENAIRE + * broker. + */ + private List listEmailSubscriptions() { + try { + return brokerClient.listSubscriptions(openaireBrokerURL, email); + } catch (Exception ex) { + throw new IllegalArgumentException("An error occurs retriving the subscriptions " + + "from the OPENAIRE broker: " + getMessage(ex), ex); + } + } + + private URL getOpenaireBrokerUri() { + try { + return new URL(configurationService.getProperty("qaevents.openaire.broker-url", "http://api.openaire.eu/broker")); + } catch (MalformedURLException e) { + throw new IllegalStateException("The configured OPENAIRE broker URL is not valid.", e); + } + } + + /** + * Get the root exception message from the given exception. + */ + private String getMessage(Exception ex) { + String message = ExceptionUtils.getRootCauseMessage(ex); + // Remove the Exception name from the message + return isNotBlank(message) ? substringAfter(message, ":").trim() : ""; + } + private void assignCurrentUserInContext() throws SQLException { UUID uuid = getEpersonIdentifier(); if (uuid != null) { diff --git a/dspace-api/src/main/java/org/dspace/qaevent/script/OpenaireEventsImportScriptConfiguration.java b/dspace-api/src/main/java/org/dspace/qaevent/script/OpenaireEventsImportScriptConfiguration.java index 1bf5ea85d879..1a6f94f6a56d 100644 --- a/dspace-api/src/main/java/org/dspace/qaevent/script/OpenaireEventsImportScriptConfiguration.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/script/OpenaireEventsImportScriptConfiguration.java @@ -58,9 +58,15 @@ public Options getOptions() { if (options == null) { Options options = new Options(); - options.addOption("f", "file", true, "Import data from OpenAIRE quality assurance broker files"); + options.addOption("f", "file", true, "Import data from OpenAIRE quality assurance broker JSON file." + + " This parameter is mutually exclusive to the email parameter."); options.getOption("f").setType(InputStream.class); - options.getOption("f").setRequired(true); + options.getOption("f").setRequired(false); + + options.addOption("e", "email", true, "Email related to the subscriptions to import data from OpenAIRE " + + "broker. This parameter is mutually exclusive to the file parameter."); + options.getOption("e").setType(String.class); + options.getOption("e").setRequired(false); super.options = options; } diff --git a/dspace-api/src/main/java/org/dspace/qaevent/service/BrokerClientFactory.java b/dspace-api/src/main/java/org/dspace/qaevent/service/BrokerClientFactory.java new file mode 100644 index 000000000000..c17a7a3ff5ae --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/qaevent/service/BrokerClientFactory.java @@ -0,0 +1,31 @@ +/** + * 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.qaevent.service; + +import eu.dnetlib.broker.BrokerClient; +import org.dspace.utils.DSpace; + +/** + * Factory for the {@link BrokerClient}. + * + * @author Luca Giamminonni (luca.giamminonni at 4science.it) + * + */ +public interface BrokerClientFactory { + + /** + * Returns an instance of the {@link BrokerClient}. + * + * @return the client instance + */ + public BrokerClient getBrokerClient(); + + public static BrokerClientFactory getInstance() { + return new DSpace().getServiceManager().getServiceByName("brokerClientFactory", BrokerClientFactory.class); + } +} diff --git a/dspace-api/src/main/java/org/dspace/qaevent/service/impl/BrokerClientFactoryImpl.java b/dspace-api/src/main/java/org/dspace/qaevent/service/impl/BrokerClientFactoryImpl.java new file mode 100644 index 000000000000..8d3f2cdaac43 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/qaevent/service/impl/BrokerClientFactoryImpl.java @@ -0,0 +1,35 @@ +/** + * 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.qaevent.service.impl; + +import eu.dnetlib.broker.BrokerClient; +import org.dspace.qaevent.service.BrokerClientFactory; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Implementation of {@link BrokerClientFactory} that returns the instance of + * {@link BrokerClient} managed by the Spring context. + * + * @author Luca Giamminonni (luca.giamminonni at 4science.it) + * + */ +public class BrokerClientFactoryImpl implements BrokerClientFactory { + + @Autowired + private BrokerClient brokerClient; + + @Override + public BrokerClient getBrokerClient() { + return brokerClient; + } + + public void setBrokerClient(BrokerClient brokerClient) { + this.brokerClient = brokerClient; + } + +} diff --git a/dspace-api/src/test/java/org/dspace/matcher/QAEventMatcher.java b/dspace-api/src/test/java/org/dspace/matcher/QAEventMatcher.java index 1e53bff4f9dd..52f3704a74b7 100644 --- a/dspace-api/src/test/java/org/dspace/matcher/QAEventMatcher.java +++ b/dspace-api/src/test/java/org/dspace/matcher/QAEventMatcher.java @@ -63,6 +63,19 @@ private QAEventMatcher(Matcher eventIdMatcher, Matcher originalI this.trustMatcher = trustMatcher; } + /** + * Creates an instance of {@link QAEventMatcher} that matches an OPENAIRE + * QAEvent with PENDING status, with an event id, without a related item and + * with the given attributes. + * + * @param originalId the original id to match + * @param target the target to match + * @param title the title to match + * @param message the message to match + * @param topic the topic to match + * @param trust the trust to match + * @return the matcher istance + */ public static QAEventMatcher pendingOpenaireEventWith(String originalId, Item target, String title, String message, String topic, Double trust) { diff --git a/dspace-api/src/test/java/org/dspace/matcher/QASourceMatcher.java b/dspace-api/src/test/java/org/dspace/matcher/QASourceMatcher.java new file mode 100644 index 000000000000..fe3b7130b543 --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/matcher/QASourceMatcher.java @@ -0,0 +1,58 @@ +/** + * 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.matcher; + +import static org.hamcrest.Matchers.is; + +import org.dspace.qaevent.QASource; +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hamcrest.TypeSafeMatcher; + +/** + * Implementation of {@link org.hamcrest.Matcher} to match a QASource by all its + * attributes. + * + * @author Luca Giamminonni (luca.giamminonni at 4science.it) + * + */ +public class QASourceMatcher extends TypeSafeMatcher { + + private Matcher nameMatcher; + + private Matcher totalEventsMatcher; + + private QASourceMatcher(Matcher nameMatcher, Matcher totalEventsMatcher) { + this.nameMatcher = nameMatcher; + this.totalEventsMatcher = totalEventsMatcher; + } + + /** + * Creates an instance of {@link QASourceMatcher} that matches a QATopic with + * the given name and total events count. + * @param name the name to match + * @param totalEvents the total events count to match + * @return the matcher instance + */ + public static QASourceMatcher with(String name, long totalEvents) { + return new QASourceMatcher(is(name), is(totalEvents)); + } + + @Override + public boolean matchesSafely(QASource event) { + return nameMatcher.matches(event.getName()) && totalEventsMatcher.matches(event.getTotalEvents()); + } + + @Override + public void describeTo(Description description) { + description.appendText("a QA source with the following attributes:") + .appendText(" name ").appendDescriptionOf(nameMatcher) + .appendText(" and total events ").appendDescriptionOf(totalEventsMatcher); + } + +} diff --git a/dspace-api/src/test/java/org/dspace/matcher/QATopicMatcher.java b/dspace-api/src/test/java/org/dspace/matcher/QATopicMatcher.java new file mode 100644 index 000000000000..dd93972814a2 --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/matcher/QATopicMatcher.java @@ -0,0 +1,58 @@ +/** + * 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.matcher; + +import static org.hamcrest.Matchers.is; + +import org.dspace.qaevent.QATopic; +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hamcrest.TypeSafeMatcher; + +/** + * Implementation of {@link org.hamcrest.Matcher} to match a QATopic by all its + * attributes. + * + * @author Luca Giamminonni (luca.giamminonni at 4science.it) + * + */ +public class QATopicMatcher extends TypeSafeMatcher { + + private Matcher keyMatcher; + + private Matcher totalEventsMatcher; + + private QATopicMatcher(Matcher keyMatcher, Matcher totalEventsMatcher) { + this.keyMatcher = keyMatcher; + this.totalEventsMatcher = totalEventsMatcher; + } + + /** + * Creates an instance of {@link QATopicMatcher} that matches a QATopic with the + * given key and total events count. + * @param key the key to match + * @param totalEvents the total events count to match + * @return the matcher instance + */ + public static QATopicMatcher with(String key, long totalEvents) { + return new QATopicMatcher(is(key), is(totalEvents)); + } + + @Override + public boolean matchesSafely(QATopic event) { + return keyMatcher.matches(event.getKey()) && totalEventsMatcher.matches(event.getTotalEvents()); + } + + @Override + public void describeTo(Description description) { + description.appendText("a QA topic with the following attributes:") + .appendText(" key ").appendDescriptionOf(keyMatcher) + .appendText(" and total events ").appendDescriptionOf(totalEventsMatcher); + } + +} diff --git a/dspace-api/src/test/java/org/dspace/qaevent/script/OpenaireEventsImportIT.java b/dspace-api/src/test/java/org/dspace/qaevent/script/OpenaireEventsImportIT.java index 258ede4ef0ed..22da785d3146 100644 --- a/dspace-api/src/test/java/org/dspace/qaevent/script/OpenaireEventsImportIT.java +++ b/dspace-api/src/test/java/org/dspace/qaevent/script/OpenaireEventsImportIT.java @@ -7,7 +7,7 @@ */ package org.dspace.qaevent.script; -import static org.dspace.app.matcher.LambdaMatcher.matches; +import static java.util.List.of; import static org.dspace.content.QAEvent.OPENAIRE_SOURCE; import static org.dspace.matcher.QAEventMatcher.pendingOpenaireEventWith; import static org.hamcrest.MatcherAssert.assertThat; @@ -16,12 +16,25 @@ import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +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.io.File; +import java.io.FileInputStream; +import java.io.OutputStream; import java.net.URL; -import java.util.List; +import eu.dnetlib.broker.BrokerClient; +import org.apache.commons.io.IOUtils; import org.dspace.AbstractIntegrationTestWithDatabase; import org.dspace.app.launcher.ScriptLauncher; import org.dspace.app.scripts.handler.impl.TestDSpaceRunnableHandler; @@ -30,10 +43,13 @@ import org.dspace.builder.ItemBuilder; import org.dspace.content.Collection; import org.dspace.content.Item; -import org.dspace.qaevent.QASource; -import org.dspace.qaevent.QATopic; +import org.dspace.matcher.QASourceMatcher; +import org.dspace.matcher.QATopicMatcher; +import org.dspace.qaevent.service.BrokerClientFactory; import org.dspace.qaevent.service.QAEventService; +import org.dspace.qaevent.service.impl.BrokerClientFactoryImpl; import org.dspace.utils.DSpace; +import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -51,6 +67,10 @@ public class OpenaireEventsImportIT extends AbstractIntegrationTestWithDatabase private Collection collection; + private BrokerClient brokerClient = BrokerClientFactory.getInstance().getBrokerClient(); + + private BrokerClient mockBrokerClient = mock(BrokerClient.class); + @Before public void setup() { @@ -65,23 +85,62 @@ public void setup() { .build(); context.restoreAuthSystemState(); + + ((BrokerClientFactoryImpl) BrokerClientFactory.getInstance()).setBrokerClient(mockBrokerClient); + } + + @After + public void after() { + ((BrokerClientFactoryImpl) BrokerClientFactory.getInstance()).setBrokerClient(brokerClient); + } + + @Test + public void testWithoutParameters() throws Exception { + TestDSpaceRunnableHandler handler = new TestDSpaceRunnableHandler(); + + String[] args = new String[] { "import-openaire-events" }; + ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl), handler, kernelImpl); + + assertThat(handler.getErrorMessages(), empty()); + assertThat(handler.getWarningMessages(), empty()); + assertThat(handler.getInfoMessages(), empty()); + + Exception exception = handler.getException(); + assertThat(exception, instanceOf(IllegalArgumentException.class)); + assertThat(exception.getMessage(), is("One parameter between the location of the file and the email " + + "must be entered to proceed with the import.")); + + verifyNoInteractions(mockBrokerClient); + } + + @Test + public void testWithBothFileAndEmailParameters() throws Exception { + TestDSpaceRunnableHandler handler = new TestDSpaceRunnableHandler(); + + String[] args = new String[] { "import-openaire-events", "-f", getFileLocation("events.json"), + "-e", "test@user.com" }; + ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl), handler, kernelImpl); + + assertThat(handler.getErrorMessages(), empty()); + assertThat(handler.getWarningMessages(), empty()); + assertThat(handler.getInfoMessages(), empty()); + + Exception exception = handler.getException(); + assertThat(exception, instanceOf(IllegalArgumentException.class)); + assertThat(exception.getMessage(), is("Only one parameter between the location of the file and the email " + + "must be entered to proceed with the import.")); + + verifyNoInteractions(mockBrokerClient); } @Test @SuppressWarnings("unchecked") - public void testManyEventsImport() throws Exception { + public void testManyEventsImportFromFile() throws Exception { context.turnOffAuthorisationSystem(); - Item firstItem = ItemBuilder.createItem(context, collection) - .withTitle("Egypt, crossroad of translations and literary interweavings") - .withHandle("123456789/99998") - .build(); - - Item secondItem = ItemBuilder.createItem(context, collection) - .withTitle("Test item") - .withHandle("123456789/99999") - .build(); + Item firstItem = createItem("Test item", "123456789/99998"); + Item secondItem = createItem("Test item 2", "123456789/99999"); context.restoreAuthSystemState(); @@ -92,19 +151,15 @@ public void testManyEventsImport() throws Exception { assertThat(handler.getErrorMessages(), empty()); assertThat(handler.getWarningMessages(), empty()); - assertThat(handler.getInfoMessages(), contains("Found 2 events to store")); - - List sources = qaEventService.findAllSources(0, 20); - assertThat(sources, hasSize(1)); + assertThat(handler.getInfoMessages(), contains( + "Trying to read the QA events from the provided file", + "Found 2 events in the given file")); - assertThat(sources.get(0).getName(), is(OPENAIRE_SOURCE)); - assertThat(sources.get(0).getTotalEvents(), is(2L)); + assertThat(qaEventService.findAllSources(0, 20), contains(QASourceMatcher.with(OPENAIRE_SOURCE, 2L))); - List topics = qaEventService.findAllTopics(0, 20); - assertThat(topics, hasSize(2)); - assertThat(topics, containsInAnyOrder( - matches(topic -> topic.getKey().equals("ENRICH/MORE/PROJECT") && topic.getTotalEvents() == 1L), - matches(topic -> topic.getKey().equals("ENRICH/MISSING/ABSTRACT") && topic.getTotalEvents() == 1L))); + assertThat(qaEventService.findAllTopics(0, 20), containsInAnyOrder( + QATopicMatcher.with("ENRICH/MORE/PROJECT", 1L), + QATopicMatcher.with("ENRICH/MISSING/ABSTRACT", 1L))); String projectMessage = "{\"projects[0].acronym\":\"PAThs\",\"projects[0].code\":\"687567\"," + "\"projects[0].funder\":\"EC\",\"projects[0].fundingProgram\":\"H2020\"," @@ -123,17 +178,16 @@ public void testManyEventsImport() throws Exception { pendingOpenaireEventWith("oai:www.openstarts.units.it:123456789/99999", secondItem, "Test Publication", abstractMessage, "ENRICH/MISSING/ABSTRACT", 1.00d))); + verifyNoInteractions(mockBrokerClient); + } @Test - public void testManyEventsImportWithUnknownHandle() throws Exception { + public void testManyEventsImportFromFileWithUnknownHandle() throws Exception { context.turnOffAuthorisationSystem(); - Item item = ItemBuilder.createItem(context, collection) - .withTitle("Test item") - .withHandle("123456789/99999") - .build(); + Item item = createItem("Test item", "123456789/99999"); context.restoreAuthSystemState(); @@ -144,21 +198,16 @@ public void testManyEventsImportWithUnknownHandle() throws Exception { assertThat(handler.getErrorMessages(), empty()); assertThat(handler.getWarningMessages(), - contains("IllegalArgumentException: Skipped event b4e09c71312cd7c397969f56c900823f" + - " related to the oai record oai:www.openstarts.units.it:123456789/99998 as the record was not found")); - assertThat(handler.getInfoMessages(), contains("Found 2 events to store")); + contains("An error occurs storing the event with id b4e09c71312cd7c397969f56c900823f: " + + "Skipped event b4e09c71312cd7c397969f56c900823f related to the oai record " + + "oai:www.openstarts.units.it:123456789/99998 as the record was not found")); + assertThat(handler.getInfoMessages(), contains( + "Trying to read the QA events from the provided file", + "Found 2 events in the given file")); - List sources = qaEventService.findAllSources(0, 20); - assertThat(sources, hasSize(1)); + assertThat(qaEventService.findAllSources(0, 20), contains(QASourceMatcher.with(OPENAIRE_SOURCE, 1L))); - assertThat(sources.get(0).getName(), is(OPENAIRE_SOURCE)); - assertThat(sources.get(0).getTotalEvents(), is(1L)); - - List topics = qaEventService.findAllTopics(0, 20); - assertThat(topics, hasSize(1)); - QATopic topic = topics.get(0); - assertThat(topic.getKey(), is("ENRICH/MISSING/ABSTRACT")); - assertThat(topic.getTotalEvents(), is(1L)); + assertThat(qaEventService.findAllTopics(0, 20), contains(QATopicMatcher.with("ENRICH/MISSING/ABSTRACT", 1L))); String abstractMessage = "{\"abstracts[0]\":\"Missing Abstract\"}"; @@ -166,22 +215,17 @@ public void testManyEventsImportWithUnknownHandle() throws Exception { pendingOpenaireEventWith("oai:www.openstarts.units.it:123456789/99999", item, "Test Publication", abstractMessage, "ENRICH/MISSING/ABSTRACT", 1.00d))); + verifyNoInteractions(mockBrokerClient); + } @Test - public void testManyEventsImportWithUnknownTopic() throws Exception { + public void testManyEventsImportFromFileWithUnknownTopic() throws Exception { context.turnOffAuthorisationSystem(); - ItemBuilder.createItem(context, collection) - .withTitle("Egypt, crossroad of translations and literary interweavings") - .withHandle("123456789/99998") - .build(); - - Item secondItem = ItemBuilder.createItem(context, collection) - .withTitle("Test item") - .withHandle("123456789/99999") - .build(); + createItem("Test item", "123456789/99999"); + Item secondItem = createItem("Test item 2", "123456789/999991"); context.restoreAuthSystemState(); @@ -193,48 +237,226 @@ public void testManyEventsImportWithUnknownTopic() throws Exception { assertThat(handler.getErrorMessages(), empty()); assertThat(handler.getWarningMessages(), contains("Event for topic ENRICH/MORE/UNKNOWN is not allowed in the qaevents.cfg")); - assertThat(handler.getInfoMessages(), contains("Found 2 events to store")); + assertThat(handler.getInfoMessages(), contains( + "Trying to read the QA events from the provided file", + "Found 2 events in the given file")); - List sources = qaEventService.findAllSources(0, 20); - assertThat(sources, hasSize(1)); + assertThat(qaEventService.findAllSources(0, 20), contains(QASourceMatcher.with(OPENAIRE_SOURCE, 1L))); - assertThat(sources.get(0).getName(), is(OPENAIRE_SOURCE)); - assertThat(sources.get(0).getTotalEvents(), is(1L)); - - List topics = qaEventService.findAllTopics(0, 20); - assertThat(topics, hasSize(1)); - QATopic topic = topics.get(0); - assertThat(topic.getKey(), is("ENRICH/MISSING/ABSTRACT")); - assertThat(topic.getTotalEvents(), is(1L)); + assertThat(qaEventService.findAllTopics(0, 20), contains(QATopicMatcher.with("ENRICH/MISSING/ABSTRACT", 1L))); String abstractMessage = "{\"abstracts[0]\":\"Missing Abstract\"}"; assertThat(qaEventService.findEventsByTopic("ENRICH/MISSING/ABSTRACT"), contains( - pendingOpenaireEventWith("oai:www.openstarts.units.it:123456789/99999", secondItem, "Test Publication", + pendingOpenaireEventWith("oai:www.openstarts.units.it:123456789/999991", secondItem, "Test Publication 2", abstractMessage, "ENRICH/MISSING/ABSTRACT", 1.00d))); + verifyNoInteractions(mockBrokerClient); + } @Test - public void testWithoutEvents() throws Exception { + public void testImportFromFileWithoutEvents() throws Exception { TestDSpaceRunnableHandler handler = new TestDSpaceRunnableHandler(); - String[] args = new String[] { "import-openaire-events", "-f", getFileLocation("empty-events.json") }; + String[] args = new String[] { "import-openaire-events", "-f", getFileLocation("empty-file.json") }; ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl), handler, kernelImpl); assertThat(handler.getErrorMessages(), - contains(containsString("A not recoverable error occurs during OPENAIRE events import."))); + contains(containsString("A not recoverable error occurs during OPENAIRE events import"))); assertThat(handler.getWarningMessages(),empty()); - assertThat(handler.getInfoMessages(), empty()); + assertThat(handler.getInfoMessages(), contains("Trying to read the QA events from the provided file")); + + assertThat(qaEventService.findAllSources(0, 20), contains(QASourceMatcher.with(OPENAIRE_SOURCE, 0L))); + + assertThat(qaEventService.findAllTopics(0, 20), empty()); + + verifyNoInteractions(mockBrokerClient); + } + + @Test + @SuppressWarnings("unchecked") + public void testImportFromOpenaireBroker() throws Exception { + + context.turnOffAuthorisationSystem(); + + Item firstItem = createItem("Test item", "123456789/99998"); + Item secondItem = createItem("Test item 2", "123456789/99999"); + Item thirdItem = createItem("Test item 3", "123456789/999991"); + + context.restoreAuthSystemState(); + + URL openaireURL = new URL("http://api.openaire.eu/broker"); + + when(mockBrokerClient.listSubscriptions(openaireURL, "user@test.com")).thenReturn(of("sub1", "sub2", "sub3")); + + doAnswer(i -> writeToOutputStream(i.getArgument(2, OutputStream.class), "events.json")) + .when(mockBrokerClient).downloadEvents(eq(openaireURL), eq("sub1"), any()); + + doAnswer(i -> writeToOutputStream(i.getArgument(2, OutputStream.class), "empty-events-list.json")) + .when(mockBrokerClient).downloadEvents(eq(openaireURL), eq("sub2"), any()); + + doAnswer(i -> writeToOutputStream(i.getArgument(2, OutputStream.class), "unknown-topic-events.json")) + .when(mockBrokerClient).downloadEvents(eq(openaireURL), eq("sub3"), any()); + + TestDSpaceRunnableHandler handler = new TestDSpaceRunnableHandler(); + + String[] args = new String[] { "import-openaire-events", "-e", "user@test.com" }; + ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl), handler, kernelImpl); + + assertThat(handler.getErrorMessages(), empty()); + assertThat(handler.getWarningMessages(), + contains("Event for topic ENRICH/MORE/UNKNOWN is not allowed in the qaevents.cfg")); + assertThat(handler.getInfoMessages(), contains( + "Trying to read the QA events from the OPENAIRE broker", + "Found 3 subscriptions related to the given email", + "Found 2 events from the subscription sub1", + "Found 0 events from the subscription sub2", + "Found 2 events from the subscription sub3")); - List sources = qaEventService.findAllSources(0, 20); - assertThat(sources, hasSize(1)); + assertThat(qaEventService.findAllSources(0, 20), contains(QASourceMatcher.with(OPENAIRE_SOURCE, 3L))); - assertThat(sources.get(0).getName(), is(OPENAIRE_SOURCE)); - assertThat(sources.get(0).getTotalEvents(), is(0L)); + assertThat(qaEventService.findAllTopics(0, 20), containsInAnyOrder( + QATopicMatcher.with("ENRICH/MORE/PROJECT", 1L), + QATopicMatcher.with("ENRICH/MISSING/ABSTRACT", 2L))); + + String projectMessage = "{\"projects[0].acronym\":\"PAThs\",\"projects[0].code\":\"687567\"," + + "\"projects[0].funder\":\"EC\",\"projects[0].fundingProgram\":\"H2020\"," + + "\"projects[0].jurisdiction\":\"EU\"," + + "\"projects[0].openaireId\":\"40|corda__h2020::6e32f5eb912688f2424c68b851483ea4\"," + + "\"projects[0].title\":\"Tracking Papyrus and Parchment Paths\"}"; + + assertThat(qaEventService.findEventsByTopic("ENRICH/MORE/PROJECT"), contains( + pendingOpenaireEventWith("oai:www.openstarts.units.it:123456789/99998", firstItem, + "Egypt, crossroad of translations and literary interweavings", projectMessage, + "ENRICH/MORE/PROJECT", 1.00d))); + + String abstractMessage = "{\"abstracts[0]\":\"Missing Abstract\"}"; + + assertThat(qaEventService.findEventsByTopic("ENRICH/MISSING/ABSTRACT"), containsInAnyOrder( + pendingOpenaireEventWith("oai:www.openstarts.units.it:123456789/99999", secondItem, "Test Publication", + abstractMessage, "ENRICH/MISSING/ABSTRACT", 1.00d), + pendingOpenaireEventWith("oai:www.openstarts.units.it:123456789/999991", thirdItem, "Test Publication 2", + abstractMessage, "ENRICH/MISSING/ABSTRACT", 1.00d))); + + verify(mockBrokerClient).listSubscriptions(openaireURL, "user@test.com"); + verify(mockBrokerClient).downloadEvents(eq(openaireURL), eq("sub1"), any()); + verify(mockBrokerClient).downloadEvents(eq(openaireURL), eq("sub2"), any()); + verify(mockBrokerClient).downloadEvents(eq(openaireURL), eq("sub3"), any()); + + verifyNoMoreInteractions(mockBrokerClient); + } + + @Test + public void testImportFromOpenaireBrokerWithErrorDuringListSubscription() throws Exception { + + URL openaireURL = new URL("http://api.openaire.eu/broker"); + + when(mockBrokerClient.listSubscriptions(openaireURL, "user@test.com")) + .thenThrow(new RuntimeException("Connection refused")); + + TestDSpaceRunnableHandler handler = new TestDSpaceRunnableHandler(); + + String[] args = new String[] { "import-openaire-events", "-e", "user@test.com" }; + ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl), handler, kernelImpl); + + assertThat(handler.getErrorMessages(), + contains("A not recoverable error occurs during OPENAIRE events import: Connection refused")); + assertThat(handler.getWarningMessages(), empty()); + assertThat(handler.getInfoMessages(), contains("Trying to read the QA events from the OPENAIRE broker")); + + assertThat(qaEventService.findAllSources(0, 20), contains(QASourceMatcher.with(OPENAIRE_SOURCE, 0L))); assertThat(qaEventService.findAllTopics(0, 20), empty()); + + verify(mockBrokerClient).listSubscriptions(openaireURL, "user@test.com"); + + verifyNoMoreInteractions(mockBrokerClient); + + } + + @Test + @SuppressWarnings("unchecked") + public void testImportFromOpenaireBrokerWithErrorDuringEventsDownload() throws Exception { + + context.turnOffAuthorisationSystem(); + + createItem("Test item", "123456789/99998"); + createItem("Test item 2", "123456789/99999"); + createItem("Test item 3", "123456789/999991"); + + context.restoreAuthSystemState(); + + URL openaireURL = new URL("http://api.openaire.eu/broker"); + + when(mockBrokerClient.listSubscriptions(openaireURL, "user@test.com")).thenReturn(of("sub1", "sub2", "sub3")); + + doAnswer(i -> writeToOutputStream(i.getArgument(2, OutputStream.class), "events.json")) + .when(mockBrokerClient).downloadEvents(eq(openaireURL), eq("sub1"), any()); + + doThrow(new RuntimeException("Invalid subscription id")) + .when(mockBrokerClient).downloadEvents(eq(openaireURL), eq("sub2"), any()); + + doAnswer(i -> writeToOutputStream(i.getArgument(2, OutputStream.class), "unknown-topic-events.json")) + .when(mockBrokerClient).downloadEvents(eq(openaireURL), eq("sub3"), any()); + + TestDSpaceRunnableHandler handler = new TestDSpaceRunnableHandler(); + + String[] args = new String[] { "import-openaire-events", "-e", "user@test.com" }; + ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl), handler, kernelImpl); + + assertThat(handler.getErrorMessages(), contains("An error occurs downloading the events " + + "related to the subscription sub2: Invalid subscription id")); + assertThat(handler.getWarningMessages(), + contains("Event for topic ENRICH/MORE/UNKNOWN is not allowed in the qaevents.cfg")); + assertThat(handler.getInfoMessages(), contains( + "Trying to read the QA events from the OPENAIRE broker", + "Found 3 subscriptions related to the given email", + "Found 2 events from the subscription sub1", + "Found 0 events from the subscription sub2", + "Found 2 events from the subscription sub3")); + + assertThat(qaEventService.findAllSources(0, 20), contains(QASourceMatcher.with(OPENAIRE_SOURCE, 3L))); + + assertThat(qaEventService.findAllTopics(0, 20), containsInAnyOrder( + QATopicMatcher.with("ENRICH/MORE/PROJECT", 1L), + QATopicMatcher.with("ENRICH/MISSING/ABSTRACT", 2L))); + + assertThat(qaEventService.findEventsByTopic("ENRICH/MORE/PROJECT"), hasSize(1)); + assertThat(qaEventService.findEventsByTopic("ENRICH/MISSING/ABSTRACT"), hasSize(2)); + + verify(mockBrokerClient).listSubscriptions(openaireURL, "user@test.com"); + verify(mockBrokerClient).downloadEvents(eq(openaireURL), eq("sub1"), any()); + verify(mockBrokerClient).downloadEvents(eq(openaireURL), eq("sub2"), any()); + verify(mockBrokerClient).downloadEvents(eq(openaireURL), eq("sub3"), any()); + + verifyNoMoreInteractions(mockBrokerClient); + + } + + private Item createItem(String title, String handle) { + return ItemBuilder.createItem(context, collection) + .withTitle(title) + .withHandle(handle) + .build(); + } + + private Void writeToOutputStream(OutputStream outputStream, String fileName) { + try { + byte[] fileContent = getFileContent(fileName); + IOUtils.write(fileContent, outputStream); + return null; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private byte[] getFileContent(String fileName) throws Exception { + String fileLocation = getFileLocation(fileName); + try (FileInputStream fis = new FileInputStream(new File(fileLocation))) { + return IOUtils.toByteArray(fis); + } } private String getFileLocation(String fileName) throws Exception { diff --git a/dspace-api/src/test/resources/org/dspace/app/openaire-events/empty-events-list.json b/dspace-api/src/test/resources/org/dspace/app/openaire-events/empty-events-list.json new file mode 100644 index 000000000000..0637a088a01e --- /dev/null +++ b/dspace-api/src/test/resources/org/dspace/app/openaire-events/empty-events-list.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/dspace-api/src/test/resources/org/dspace/app/openaire-events/empty-events.json b/dspace-api/src/test/resources/org/dspace/app/openaire-events/empty-file.json similarity index 100% rename from dspace-api/src/test/resources/org/dspace/app/openaire-events/empty-events.json rename to dspace-api/src/test/resources/org/dspace/app/openaire-events/empty-file.json diff --git a/dspace-api/src/test/resources/org/dspace/app/openaire-events/unknown-topic-events.json b/dspace-api/src/test/resources/org/dspace/app/openaire-events/unknown-topic-events.json index d281db450f0c..3caa72cf35b3 100644 --- a/dspace-api/src/test/resources/org/dspace/app/openaire-events/unknown-topic-events.json +++ b/dspace-api/src/test/resources/org/dspace/app/openaire-events/unknown-topic-events.json @@ -8,8 +8,8 @@ }, { - "originalId": "oai:www.openstarts.units.it:123456789/99999", - "title": "Test Publication", + "originalId": "oai:www.openstarts.units.it:123456789/999991", + "title": "Test Publication 2", "topic": "ENRICH/MISSING/ABSTRACT", "trust": 1.0, "message": { diff --git a/dspace/config/modules/qaevents.cfg b/dspace/config/modules/qaevents.cfg index d30c5181f514..d9a6fba9626e 100644 --- a/dspace/config/modules/qaevents.cfg +++ b/dspace/config/modules/qaevents.cfg @@ -27,4 +27,7 @@ qaevents.openaire.pid-href-prefix.urn = qaevents.openaire.pid-href-prefix.doi = https://doi.org/ qaevents.openaire.pid-href-prefix.pmc = https://www.ncbi.nlm.nih.gov/pmc/articles/ qaevents.openaire.pid-href-prefix.pmid = https://pubmed.ncbi.nlm.nih.gov/ -qaevents.openaire.pid-href-prefix.ncid = https://ci.nii.ac.jp/ncid/ \ No newline at end of file +qaevents.openaire.pid-href-prefix.ncid = https://ci.nii.ac.jp/ncid/ + +# The URI used by the OPENAIRE broker client to import QA events +qaevents.openaire.broker-url = http://api.openaire.eu/broker \ No newline at end of file diff --git a/dspace/config/spring/api/qaevents.xml b/dspace/config/spring/api/qaevents.xml index c292cb4beb27..25bb282672fa 100644 --- a/dspace/config/spring/api/qaevents.xml +++ b/dspace/config/spring/api/qaevents.xml @@ -10,6 +10,10 @@ + + + + From 3a9d68cf8cb70408c9c531e251dfff2485326e91 Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Thu, 14 Jul 2022 09:25:59 +0200 Subject: [PATCH 022/510] [CST-5247] Upgraded OPENAIRE broker client --- 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 dc67ae8d7130..d09fcf004d99 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -881,7 +881,7 @@ eu.openaire broker-client - 1.1.1 + 1.1.2 From 85f708a1e63b4e85e7235fcaa563a1e980193755 Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Thu, 1 Sep 2022 10:49:30 +0200 Subject: [PATCH 023/510] [CST-5247] Updated qaevent core configuration --- dspace/solr/qaevent/conf/schema.xml | 227 +++++++++++------------- dspace/solr/qaevent/conf/solrconfig.xml | 6 + 2 files changed, 111 insertions(+), 122 deletions(-) diff --git a/dspace/solr/qaevent/conf/schema.xml b/dspace/solr/qaevent/conf/schema.xml index 68eb79afd0d4..d523e99c9fc1 100644 --- a/dspace/solr/qaevent/conf/schema.xml +++ b/dspace/solr/qaevent/conf/schema.xml @@ -16,52 +16,50 @@ limitations under the License. --> - + + - + - - - - - - - - - - - - + + + + + + + + + + - - - - - - - - - - - - - - - + + + + + + @@ -69,8 +67,14 @@ ignoreCase="true" words="stopwords.txt" /> - - + + @@ -81,55 +85,54 @@ ignoreCase="true" words="stopwords.txt" /> - - + + - + + - + - + - - - - - - - - + words="stopwords.txt" /> + - - - - - - - - - - @@ -137,62 +140,29 @@ ignoreCase="true" words="stopwords.txt" /> - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -200,6 +170,7 @@ + @@ -223,28 +194,30 @@ - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + @@ -252,15 +225,25 @@ + + + + + + + + + + diff --git a/dspace/solr/qaevent/conf/solrconfig.xml b/dspace/solr/qaevent/conf/solrconfig.xml index 76f17a3ef308..2a5f1ef4e9fb 100644 --- a/dspace/solr/qaevent/conf/solrconfig.xml +++ b/dspace/solr/qaevent/conf/solrconfig.xml @@ -24,6 +24,12 @@ --> 8.8.1 + + + + ${solr.data.dir:} From 51d8874a8fdf9488615aab7d80e7597f5b0d2341 Mon Sep 17 00:00:00 2001 From: jensroets Date: Thu, 8 Sep 2022 16:50:02 +0200 Subject: [PATCH 024/510] 94299 Multiple Bitstream deletion endpoint --- .../app/rest/RestResourceController.java | 33 + .../repository/BitstreamRestRepository.java | 44 + .../rest/repository/DSpaceRestRepository.java | 18 + .../app/rest/BitstreamRestRepositoryIT.java | 955 ++++++++++++++++++ 4 files changed, 1050 insertions(+) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/RestResourceController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/RestResourceController.java index b82b4830753c..24468660f016 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/RestResourceController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/RestResourceController.java @@ -7,6 +7,7 @@ */ package org.dspace.app.rest; +import static org.dspace.app.rest.utils.ContextUtil.obtainContext; import static org.dspace.app.rest.utils.RegexUtils.REGEX_REQUESTMAPPING_IDENTIFIER_AS_DIGIT; import static org.dspace.app.rest.utils.RegexUtils.REGEX_REQUESTMAPPING_IDENTIFIER_AS_HEX32; import static org.dspace.app.rest.utils.RegexUtils.REGEX_REQUESTMAPPING_IDENTIFIER_AS_STRING_VERSION_STRONG; @@ -55,6 +56,8 @@ import org.dspace.app.rest.utils.RestRepositoryUtils; import org.dspace.app.rest.utils.Utils; import org.dspace.authorize.AuthorizeException; +import org.dspace.content.DSpaceObject; +import org.dspace.core.Context; import org.dspace.util.UUIDUtils; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; @@ -1050,6 +1053,13 @@ public ResponseEntity> delete(HttpServletRequest request, return deleteInternal(apiCategory, model, uuid); } + @RequestMapping(method = RequestMethod.DELETE, consumes = {"text/uri-list"}) + public ResponseEntity> delete(HttpServletRequest request, @PathVariable String apiCategory, + @PathVariable String model) + throws HttpRequestMethodNotSupportedException { + return deleteUriListInternal(request, apiCategory, model); + } + /** * Internal method to delete resource. * @@ -1067,6 +1077,29 @@ private ResponseEntity> deleteI return ControllerUtils.toEmptyResponse(HttpStatus.NO_CONTENT); } + public ResponseEntity> deleteUriListInternal( + HttpServletRequest request, + String apiCategory, + String model) + throws HttpRequestMethodNotSupportedException { + checkModelPluralForm(apiCategory, model); + DSpaceRestRepository repository = utils.getResourceRepository(apiCategory, model); + Context context = obtainContext(request); + List dsoStringList = utils.getStringListFromRequest(request); + List dsoList = utils.constructDSpaceObjectList(context, dsoStringList); + if (dsoStringList.size() != dsoList.size()) { + throw new ResourceNotFoundException("One or more bitstreams could not be found."); + } + try { + repository.delete(dsoList); + } catch (ClassCastException e) { + log.error("Something went wrong whilst creating the object for apiCategory: " + apiCategory + + " and model: " + model, e); + return ControllerUtils.toEmptyResponse(HttpStatus.INTERNAL_SERVER_ERROR); + } + return ControllerUtils.toEmptyResponse(HttpStatus.NO_CONTENT); + } + /** * Execute a PUT request for an entity with id of type UUID; * diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BitstreamRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BitstreamRestRepository.java index ae3cf91d4c40..f599d993be43 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BitstreamRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BitstreamRestRepository.java @@ -10,6 +10,8 @@ import java.io.IOException; import java.io.InputStream; import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.UUID; @@ -147,6 +149,48 @@ protected void delete(Context context, UUID id) throws AuthorizeException { } } + @Override + protected void deleteList(Context context, List dsoList) + throws SQLException, AuthorizeException { + // check if list is empty + if (dsoList.isEmpty()) { + throw new ResourceNotFoundException("No bitstreams given."); + } + // check if every DSO is a Bitstream + if (dsoList.stream().anyMatch(dso -> !(dso instanceof Bitstream))) { + throw new UnprocessableEntityException("Not all given items are bitstreams."); + } + // check that they're all part of the same Item + List items = new ArrayList<>(); + for (DSpaceObject dso : dsoList) { + Bitstream bit = bs.find(context, dso.getID()); + DSpaceObject bitstreamParent = bs.getParentObject(context, bit); + if (bit == null) { + throw new ResourceNotFoundException("The bitstream with uuid " + dso.getID() + " could not be found"); + } + // we have to check if the bitstream has already been deleted + if (bit.isDeleted()) { + throw new UnprocessableEntityException("The bitstream with uuid " + bit.getID() + + " was already deleted"); + } else { + items.add(bitstreamParent); + } + } + if (items.stream().distinct().count() > 1) { + throw new UnprocessableEntityException("Not all given items are part of the same Item."); + } + // delete all Bitstreams + Iterator iterator = dsoList.iterator(); + while (iterator.hasNext()) { + Bitstream bit = (Bitstream) iterator.next(); + try { + bs.delete(context, bit); + } catch (SQLException | IOException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + } + /** * Find the bitstream for the provided handle and sequence or filename. * When a bitstream can be found with the sequence ID it will be returned if the user has "METADATA_READ" access. diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/DSpaceRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/DSpaceRestRepository.java index 01f127eca5ac..219b7c4123b2 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/DSpaceRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/DSpaceRestRepository.java @@ -26,6 +26,7 @@ import org.dspace.app.rest.model.RestAddressableModel; import org.dspace.app.rest.model.patch.Patch; import org.dspace.authorize.AuthorizeException; +import org.dspace.content.DSpaceObject; import org.dspace.content.service.MetadataFieldService; import org.dspace.core.Context; import org.springframework.beans.factory.BeanNameAware; @@ -256,6 +257,23 @@ public void deleteAll() { } + public void delete(List dsoList) { + Context context = obtainContext(); + try { + getThisRepository().deleteList(context, dsoList); + context.commit(); + } catch (AuthorizeException e) { + throw new RESTAuthorizationException(e); + } catch (SQLException ex) { + throw new RuntimeException(ex.getMessage(), ex); + } + } + + protected void deleteList(Context context, List list) + throws AuthorizeException, SQLException, RepositoryMethodNotImplementedException { + throw new RepositoryMethodNotImplementedException("No implementation found; Method not allowed!", ""); + } + @Override /** * This method cannot be implemented we required all the find method to be paginated diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java index f9c1e469fcfe..391d9e419330 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java @@ -13,6 +13,7 @@ import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; +import static org.springframework.data.rest.webmvc.RestMediaTypes.TEXT_URI_LIST; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; @@ -1201,6 +1202,960 @@ public void deleteDeleted() throws Exception { .andExpect(status().isNotFound()); } + @Test + public void deleteListOneBitstream() throws Exception { + + //We turn off the authorization system in order to create the structure as defined below + context.turnOffAuthorisationSystem(); + + //** GIVEN ** + //1. A community-collection structure with one parent community with sub-community and one collection. + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); + + //2. One public items that is readable by Anonymous + Item publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Test") + .withIssueDate("2010-10-17") + .withAuthor("Smith, Donald") + .withSubject("ExtraEntry") + .build(); + + String bitstreamContent = "ThisIsSomeDummyText"; + //Add a bitstream to an item + Bitstream bitstream = null; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + bitstream = BitstreamBuilder. + createBitstream(context, publicItem1, is) + .withName("Bitstream") + .withDescription("Description") + .withMimeType("text/plain") + .build(); + } + + context.restoreAuthSystemState(); + + String token = getAuthToken(admin.getEmail(), password); + + // Delete + getClient(token).perform(delete("/api/core/bitstreams") + .contentType(TEXT_URI_LIST) + .content("http://localhost:8080/server/api/core/bitstreams/" + bitstream.getID())) + .andExpect(status().is(204)); + + // Verify 404 after delete + getClient(token).perform(get("/api/core/bitstreams/" + bitstream.getID())) + .andExpect(status().isNotFound()); + } + + @Test + public void deleteListOneOfMultipleBitstreams() throws Exception { + + //We turn off the authorization system in order to create the structure as defined below + context.turnOffAuthorisationSystem(); + + //** GIVEN ** + //1. A community-collection structure with one parent community with sub-community and one collection. + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); + + //2. One public items that is readable by Anonymous + Item publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Test") + .withIssueDate("2010-10-17") + .withAuthor("Smith, Donald") + .withSubject("ExtraEntry") + .build(); + + // Add 3 bitstreams to the item + String bitstreamContent1 = "ThisIsSomeDummyText1"; + Bitstream bitstream1 = null; + try (InputStream is = IOUtils.toInputStream(bitstreamContent1, CharEncoding.UTF_8)) { + bitstream1 = BitstreamBuilder. + createBitstream(context, publicItem1, is) + .withName("Bitstream1") + .withDescription("Description1") + .withMimeType("text/plain") + .build(); + } + + String bitstreamContent2 = "ThisIsSomeDummyText2"; + Bitstream bitstream2 = null; + try (InputStream is = IOUtils.toInputStream(bitstreamContent2, CharEncoding.UTF_8)) { + bitstream2 = BitstreamBuilder. + createBitstream(context, publicItem1, is) + .withName("Bitstream2") + .withDescription("Description2") + .withMimeType("text/plain") + .build(); + } + + String bitstreamContent3 = "ThisIsSomeDummyText3"; + Bitstream bitstream3 = null; + try (InputStream is = IOUtils.toInputStream(bitstreamContent3, CharEncoding.UTF_8)) { + bitstream3 = BitstreamBuilder. + createBitstream(context, publicItem1, is) + .withName("Bitstream3") + .withDescription("Description3") + .withMimeType("text/plain") + .build(); + } + + context.restoreAuthSystemState(); + + String token = getAuthToken(admin.getEmail(), password); + + // Delete bitstream1 + getClient(token).perform(delete("/api/core/bitstreams") + .contentType(TEXT_URI_LIST) + .content("http://localhost:8080/server/api/core/bitstreams/" + bitstream1.getID())) + .andExpect(status().is(204)); + + // Verify 404 after delete for bitstream1 + getClient(token).perform(get("/api/core/bitstreams/" + bitstream1.getID())) + .andExpect(status().isNotFound()); + + // check that bitstream2 still exists + getClient().perform(get("/api/core/bitstreams/" + bitstream2.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", HalMatcher.matchNoEmbeds())); + + // check that bitstream3 still exists + getClient().perform(get("/api/core/bitstreams/" + bitstream3.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", HalMatcher.matchNoEmbeds())) + ; + } + + @Test + public void deleteListAllBitstreams() throws Exception { + + //We turn off the authorization system in order to create the structure as defined below + context.turnOffAuthorisationSystem(); + + //** GIVEN ** + //1. A community-collection structure with one parent community with sub-community and one collection. + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); + + //2. One public items that is readable by Anonymous + Item publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Test") + .withIssueDate("2010-10-17") + .withAuthor("Smith, Donald") + .withSubject("ExtraEntry") + .build(); + + // Add 3 bitstreams to the item + String bitstreamContent1 = "ThisIsSomeDummyText1"; + Bitstream bitstream1 = null; + try (InputStream is = IOUtils.toInputStream(bitstreamContent1, CharEncoding.UTF_8)) { + bitstream1 = BitstreamBuilder. + createBitstream(context, publicItem1, is) + .withName("Bitstream1") + .withDescription("Description1") + .withMimeType("text/plain") + .build(); + } + + String bitstreamContent2 = "ThisIsSomeDummyText2"; + Bitstream bitstream2 = null; + try (InputStream is = IOUtils.toInputStream(bitstreamContent2, CharEncoding.UTF_8)) { + bitstream2 = BitstreamBuilder. + createBitstream(context, publicItem1, is) + .withName("Bitstream2") + .withDescription("Description2") + .withMimeType("text/plain") + .build(); + } + + String bitstreamContent3 = "ThisIsSomeDummyText3"; + Bitstream bitstream3 = null; + try (InputStream is = IOUtils.toInputStream(bitstreamContent3, CharEncoding.UTF_8)) { + bitstream3 = BitstreamBuilder. + createBitstream(context, publicItem1, is) + .withName("Bitstream3") + .withDescription("Description3") + .withMimeType("text/plain") + .build(); + } + + context.restoreAuthSystemState(); + + String token = getAuthToken(admin.getEmail(), password); + + // Delete all bitstreams + getClient(token).perform(delete("/api/core/bitstreams") + .contentType(TEXT_URI_LIST) + .content("http://localhost:8080/server/api/core/bitstreams/" + bitstream1.getID() + + " \n http://localhost:8080/server/api/core/bitstreams/" + bitstream2.getID() + + " \n http://localhost:8080/server/api/core/bitstreams/" + bitstream3.getID())) + .andExpect(status().is(204)); + + // Verify 404 after delete for bitstream1 + getClient(token).perform(get("/api/core/bitstreams/" + bitstream1.getID())) + .andExpect(status().isNotFound()); + + // Verify 404 after delete for bitstream2 + getClient(token).perform(get("/api/core/bitstreams/" + bitstream2.getID())) + .andExpect(status().isNotFound()); + + // Verify 404 after delete for bitstream3 + getClient(token).perform(get("/api/core/bitstreams/" + bitstream3.getID())) + .andExpect(status().isNotFound()); + } + + @Test + public void deleteListForbidden() throws Exception { + + //We turn off the authorization system in order to create the structure as defined below + context.turnOffAuthorisationSystem(); + + //** GIVEN ** + //1. A community-collection structure with one parent community with sub-community and one collection. + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); + + //2. One public items that is readable by Anonymous + Item publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Test") + .withIssueDate("2010-10-17") + .withAuthor("Smith, Donald") + .withSubject("ExtraEntry") + .build(); + + // Add 3 bitstreams to the item + String bitstreamContent1 = "ThisIsSomeDummyText1"; + Bitstream bitstream1 = null; + try (InputStream is = IOUtils.toInputStream(bitstreamContent1, CharEncoding.UTF_8)) { + bitstream1 = BitstreamBuilder. + createBitstream(context, publicItem1, is) + .withName("Bitstream1") + .withDescription("Description1") + .withMimeType("text/plain") + .build(); + } + + String bitstreamContent2 = "ThisIsSomeDummyText2"; + Bitstream bitstream2 = null; + try (InputStream is = IOUtils.toInputStream(bitstreamContent2, CharEncoding.UTF_8)) { + bitstream2 = BitstreamBuilder. + createBitstream(context, publicItem1, is) + .withName("Bitstream2") + .withDescription("Description2") + .withMimeType("text/plain") + .build(); + } + + String bitstreamContent3 = "ThisIsSomeDummyText3"; + Bitstream bitstream3 = null; + try (InputStream is = IOUtils.toInputStream(bitstreamContent3, CharEncoding.UTF_8)) { + bitstream3 = BitstreamBuilder. + createBitstream(context, publicItem1, is) + .withName("Bitstream3") + .withDescription("Description3") + .withMimeType("text/plain") + .build(); + } + + context.restoreAuthSystemState(); + + String token = getAuthToken(eperson.getEmail(), password); + + // Delete using an unauthorized user + getClient(token).perform(delete("/api/core/bitstreams") + .contentType(TEXT_URI_LIST) + .content("http://localhost:8080/server/api/core/bitstreams/" + bitstream1.getID() + + " \n http://localhost:8080/server/api/core/bitstreams/" + bitstream2.getID() + + " \n http://localhost:8080/server/api/core/bitstreams/" + bitstream3.getID())) + .andExpect(status().isForbidden()); + + // Verify the bitstreams are still here + getClient().perform(get("/api/core/bitstreams/" + bitstream1.getID())) + .andExpect(status().isOk()); + + getClient().perform(get("/api/core/bitstreams/" + bitstream2.getID())) + .andExpect(status().isOk()); + + getClient().perform(get("/api/core/bitstreams/" + bitstream3.getID())) + .andExpect(status().isOk()); + } + + @Test + public void deleteListUnauthorized() throws Exception { + + //We turn off the authorization system in order to create the structure as defined below + context.turnOffAuthorisationSystem(); + + //** GIVEN ** + //1. A community-collection structure with one parent community with sub-community and one collection. + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); + + //2. One public items that is readable by Anonymous + Item publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Test") + .withIssueDate("2010-10-17") + .withAuthor("Smith, Donald") + .withSubject("ExtraEntry") + .build(); + + // Add 3 bitstreams to the item + String bitstreamContent1 = "ThisIsSomeDummyText1"; + Bitstream bitstream1 = null; + try (InputStream is = IOUtils.toInputStream(bitstreamContent1, CharEncoding.UTF_8)) { + bitstream1 = BitstreamBuilder. + createBitstream(context, publicItem1, is) + .withName("Bitstream1") + .withDescription("Description1") + .withMimeType("text/plain") + .build(); + } + + String bitstreamContent2 = "ThisIsSomeDummyText2"; + Bitstream bitstream2 = null; + try (InputStream is = IOUtils.toInputStream(bitstreamContent2, CharEncoding.UTF_8)) { + bitstream2 = BitstreamBuilder. + createBitstream(context, publicItem1, is) + .withName("Bitstream2") + .withDescription("Description2") + .withMimeType("text/plain") + .build(); + } + + String bitstreamContent3 = "ThisIsSomeDummyText3"; + Bitstream bitstream3 = null; + try (InputStream is = IOUtils.toInputStream(bitstreamContent3, CharEncoding.UTF_8)) { + bitstream3 = BitstreamBuilder. + createBitstream(context, publicItem1, is) + .withName("Bitstream3") + .withDescription("Description3") + .withMimeType("text/plain") + .build(); + } + + context.restoreAuthSystemState(); + + // Delete as anonymous + getClient().perform(delete("/api/core/bitstreams") + .contentType(TEXT_URI_LIST) + .content("http://localhost:8080/server/api/core/bitstreams/" + bitstream1.getID() + + " \n http://localhost:8080/server/api/core/bitstreams/" + bitstream2.getID() + + " \n http://localhost:8080/server/api/core/bitstreams/" + bitstream3.getID())) + .andExpect(status().isUnauthorized()); + + // Verify the bitstreams are still here + getClient().perform(get("/api/core/bitstreams/" + bitstream1.getID())) + .andExpect(status().isOk()); + + getClient().perform(get("/api/core/bitstreams/" + bitstream2.getID())) + .andExpect(status().isOk()); + + getClient().perform(get("/api/core/bitstreams/" + bitstream3.getID())) + .andExpect(status().isOk()); + } + + @Test + public void deleteListEmpty() throws Exception { + + //We turn off the authorization system in order to create the structure as defined below + context.turnOffAuthorisationSystem(); + + //** GIVEN ** + //1. A community-collection structure with one parent community with sub-community and one collection. + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); + + //2. One public items that is readable by Anonymous + Item publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Test") + .withIssueDate("2010-10-17") + .withAuthor("Smith, Donald") + .withSubject("ExtraEntry") + .build(); + + // Add 3 bitstreams to the item + String bitstreamContent1 = "ThisIsSomeDummyText1"; + Bitstream bitstream1 = null; + try (InputStream is = IOUtils.toInputStream(bitstreamContent1, CharEncoding.UTF_8)) { + bitstream1 = BitstreamBuilder. + createBitstream(context, publicItem1, is) + .withName("Bitstream1") + .withDescription("Description1") + .withMimeType("text/plain") + .build(); + } + + String bitstreamContent2 = "ThisIsSomeDummyText2"; + Bitstream bitstream2 = null; + try (InputStream is = IOUtils.toInputStream(bitstreamContent2, CharEncoding.UTF_8)) { + bitstream2 = BitstreamBuilder. + createBitstream(context, publicItem1, is) + .withName("Bitstream2") + .withDescription("Description2") + .withMimeType("text/plain") + .build(); + } + + String bitstreamContent3 = "ThisIsSomeDummyText3"; + Bitstream bitstream3 = null; + try (InputStream is = IOUtils.toInputStream(bitstreamContent3, CharEncoding.UTF_8)) { + bitstream3 = BitstreamBuilder. + createBitstream(context, publicItem1, is) + .withName("Bitstream3") + .withDescription("Description3") + .withMimeType("text/plain") + .build(); + } + + context.restoreAuthSystemState(); + + String token = getAuthToken(admin.getEmail(), password); + + // Delete with empty list throws 404 + getClient(token).perform(delete("/api/core/bitstreams") + .contentType(TEXT_URI_LIST) + .content("")) + .andExpect(status().isNotFound()); + + // Verify the bitstreams are still here + getClient(token).perform(get("/api/core/bitstreams/" + bitstream1.getID())) + .andExpect(status().isOk()); + + getClient(token).perform(get("/api/core/bitstreams/" + bitstream2.getID())) + .andExpect(status().isOk()); + + getClient(token).perform(get("/api/core/bitstreams/" + bitstream3.getID())) + .andExpect(status().isOk()); + } + + @Test + public void deleteListNotBitstream() throws Exception { + + //We turn off the authorization system in order to create the structure as defined below + context.turnOffAuthorisationSystem(); + + //** GIVEN ** + //1. A community-collection structure with one parent community with sub-community and one collection. + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); + + //2. One public items that is readable by Anonymous + Item publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Test") + .withIssueDate("2010-10-17") + .withAuthor("Smith, Donald") + .withSubject("ExtraEntry") + .build(); + + // Add 3 bitstreams to the item + String bitstreamContent1 = "ThisIsSomeDummyText1"; + Bitstream bitstream1 = null; + try (InputStream is = IOUtils.toInputStream(bitstreamContent1, CharEncoding.UTF_8)) { + bitstream1 = BitstreamBuilder. + createBitstream(context, publicItem1, is) + .withName("Bitstream1") + .withDescription("Description1") + .withMimeType("text/plain") + .build(); + } + + String bitstreamContent2 = "ThisIsSomeDummyText2"; + Bitstream bitstream2 = null; + try (InputStream is = IOUtils.toInputStream(bitstreamContent2, CharEncoding.UTF_8)) { + bitstream2 = BitstreamBuilder. + createBitstream(context, publicItem1, is) + .withName("Bitstream2") + .withDescription("Description2") + .withMimeType("text/plain") + .build(); + } + + String bitstreamContent3 = "ThisIsSomeDummyText3"; + Bitstream bitstream3 = null; + try (InputStream is = IOUtils.toInputStream(bitstreamContent3, CharEncoding.UTF_8)) { + bitstream3 = BitstreamBuilder. + createBitstream(context, publicItem1, is) + .withName("Bitstream3") + .withDescription("Description3") + .withMimeType("text/plain") + .build(); + } + + context.restoreAuthSystemState(); + + String token = getAuthToken(admin.getEmail(), password); + + // Delete with list containing non-Bitstream throws 422 + getClient(token).perform(delete("/api/core/bitstreams") + .contentType(TEXT_URI_LIST) + .content("http://localhost:8080/server/api/core/bitstreams/" + bitstream1.getID() + + " \n http://localhost:8080/server/api/core/bitstreams/" + bitstream2.getID() + + " \n http://localhost:8080/server/api/core/bitstreams/" + bitstream3.getID() + + " \n http://localhost:8080/server/api/core/items/" + publicItem1.getID())) + .andExpect(status().is(422)); + + // Verify the bitstreams are still here + getClient(token).perform(get("/api/core/bitstreams/" + bitstream1.getID())) + .andExpect(status().isOk()); + + getClient(token).perform(get("/api/core/bitstreams/" + bitstream2.getID())) + .andExpect(status().isOk()); + + getClient(token).perform(get("/api/core/bitstreams/" + bitstream3.getID())) + .andExpect(status().isOk()); + } + + @Test + public void deleteListDifferentItems() throws Exception { + + //We turn off the authorization system in order to create the structure as defined below + context.turnOffAuthorisationSystem(); + + //** GIVEN ** + //1. A community-collection structure with one parent community with sub-community and one collection. + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); + + //2. Two public items that is readable by Anonymous + Item publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Test") + .withIssueDate("2010-10-17") + .withAuthor("Smith, Donald") + .withSubject("ExtraEntry") + .build(); + + Item publicItem2 = ItemBuilder.createItem(context, col1) + .withTitle("Test") + .withIssueDate("2010-10-17") + .withAuthor("Smith, Donald") + .withSubject("ExtraEntry") + .build(); + + // Add 1 bitstream to each item + String bitstreamContent1 = "ThisIsSomeDummyText1"; + Bitstream bitstream1 = null; + try (InputStream is = IOUtils.toInputStream(bitstreamContent1, CharEncoding.UTF_8)) { + bitstream1 = BitstreamBuilder. + createBitstream(context, publicItem1, is) + .withName("Bitstream1") + .withDescription("Description1") + .withMimeType("text/plain") + .build(); + } + + String bitstreamContent2 = "ThisIsSomeDummyText2"; + Bitstream bitstream2 = null; + try (InputStream is = IOUtils.toInputStream(bitstreamContent2, CharEncoding.UTF_8)) { + bitstream2 = BitstreamBuilder. + createBitstream(context, publicItem2, is) + .withName("Bitstream2") + .withDescription("Description2") + .withMimeType("text/plain") + .build(); + } + + context.restoreAuthSystemState(); + + String token = getAuthToken(admin.getEmail(), password); + + // Delete with list containing Bitstreams from different items throws 422 + getClient(token).perform(delete("/api/core/bitstreams") + .contentType(TEXT_URI_LIST) + .content("http://localhost:8080/server/api/core/bitstreams/" + bitstream1.getID() + + " \n http://localhost:8080/server/api/core/bitstreams/" + bitstream2.getID())) + .andExpect(status().is(422)); + + // Verify the bitstreams are still here + getClient(token).perform(get("/api/core/bitstreams/" + bitstream1.getID())) + .andExpect(status().isOk()); + + getClient(token).perform(get("/api/core/bitstreams/" + bitstream2.getID())) + .andExpect(status().isOk()); + + } + + @Test + public void deleteListLogo() throws Exception { + // We turn off the authorization system in order to create the structure as defined below + context.turnOffAuthorisationSystem(); + + // ** GIVEN ** + // 1. A community with a logo + parentCommunity = CommunityBuilder.createCommunity(context).withName("Community").withLogo("logo_community") + .build(); + + // 2. A collection with a logo + Collection col = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection") + .withLogo("logo_collection").build(); + + context.restoreAuthSystemState(); + + String token = getAuthToken(admin.getEmail(), password); + + // trying to DELETE parentCommunity logo and collection logo should work + // we have to delete them separately otherwise it will throw 422 as they belong to different items + getClient(token).perform(delete("/api/core/bitstreams") + .contentType(TEXT_URI_LIST) + .content("http://localhost:8080/server/api/core/bitstreams/" + parentCommunity.getLogo().getID())) + .andExpect(status().is(204)); + + getClient(token).perform(delete("/api/core/bitstreams") + .contentType(TEXT_URI_LIST) + .content("http://localhost:8080/server/api/core/bitstreams/" + col.getLogo().getID())) + .andExpect(status().is(204)); + + // Verify 404 after delete for parentCommunity logo + getClient(token).perform(get("/api/core/bitstreams/" + parentCommunity.getLogo().getID())) + .andExpect(status().isNotFound()); + + // Verify 404 after delete for collection logo + getClient(token).perform(get("/api/core/bitstreams/" + col.getLogo().getID())) + .andExpect(status().isNotFound()); + } + + @Test + public void deleteListMissing() throws Exception { + String token = getAuthToken(admin.getEmail(), password); + + // Delete + getClient(token).perform(delete("/api/core/bitstreams") + .contentType(TEXT_URI_LIST) + .content("http://localhost:8080/server/api/core/bitstreams/1c11f3f1-ba1f-4f36-908a-3f1ea9a557eb")) + .andExpect(status().isNotFound()); + + // Verify 404 after failed delete + getClient(token).perform(delete("/api/core/bitstreams") + .contentType(TEXT_URI_LIST) + .content("http://localhost:8080/server/api/core/bitstreams/1c11f3f1-ba1f-4f36-908a-3f1ea9a557eb")) + .andExpect(status().isNotFound()); + } + + @Test + public void deleteListOneMissing() throws Exception { + + //We turn off the authorization system in order to create the structure as defined below + context.turnOffAuthorisationSystem(); + + //** GIVEN ** + //1. A community-collection structure with one parent community with sub-community and one collection. + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); + + //2. One public items that is readable by Anonymous + Item publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Test") + .withIssueDate("2010-10-17") + .withAuthor("Smith, Donald") + .withSubject("ExtraEntry") + .build(); + + // Add 3 bitstreams to the item + String bitstreamContent1 = "ThisIsSomeDummyText1"; + Bitstream bitstream1 = null; + try (InputStream is = IOUtils.toInputStream(bitstreamContent1, CharEncoding.UTF_8)) { + bitstream1 = BitstreamBuilder. + createBitstream(context, publicItem1, is) + .withName("Bitstream1") + .withDescription("Description1") + .withMimeType("text/plain") + .build(); + } + + String bitstreamContent2 = "ThisIsSomeDummyText2"; + Bitstream bitstream2 = null; + try (InputStream is = IOUtils.toInputStream(bitstreamContent2, CharEncoding.UTF_8)) { + bitstream2 = BitstreamBuilder. + createBitstream(context, publicItem1, is) + .withName("Bitstream2") + .withDescription("Description2") + .withMimeType("text/plain") + .build(); + } + + String bitstreamContent3 = "ThisIsSomeDummyText3"; + Bitstream bitstream3 = null; + try (InputStream is = IOUtils.toInputStream(bitstreamContent3, CharEncoding.UTF_8)) { + bitstream3 = BitstreamBuilder. + createBitstream(context, publicItem1, is) + .withName("Bitstream3") + .withDescription("Description3") + .withMimeType("text/plain") + .build(); + } + + context.restoreAuthSystemState(); + + String token = getAuthToken(admin.getEmail(), password); + + // Delete all bitstreams and a missing bitstream returns 404 + getClient(token).perform(delete("/api/core/bitstreams") + .contentType(TEXT_URI_LIST) + .content("http://localhost:8080/server/api/core/bitstreams/" + bitstream1.getID() + + " \n http://localhost:8080/server/api/core/bitstreams/" + bitstream2.getID() + + " \n http://localhost:8080/server/api/core/bitstreams/" + bitstream3.getID() + + " \n http://localhost:8080/server/api/core/bitstreams/1c11f3f1-ba1f-4f36-908a-3f1ea9a557eb")) + .andExpect(status().isNotFound()); + + // Verify the bitstreams are still here + getClient(token).perform(get("/api/core/bitstreams/" + bitstream1.getID())) + .andExpect(status().isOk()); + + getClient(token).perform(get("/api/core/bitstreams/" + bitstream2.getID())) + .andExpect(status().isOk()); + + getClient(token).perform(get("/api/core/bitstreams/" + bitstream3.getID())) + .andExpect(status().isOk()); + } + + @Test + public void deleteListOneMissingDifferentItems() throws Exception { + + //We turn off the authorization system in order to create the structure as defined below + context.turnOffAuthorisationSystem(); + + //** GIVEN ** + //1. A community-collection structure with one parent community with sub-community and one collection. + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); + + //2. Two public items that is readable by Anonymous + Item publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Test") + .withIssueDate("2010-10-17") + .withAuthor("Smith, Donald") + .withSubject("ExtraEntry") + .build(); + + Item publicItem2 = ItemBuilder.createItem(context, col1) + .withTitle("Test") + .withIssueDate("2010-10-17") + .withAuthor("Smith, Donald") + .withSubject("ExtraEntry") + .build(); + + // Add 1 bitstream to each item + String bitstreamContent1 = "ThisIsSomeDummyText1"; + Bitstream bitstream1 = null; + try (InputStream is = IOUtils.toInputStream(bitstreamContent1, CharEncoding.UTF_8)) { + bitstream1 = BitstreamBuilder. + createBitstream(context, publicItem1, is) + .withName("Bitstream1") + .withDescription("Description1") + .withMimeType("text/plain") + .build(); + } + + String bitstreamContent2 = "ThisIsSomeDummyText2"; + Bitstream bitstream2 = null; + try (InputStream is = IOUtils.toInputStream(bitstreamContent2, CharEncoding.UTF_8)) { + bitstream2 = BitstreamBuilder. + createBitstream(context, publicItem2, is) + .withName("Bitstream2") + .withDescription("Description2") + .withMimeType("text/plain") + .build(); + } + + context.restoreAuthSystemState(); + + String token = getAuthToken(admin.getEmail(), password); + + // Delete all bitstreams and a missing bitstream returns 404 + getClient(token).perform(delete("/api/core/bitstreams") + .contentType(TEXT_URI_LIST) + .content("http://localhost:8080/server/api/core/bitstreams/" + bitstream1.getID() + + " \n http://localhost:8080/server/api/core/bitstreams/" + bitstream2.getID() + + " \n http://localhost:8080/server/api/core/bitstreams/1c11f3f1-ba1f-4f36-908a-3f1ea9a557eb")) + .andExpect(status().isNotFound()); + + // Verify the bitstreams are still here + getClient(token).perform(get("/api/core/bitstreams/" + bitstream1.getID())) + .andExpect(status().isOk()); + + getClient(token).perform(get("/api/core/bitstreams/" + bitstream2.getID())) + .andExpect(status().isOk()); + + } + + @Test + public void deleteListDeleted() throws Exception { + //We turn off the authorization system in order to create the structure as defined below + context.turnOffAuthorisationSystem(); + + //** GIVEN ** + //1. A community-collection structure with one parent community with sub-community and one collection. + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); + + //2. One public items that is readable by Anonymous + Item publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Test") + .withIssueDate("2010-10-17") + .withAuthor("Smith, Donald") + .withSubject("ExtraEntry") + .build(); + + String bitstreamContent = "ThisIsSomeDummyText"; + //Add a bitstream to an item + Bitstream bitstream = null; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + bitstream = BitstreamBuilder. + createBitstream(context, publicItem1, is) + .withName("Bitstream") + .withDescription("Description") + .withMimeType("text/plain") + .build(); + } + + context.restoreAuthSystemState(); + + String token = getAuthToken(admin.getEmail(), password); + + // Delete + getClient(token).perform(delete("/api/core/bitstreams") + .contentType(TEXT_URI_LIST) + .content("http://localhost:8080/server/api/core/bitstreams/" + bitstream.getID())) + .andExpect(status().is(204)); + + // Verify 404 when trying to delete a non-existing, already deleted, bitstream + getClient(token).perform(delete("/api/core/bitstreams") + .contentType(TEXT_URI_LIST) + .content("http://localhost:8080/server/api/core/bitstreams/" + bitstream.getID())) + .andExpect(status().is(422)); + } + + @Test + public void deleteListOneDeleted() throws Exception { + //We turn off the authorization system in order to create the structure as defined below + context.turnOffAuthorisationSystem(); + + //** GIVEN ** + //1. A community-collection structure with one parent community with sub-community and one collection. + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); + + //2. One public items that is readable by Anonymous + Item publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Test") + .withIssueDate("2010-10-17") + .withAuthor("Smith, Donald") + .withSubject("ExtraEntry") + .build(); + + // Add 3 bitstreams to the item + String bitstreamContent1 = "ThisIsSomeDummyText1"; + Bitstream bitstream1 = null; + try (InputStream is = IOUtils.toInputStream(bitstreamContent1, CharEncoding.UTF_8)) { + bitstream1 = BitstreamBuilder. + createBitstream(context, publicItem1, is) + .withName("Bitstream1") + .withDescription("Description1") + .withMimeType("text/plain") + .build(); + } + + String bitstreamContent2 = "ThisIsSomeDummyText2"; + Bitstream bitstream2 = null; + try (InputStream is = IOUtils.toInputStream(bitstreamContent2, CharEncoding.UTF_8)) { + bitstream2 = BitstreamBuilder. + createBitstream(context, publicItem1, is) + .withName("Bitstream2") + .withDescription("Description2") + .withMimeType("text/plain") + .build(); + } + + String bitstreamContent3 = "ThisIsSomeDummyText3"; + Bitstream bitstream3 = null; + try (InputStream is = IOUtils.toInputStream(bitstreamContent3, CharEncoding.UTF_8)) { + bitstream3 = BitstreamBuilder. + createBitstream(context, publicItem1, is) + .withName("Bitstream3") + .withDescription("Description3") + .withMimeType("text/plain") + .build(); + } + + context.restoreAuthSystemState(); + + String token = getAuthToken(admin.getEmail(), password); + + // Delete bitstream1 + getClient(token).perform(delete("/api/core/bitstreams") + .contentType(TEXT_URI_LIST) + .content("http://localhost:8080/server/api/core/bitstreams/" + bitstream1.getID())) + .andExpect(status().is(204)); + + // Verify 404 when trying to delete a non-existing, already deleted, bitstream + getClient(token).perform(delete("/api/core/bitstreams") + .contentType(TEXT_URI_LIST) + .content("http://localhost:8080/server/api/core/bitstreams/" + bitstream1.getID() + + " \n http://localhost:8080/server/api/core/bitstreams/" + bitstream2.getID() + + " \n http://localhost:8080/server/api/core/bitstreams/" + bitstream3.getID())) + .andExpect(status().is(422)); + } + @Test public void patchBitstreamMetadataAuthorized() throws Exception { runPatchMetadataTests(admin, 200); From 464465560187002f0d50dbd0f6a9f12044a42723 Mon Sep 17 00:00:00 2001 From: jensroets Date: Wed, 14 Sep 2022 15:49:03 +0200 Subject: [PATCH 025/510] 94299 Multiple Bitstream deletion endpoint: rename items to parents --- .../dspace/app/rest/repository/BitstreamRestRepository.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BitstreamRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BitstreamRestRepository.java index f599d993be43..3696b3866808 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BitstreamRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BitstreamRestRepository.java @@ -161,7 +161,7 @@ protected void deleteList(Context context, List dsoList) throw new UnprocessableEntityException("Not all given items are bitstreams."); } // check that they're all part of the same Item - List items = new ArrayList<>(); + List parents = new ArrayList<>(); for (DSpaceObject dso : dsoList) { Bitstream bit = bs.find(context, dso.getID()); DSpaceObject bitstreamParent = bs.getParentObject(context, bit); @@ -173,10 +173,10 @@ protected void deleteList(Context context, List dsoList) throw new UnprocessableEntityException("The bitstream with uuid " + bit.getID() + " was already deleted"); } else { - items.add(bitstreamParent); + parents.add(bitstreamParent); } } - if (items.stream().distinct().count() > 1) { + if (parents.stream().distinct().count() > 1) { throw new UnprocessableEntityException("Not all given items are part of the same Item."); } // delete all Bitstreams From b05d19ed6caca47fb0f599fb48145223f934b272 Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Tue, 6 Sep 2022 19:17:24 +0200 Subject: [PATCH 026/510] Always use md5 checksum for data integrity check. Send it to S3 to exclude corruption during upload --- .../storage/bitstore/S3BitStoreService.java | 30 ++++++++++++++----- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/storage/bitstore/S3BitStoreService.java b/dspace-api/src/main/java/org/dspace/storage/bitstore/S3BitStoreService.java index 992b940df2ba..f5225154db1d 100644 --- a/dspace-api/src/main/java/org/dspace/storage/bitstore/S3BitStoreService.java +++ b/dspace-api/src/main/java/org/dspace/storage/bitstore/S3BitStoreService.java @@ -8,8 +8,12 @@ package org.dspace.storage.bitstore; import java.io.File; +import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.security.DigestInputStream; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.util.Map; import java.util.function.Supplier; import javax.validation.constraints.NotNull; @@ -35,7 +39,7 @@ import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; -import org.apache.commons.io.FileUtils; +import org.apache.commons.codec.binary.Base64; import org.apache.commons.lang3.StringUtils; import org.apache.http.HttpStatus; import org.apache.logging.log4j.LogManager; @@ -258,15 +262,24 @@ public void put(Bitstream bitstream, InputStream in) throws IOException { String key = getFullKey(bitstream.getInternalId()); //Copy istream to temp file, and send the file, with some metadata File scratchFile = File.createTempFile(bitstream.getInternalId(), "s3bs"); - try { - FileUtils.copyInputStreamToFile(in, scratchFile); - long contentLength = scratchFile.length(); - + try ( + FileOutputStream fos = new FileOutputStream(scratchFile); + // Read through a digest input stream that will work out the MD5 + DigestInputStream dis = new DigestInputStream(in, MessageDigest.getInstance(CSA)); + ) { + Utils.bufferedCopy(dis, fos); + in.close(); + byte[] md5Digest = dis.getMessageDigest().digest(); + String md5Base64 = Base64.encodeBase64String(md5Digest); + ObjectMetadata objMetadata = new ObjectMetadata(); + objMetadata.setContentMD5(md5Base64); PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, key, scratchFile); PutObjectResult putObjectResult = s3Service.putObject(putObjectRequest); - bitstream.setSizeBytes(contentLength); - bitstream.setChecksum(putObjectResult.getETag()); + bitstream.setSizeBytes(scratchFile.length()); + // we cannot use the S3 ETAG here as it could be not a MD5 in case of multipart upload (large files) or if + // the bucket is encrypted + bitstream.setChecksum(Utils.toHex(md5Digest)); bitstream.setChecksumAlgorithm(CSA); scratchFile.delete(); @@ -274,6 +287,9 @@ public void put(Bitstream bitstream, InputStream in) throws IOException { } catch (AmazonClientException | IOException e) { log.error("put(" + bitstream.getInternalId() + ", is)", e); throw new IOException(e); + } catch (NoSuchAlgorithmException nsae) { + // Should never happen + log.warn("Caught NoSuchAlgorithmException", nsae); } finally { if (scratchFile.exists()) { scratchFile.delete(); From d7d2723d65a8ff2b0ca9f0cf231db0abc7b2ca5b Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Fri, 30 Sep 2022 16:08:36 +0200 Subject: [PATCH 027/510] [DURACOM-92] Use TransferManager to download files from S3 --- .../DeleteOnCloseFileInputStream.java | 42 +++++++++++++++++++ .../storage/bitstore/S3BitStoreService.java | 22 ++++++++-- 2 files changed, 60 insertions(+), 4 deletions(-) create mode 100644 dspace-api/src/main/java/org/dspace/storage/bitstore/DeleteOnCloseFileInputStream.java diff --git a/dspace-api/src/main/java/org/dspace/storage/bitstore/DeleteOnCloseFileInputStream.java b/dspace-api/src/main/java/org/dspace/storage/bitstore/DeleteOnCloseFileInputStream.java new file mode 100644 index 000000000000..62c24544eeac --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/storage/bitstore/DeleteOnCloseFileInputStream.java @@ -0,0 +1,42 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.storage.bitstore; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; + +/** + * When inputstream closes, then delete the file + * http://stackoverflow.com/a/4694155/368581 + */ +public class DeleteOnCloseFileInputStream extends FileInputStream { + + private File file; + + public DeleteOnCloseFileInputStream(String fileName) throws FileNotFoundException { + this(new File(fileName)); + } + + public DeleteOnCloseFileInputStream(File file) throws FileNotFoundException { + super(file); + this.file = file; + } + + public void close() throws IOException { + try { + super.close(); + } finally { + if (file != null) { + file.delete(); + file = null; + } + } + } +} diff --git a/dspace-api/src/main/java/org/dspace/storage/bitstore/S3BitStoreService.java b/dspace-api/src/main/java/org/dspace/storage/bitstore/S3BitStoreService.java index f5225154db1d..6f6711458919 100644 --- a/dspace-api/src/main/java/org/dspace/storage/bitstore/S3BitStoreService.java +++ b/dspace-api/src/main/java/org/dspace/storage/bitstore/S3BitStoreService.java @@ -15,6 +15,7 @@ import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Map; +import java.util.UUID; import java.util.function.Supplier; import javax.validation.constraints.NotNull; @@ -32,7 +33,9 @@ import com.amazonaws.services.s3.model.ObjectMetadata; import com.amazonaws.services.s3.model.PutObjectRequest; import com.amazonaws.services.s3.model.PutObjectResult; -import com.amazonaws.services.s3.model.S3Object; +import com.amazonaws.services.s3.transfer.Download; +import com.amazonaws.services.s3.transfer.TransferManager; +import com.amazonaws.services.s3.transfer.TransferManagerBuilder; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.DefaultParser; import org.apache.commons.cli.HelpFormatter; @@ -238,9 +241,20 @@ public String generateId() { public InputStream get(Bitstream bitstream) throws IOException { String key = getFullKey(bitstream.getInternalId()); try { - S3Object object = s3Service.getObject(new GetObjectRequest(bucketName, key)); - return (object != null) ? object.getObjectContent() : null; - } catch (AmazonClientException e) { + File tempFile = File.createTempFile("s3-disk-copy-" + UUID.randomUUID(), "temp"); + tempFile.deleteOnExit(); + + GetObjectRequest getObjectRequest = new GetObjectRequest(bucketName, key); + + TransferManager transferManager = TransferManagerBuilder.standard() + .withS3Client(s3Service) + .build(); + + Download download = transferManager.download(getObjectRequest, tempFile); + download.waitForCompletion(); + + return new DeleteOnCloseFileInputStream(tempFile); + } catch (AmazonClientException | InterruptedException e) { log.error("get(" + key + ")", e); throw new IOException(e); } From 5210aedbf32d9fda7cde79e407d7ba256aad97a1 Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Sun, 2 Oct 2022 19:44:29 +0200 Subject: [PATCH 028/510] CST-6950 fix checksum check and history --- .../checker/ChecksumHistoryServiceImpl.java | 3 +- .../storage/bitstore/S3BitStoreService.java | 43 ++++++++++++++++++- dspace/config/modules/storage.cfg | 34 +++++++++++++++ dspace/config/spring/api/bitstore.xml | 1 + 4 files changed, 79 insertions(+), 2 deletions(-) create mode 100644 dspace/config/modules/storage.cfg diff --git a/dspace-api/src/main/java/org/dspace/checker/ChecksumHistoryServiceImpl.java b/dspace-api/src/main/java/org/dspace/checker/ChecksumHistoryServiceImpl.java index f8d6560e9246..f7b05d4de9d3 100644 --- a/dspace-api/src/main/java/org/dspace/checker/ChecksumHistoryServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/checker/ChecksumHistoryServiceImpl.java @@ -74,7 +74,8 @@ public void addHistory(Context context, MostRecentChecksum mostRecentChecksum) t if (mostRecentChecksum.getBitstream().isDeleted()) { checksumResult = checksumResultService.findByCode(context, ChecksumResultCode.BITSTREAM_MARKED_DELETED); } else { - checksumResult = checksumResultService.findByCode(context, ChecksumResultCode.CHECKSUM_MATCH); + checksumResult = checksumResultService.findByCode(context, + mostRecentChecksum.getChecksumResult().getResultCode()); } checksumHistory.setResult(checksumResult); diff --git a/dspace-api/src/main/java/org/dspace/storage/bitstore/S3BitStoreService.java b/dspace-api/src/main/java/org/dspace/storage/bitstore/S3BitStoreService.java index 6f6711458919..b1e26f8624de 100644 --- a/dspace-api/src/main/java/org/dspace/storage/bitstore/S3BitStoreService.java +++ b/dspace-api/src/main/java/org/dspace/storage/bitstore/S3BitStoreService.java @@ -97,6 +97,7 @@ public class S3BitStoreService extends BaseBitStoreService { private String awsSecretKey; private String awsRegionName; private boolean useRelativePath; + private boolean trustS3Etag; /** * container for all the assets @@ -330,7 +331,34 @@ public Map about(Bitstream bitstream, Map attrs) throws IOException { try { ObjectMetadata objectMetadata = s3Service.getObjectMetadata(bucketName, key); if (objectMetadata != null) { - return this.about(objectMetadata, attrs); + if (attrs.containsKey("size_bytes")) { + attrs.put("size_bytes", objectMetadata.getContentLength()); + } + if (attrs.containsKey("checksum")) { + String eTag = objectMetadata.getETag(); + if (trustS3Etag && isMD5Checksum(eTag)) { + attrs.put("checksum", eTag); + } else { + try ( + InputStream in = get(bitstream); + // Read through a digest input stream that will work out the MD5 + DigestInputStream dis = new DigestInputStream(in, MessageDigest.getInstance(CSA)); + ) { + in.close(); + byte[] md5Digest = dis.getMessageDigest().digest(); + String md5Base64 = Base64.encodeBase64String(md5Digest); + attrs.put("checksum", md5Base64); + } catch (NoSuchAlgorithmException nsae) { + // Should never happen + log.warn("Caught NoSuchAlgorithmException", nsae); + } + } + attrs.put("checksum_algorithm", CSA); + } + if (attrs.containsKey("modified")) { + attrs.put("modified", String.valueOf(objectMetadata.getLastModified().getTime())); + } + return attrs; } } catch (AmazonS3Exception e) { if (e.getStatusCode() == HttpStatus.SC_NOT_FOUND) { @@ -343,6 +371,11 @@ public Map about(Bitstream bitstream, Map attrs) throws IOException { return null; } + private boolean isMD5Checksum(String eTag) { + // if the etag is NOT an MD5 it end with -x where x is the number of part used in the multipart upload + return StringUtils.contains(eTag, "-"); + } + /** * Populates map values by checking key existence *
@@ -495,6 +528,14 @@ public void setUseRelativePath(boolean useRelativePath) { this.useRelativePath = useRelativePath; } + public void setTrustS3Etag(boolean trustS3Etag) { + this.trustS3Etag = trustS3Etag; + } + + public boolean isTrustS3Etag() { + return trustS3Etag; + } + /** * Contains a command-line testing tool. Expects arguments: * -a accessKey -s secretKey -f assetFileName diff --git a/dspace/config/modules/storage.cfg b/dspace/config/modules/storage.cfg new file mode 100644 index 000000000000..c19aab7c089e --- /dev/null +++ b/dspace/config/modules/storage.cfg @@ -0,0 +1,34 @@ +#---------------------------------------------------------------# +#-----------------STORAGE CONFIGURATIONS------------------------# +#---------------------------------------------------------------# +# Configuration properties used by the bitstore.xml config file # +# # +#---------------------------------------------------------------# + +# Use the localStore or the s3Store implementation +assetstore.storename.0 = localStore + +# For using a relative path (xx/xx/xx/xxx...) set to true, default it false +assetstore.s3.useRelativePath = false + +## Assetstore S3 configuration, only used if the above configuration +## is set to s3Store + +# S3 bucket name to store assets in, default would generate a bucket +# based on the dspace host name +assetstore.s3.bucketName = +# Subfolder to organize assets within the bucket, in case this bucket +# is shared. Optional, default is root level of bucket +assetstore.s3.subfolder = + +# please do not use these in production but rely on the aws credentials +# discovery mechanism to configure them (ENV VAR, EC2 Iam role, etc.) +assetstore.s3.awsAccessKey = +assetstore.s3.awsSecretKey = +# to force the use of a specific region when credentials are provided +# in this configuratin file. If credentials are left empty this prop +# is ignored +assetstore.s3.awsRegionName = +# trust s3 ETag during the checker process, if it is a md5 checksum +# setting it to false will download the file locally to compute the md5 +assetstore.s3.trustS3Etag = true \ No newline at end of file diff --git a/dspace/config/spring/api/bitstore.xml b/dspace/config/spring/api/bitstore.xml index 15bb3ef1580b..ee5328b5bc68 100644 --- a/dspace/config/spring/api/bitstore.xml +++ b/dspace/config/spring/api/bitstore.xml @@ -23,6 +23,7 @@ + From 70b1ee19f3b5d7e5e0894fcbf0300a6f5f4f3faf Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Sun, 2 Oct 2022 20:47:48 +0200 Subject: [PATCH 029/510] CST-6950 fix checksum check --- .../storage/bitstore/S3BitStoreService.java | 37 ++++++++----------- 1 file changed, 15 insertions(+), 22 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/storage/bitstore/S3BitStoreService.java b/dspace-api/src/main/java/org/dspace/storage/bitstore/S3BitStoreService.java index b1e26f8624de..da170fe0b037 100644 --- a/dspace-api/src/main/java/org/dspace/storage/bitstore/S3BitStoreService.java +++ b/dspace-api/src/main/java/org/dspace/storage/bitstore/S3BitStoreService.java @@ -334,32 +334,25 @@ public Map about(Bitstream bitstream, Map attrs) throws IOException { if (attrs.containsKey("size_bytes")) { attrs.put("size_bytes", objectMetadata.getContentLength()); } - if (attrs.containsKey("checksum")) { - String eTag = objectMetadata.getETag(); - if (trustS3Etag && isMD5Checksum(eTag)) { - attrs.put("checksum", eTag); - } else { - try ( - InputStream in = get(bitstream); - // Read through a digest input stream that will work out the MD5 - DigestInputStream dis = new DigestInputStream(in, MessageDigest.getInstance(CSA)); - ) { - in.close(); - byte[] md5Digest = dis.getMessageDigest().digest(); - String md5Base64 = Base64.encodeBase64String(md5Digest); - attrs.put("checksum", md5Base64); - } catch (NoSuchAlgorithmException nsae) { - // Should never happen - log.warn("Caught NoSuchAlgorithmException", nsae); - } - } - attrs.put("checksum_algorithm", CSA); - } if (attrs.containsKey("modified")) { attrs.put("modified", String.valueOf(objectMetadata.getLastModified().getTime())); } - return attrs; } + try ( + InputStream in = get(bitstream); + // Read through a digest input stream that will work out the MD5 + DigestInputStream dis = new DigestInputStream(in, MessageDigest.getInstance(CSA)); + ) { + in.close(); + byte[] md5Digest = dis.getMessageDigest().digest(); + String md5Base64 = Base64.encodeBase64String(md5Digest); + attrs.put("checksum", md5Base64); + attrs.put("checksum_algorithm", CSA); + } catch (NoSuchAlgorithmException nsae) { + // Should never happen + log.warn("Caught NoSuchAlgorithmException", nsae); + } + return attrs; } catch (AmazonS3Exception e) { if (e.getStatusCode() == HttpStatus.SC_NOT_FOUND) { return null; From 75840c70c6df6949f26acc15843e997657008024 Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Sun, 2 Oct 2022 21:49:28 +0200 Subject: [PATCH 030/510] CST-6950 fix checker report query --- .../dspace/checker/dao/impl/MostRecentChecksumDAOImpl.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/checker/dao/impl/MostRecentChecksumDAOImpl.java b/dspace-api/src/main/java/org/dspace/checker/dao/impl/MostRecentChecksumDAOImpl.java index 66ce666b9d6d..a31e02cbab4a 100644 --- a/dspace-api/src/main/java/org/dspace/checker/dao/impl/MostRecentChecksumDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/checker/dao/impl/MostRecentChecksumDAOImpl.java @@ -92,8 +92,8 @@ public List findByResultTypeInDateRange(Context context, Dat criteriaQuery.where(criteriaBuilder.and( criteriaBuilder.equal(mostRecentResult.get(ChecksumResult_.resultCode), resultCode), criteriaBuilder.lessThanOrEqualTo( - mostRecentChecksumRoot.get(MostRecentChecksum_.processStartDate), startDate), - criteriaBuilder.greaterThan(mostRecentChecksumRoot.get(MostRecentChecksum_.processStartDate), endDate) + mostRecentChecksumRoot.get(MostRecentChecksum_.processStartDate), endDate), + criteriaBuilder.greaterThan(mostRecentChecksumRoot.get(MostRecentChecksum_.processStartDate), startDate) ) ); List orderList = new LinkedList<>(); From 66c452bcf93e5366e2ef4da5bd892a079e31864e Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Sun, 2 Oct 2022 22:56:29 +0200 Subject: [PATCH 031/510] CST-6950 fix report email --- .../org/dspace/checker/SimpleReporterServiceImpl.java | 2 ++ dspace-api/src/main/java/org/dspace/core/Email.java | 6 ++++-- .../org/dspace/storage/bitstore/S3BitStoreService.java | 9 --------- dspace/config/modules/storage.cfg | 5 +---- dspace/config/spring/api/bitstore.xml | 1 - 5 files changed, 7 insertions(+), 16 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/checker/SimpleReporterServiceImpl.java b/dspace-api/src/main/java/org/dspace/checker/SimpleReporterServiceImpl.java index 26c102e1e78b..ddefb28e1b57 100644 --- a/dspace-api/src/main/java/org/dspace/checker/SimpleReporterServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/checker/SimpleReporterServiceImpl.java @@ -152,6 +152,7 @@ public int getBitstreamNotFoundReport(Context context, Date startDate, Date endD osw.write("\n"); osw.write(msg("bitstream-not-found-report")); + osw.write(" "); osw.write(applyDateFormatShort(startDate)); osw.write(" "); osw.write(msg("date-range-to")); @@ -230,6 +231,7 @@ public int getUncheckedBitstreamsReport(Context context, OutputStreamWriter osw) osw.write("\n"); osw.write(msg("unchecked-bitstream-report")); + osw.write(" "); osw.write(applyDateFormatShort(new Date())); osw.write("\n\n\n"); diff --git a/dspace-api/src/main/java/org/dspace/core/Email.java b/dspace-api/src/main/java/org/dspace/core/Email.java index 6db27c9e4f18..6b86756e0c08 100644 --- a/dspace-api/src/main/java/org/dspace/core/Email.java +++ b/dspace-api/src/main/java/org/dspace/core/Email.java @@ -314,6 +314,8 @@ public void send() throws MessagingException, IOException { message.addRecipient(Message.RecipientType.TO, new InternetAddress( i.next())); } + // Get headers defined by the template. + String[] templateHeaders = config.getArrayProperty("mail.message.headers"); // Format the mail message body VelocityEngine templateEngine = new VelocityEngine(); @@ -334,6 +336,7 @@ public void send() throws MessagingException, IOException { repo.putStringResource(contentName, content); // Turn content into a template. template = templateEngine.getTemplate(contentName); + templateHeaders = new String[] {}; } StringWriter writer = new StringWriter(); @@ -351,8 +354,7 @@ public void send() throws MessagingException, IOException { message.setSentDate(date); message.setFrom(new InternetAddress(from)); - // Get headers defined by the template. - for (String headerName : config.getArrayProperty("mail.message.headers")) { + for (String headerName : templateHeaders) { String headerValue = (String) vctx.get(headerName); if ("subject".equalsIgnoreCase(headerName)) { if (null != subject) { diff --git a/dspace-api/src/main/java/org/dspace/storage/bitstore/S3BitStoreService.java b/dspace-api/src/main/java/org/dspace/storage/bitstore/S3BitStoreService.java index da170fe0b037..f287c0a59191 100644 --- a/dspace-api/src/main/java/org/dspace/storage/bitstore/S3BitStoreService.java +++ b/dspace-api/src/main/java/org/dspace/storage/bitstore/S3BitStoreService.java @@ -97,7 +97,6 @@ public class S3BitStoreService extends BaseBitStoreService { private String awsSecretKey; private String awsRegionName; private boolean useRelativePath; - private boolean trustS3Etag; /** * container for all the assets @@ -521,14 +520,6 @@ public void setUseRelativePath(boolean useRelativePath) { this.useRelativePath = useRelativePath; } - public void setTrustS3Etag(boolean trustS3Etag) { - this.trustS3Etag = trustS3Etag; - } - - public boolean isTrustS3Etag() { - return trustS3Etag; - } - /** * Contains a command-line testing tool. Expects arguments: * -a accessKey -s secretKey -f assetFileName diff --git a/dspace/config/modules/storage.cfg b/dspace/config/modules/storage.cfg index c19aab7c089e..3b9171585ee1 100644 --- a/dspace/config/modules/storage.cfg +++ b/dspace/config/modules/storage.cfg @@ -28,7 +28,4 @@ assetstore.s3.awsSecretKey = # to force the use of a specific region when credentials are provided # in this configuratin file. If credentials are left empty this prop # is ignored -assetstore.s3.awsRegionName = -# trust s3 ETag during the checker process, if it is a md5 checksum -# setting it to false will download the file locally to compute the md5 -assetstore.s3.trustS3Etag = true \ No newline at end of file +assetstore.s3.awsRegionName = \ No newline at end of file diff --git a/dspace/config/spring/api/bitstore.xml b/dspace/config/spring/api/bitstore.xml index ee5328b5bc68..15bb3ef1580b 100644 --- a/dspace/config/spring/api/bitstore.xml +++ b/dspace/config/spring/api/bitstore.xml @@ -23,7 +23,6 @@ - From 9bc01e8f05f00c4b5dec4d68390872f3758e0fd3 Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Mon, 3 Oct 2022 12:05:12 +0200 Subject: [PATCH 032/510] [DURACOM-92] Fixed S3BitStoreServiceTest tests --- .../storage/bitstore/S3BitStoreService.java | 2 +- .../bitstore/S3BitStoreServiceTest.java | 164 +++++------------- dspace/config/modules/storage.cfg | 31 ---- 3 files changed, 41 insertions(+), 156 deletions(-) delete mode 100644 dspace/config/modules/storage.cfg diff --git a/dspace-api/src/main/java/org/dspace/storage/bitstore/S3BitStoreService.java b/dspace-api/src/main/java/org/dspace/storage/bitstore/S3BitStoreService.java index 5d531e2b12e8..24204c7cb1c7 100644 --- a/dspace-api/src/main/java/org/dspace/storage/bitstore/S3BitStoreService.java +++ b/dspace-api/src/main/java/org/dspace/storage/bitstore/S3BitStoreService.java @@ -75,7 +75,7 @@ public class S3BitStoreService extends BaseBitStoreService { /** * Checksum algorithm */ - private static final String CSA = "MD5"; + static final String CSA = "MD5"; // These settings control the way an identifier is hashed into // directory and file names diff --git a/dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceTest.java b/dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceTest.java index e972aaa02b00..56080d1f97df 100644 --- a/dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceTest.java +++ b/dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceTest.java @@ -9,32 +9,35 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.isEmptyOrNullString; -import static org.junit.Assert.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.startsWith; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.nio.charset.Charset; import java.util.function.Supplier; import com.amazonaws.regions.Regions; import com.amazonaws.services.s3.AmazonS3; import com.amazonaws.services.s3.AmazonS3Client; import com.amazonaws.services.s3.model.GetObjectRequest; -import com.amazonaws.services.s3.model.PutObjectRequest; -import com.amazonaws.services.s3.model.PutObjectResult; -import com.amazonaws.services.s3.model.S3Object; -import com.amazonaws.services.s3.model.S3ObjectInputStream; +import com.amazonaws.services.s3.transfer.Download; import com.amazonaws.services.s3.transfer.TransferManager; import com.amazonaws.services.s3.transfer.Upload; import com.amazonaws.services.s3.transfer.model.UploadResult; import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; import org.dspace.AbstractUnitTest; import org.dspace.content.Bitstream; -import org.dspace.curate.Utils; import org.hamcrest.Matchers; import org.junit.Before; import org.junit.Test; @@ -42,6 +45,7 @@ import org.mockito.Mock; import org.mockito.MockedStatic; import org.mockito.Mockito; +import org.mockito.invocation.InvocationOnMock; @@ -158,49 +162,17 @@ public void givenAccessKeysWhenInitThenVerifiesCorrectBuilderCreation() throws I @Test public void givenBucketBitStreamIdInputStreamWhenRetrievingFromS3ThenUsesBucketBitStreamId() throws IOException { String bucketName = "BucketTest"; - String bitStreamId = "BitStreamId"; this.s3BitStoreService.setBucketName(bucketName); this.s3BitStoreService.setUseRelativePath(false); - when(bitstream.getInternalId()).thenReturn(bitStreamId); - - S3Object object = Mockito.mock(S3Object.class); - S3ObjectInputStream inputStream = Mockito.mock(S3ObjectInputStream.class); - when(object.getObjectContent()).thenReturn(inputStream); - when(this.s3Service.getObject(ArgumentMatchers.any(GetObjectRequest.class))).thenReturn(object); - this.s3BitStoreService.init(); - assertThat(this.s3BitStoreService.get(bitstream), Matchers.equalTo(inputStream)); - verify(this.s3Service).getObject( - ArgumentMatchers.argThat( - request -> - bucketName.contentEquals(request.getBucketName()) && - bitStreamId.contentEquals(request.getKey()) - ) - ); + Download download = mock(Download.class); - } - - @Test - public void givenBucketBitStreamIdWhenNothingFoundOnS3ThenReturnsNull() throws IOException { - String bucketName = "BucketTest"; - String bitStreamId = "BitStreamId"; - this.s3BitStoreService.setBucketName(bucketName); - this.s3BitStoreService.setUseRelativePath(false); - when(bitstream.getInternalId()).thenReturn(bitStreamId); - - when(this.s3Service.getObject(ArgumentMatchers.any(GetObjectRequest.class))).thenReturn(null); - - this.s3BitStoreService.init(); - assertThat(this.s3BitStoreService.get(bitstream), Matchers.nullValue()); + when(tm.download(any(GetObjectRequest.class), any(File.class))) + .thenAnswer(invocation -> writeIntoFile(download, invocation, "Test file content")); - verify(this.s3Service).getObject( - ArgumentMatchers.argThat( - request -> - bucketName.contentEquals(request.getBucketName()) && - bitStreamId.contentEquals(request.getKey()) - ) - ); + InputStream inputStream = this.s3BitStoreService.get(bitstream); + assertThat(IOUtils.toString(inputStream, Charset.defaultCharset()), is("Test file content")); } @@ -214,23 +186,14 @@ public void givenSubFolderWhenRequestsItemFromS3ThenTheIdentifierShouldHavePrope this.s3BitStoreService.setSubfolder(subfolder); when(bitstream.getInternalId()).thenReturn(bitStreamId); - S3Object object = Mockito.mock(S3Object.class); - S3ObjectInputStream inputStream = Mockito.mock(S3ObjectInputStream.class); - when(object.getObjectContent()).thenReturn(inputStream); - when(this.s3Service.getObject(ArgumentMatchers.any(GetObjectRequest.class))).thenReturn(object); + Download download = mock(Download.class); + + when(tm.download(any(GetObjectRequest.class), any(File.class))) + .thenAnswer(invocation -> writeIntoFile(download, invocation, "Test file content")); this.s3BitStoreService.init(); - assertThat(this.s3BitStoreService.get(bitstream), Matchers.equalTo(inputStream)); - - verify(this.s3Service).getObject( - ArgumentMatchers.argThat( - request -> - bucketName.equals(request.getBucketName()) && - request.getKey().startsWith(subfolder) && - request.getKey().contains(bitStreamId) && - !request.getKey().contains(File.separator + File.separator) - ) - ); + InputStream inputStream = this.s3BitStoreService.get(bitstream); + assertThat(IOUtils.toString(inputStream, Charset.defaultCharset()), is("Test file content")); } @@ -364,86 +327,39 @@ public void givenBitStreamWhenPutThenCallS3PutMethodAndStoresInBitStream() throw this.s3BitStoreService.setUseRelativePath(false); when(bitstream.getInternalId()).thenReturn(bitStreamId); - File file = Mockito.mock(File.class); - InputStream in = Mockito.mock(InputStream.class); - PutObjectResult putObjectResult = Mockito.mock(PutObjectResult.class); + InputStream in = IOUtils.toInputStream("Test file content", Charset.defaultCharset()); + Upload upload = Mockito.mock(Upload.class); UploadResult uploadResult = Mockito.mock(UploadResult.class); when(upload.waitForUploadResult()).thenReturn(uploadResult); - String mockedTag = "1a7771d5fdd7bfdfc84033c70b1ba555"; - when(file.length()).thenReturn(8L); - try (MockedStatic fileMock = Mockito.mockStatic(File.class)) { - try (MockedStatic fileUtilsMock = Mockito.mockStatic(FileUtils.class)) { - try (MockedStatic curateUtils = Mockito.mockStatic(Utils.class)) { - curateUtils.when(() -> Utils.checksum((File) ArgumentMatchers.any(), ArgumentMatchers.any())) - .thenReturn(mockedTag); - - fileMock - .when(() -> File.createTempFile(ArgumentMatchers.any(), ArgumentMatchers.any())) - .thenReturn(file); - - when(this.tm.upload(ArgumentMatchers.any(), ArgumentMatchers.any(), ArgumentMatchers.any())) - .thenReturn(upload); - - this.s3BitStoreService.init(); - this.s3BitStoreService.put(bitstream, in); - } - } - - } - verify(this.bitstream, Mockito.times(1)).setSizeBytes( - ArgumentMatchers.eq(8L) - ); + when(this.tm.upload(ArgumentMatchers.any(), ArgumentMatchers.any(), ArgumentMatchers.any())) + .thenReturn(upload); - verify(this.bitstream, Mockito.times(1)).setChecksum( - ArgumentMatchers.eq(mockedTag) - ); + this.s3BitStoreService.init(); + this.s3BitStoreService.put(bitstream, in); - verify(this.tm, Mockito.times(1)).upload( - ArgumentMatchers.eq(bucketName), - ArgumentMatchers.eq(bitStreamId), - ArgumentMatchers.eq(file) - ); + verify(this.bitstream).setSizeBytes(17); + verify(this.bitstream, times(2)).getInternalId(); + verify(this.bitstream).setChecksum("ac79653edeb65ab5563585f2d5f14fe9"); + verify(this.bitstream).setChecksumAlgorithm(org.dspace.storage.bitstore.S3BitStoreService.CSA); + verify(this.tm).upload(eq(bucketName), eq(bitStreamId), any(File.class)); - verify(file, Mockito.times(1)).delete(); + verifyNoMoreInteractions(this.bitstream, this.tm); } - @Test - public void givenBitStreamWhenCallingPutFileCopyingThrowsIOExceptionPutThenFileIsRemovedAndStreamClosed() - throws Exception { - String bucketName = "BucketTest"; - String bitStreamId = "BitStreamId"; - this.s3BitStoreService.setBucketName(bucketName); - this.s3BitStoreService.setUseRelativePath(false); - when(bitstream.getInternalId()).thenReturn(bitStreamId); + private Download writeIntoFile(Download download, InvocationOnMock invocation, String content) { - File file = Mockito.mock(File.class); - InputStream in = Mockito.mock(InputStream.class); - try (MockedStatic fileMock = Mockito.mockStatic(File.class)) { - try (MockedStatic fileUtilsMock = Mockito.mockStatic(FileUtils.class)) { - fileUtilsMock - .when(() -> FileUtils.copyInputStreamToFile(ArgumentMatchers.any(), ArgumentMatchers.any())) - .thenThrow(IOException.class); - fileMock - .when(() -> File.createTempFile(ArgumentMatchers.any(), ArgumentMatchers.any())) - .thenReturn(file); - - this.s3BitStoreService.init(); - assertThrows(IOException.class, () -> this.s3BitStoreService.put(bitstream, in)); - } + File file = invocation.getArgument(1, File.class); + try { + FileUtils.write(file, content, Charset.defaultCharset()); + } catch (IOException e) { + throw new RuntimeException(e); } - verify(this.bitstream, Mockito.never()).setSizeBytes(ArgumentMatchers.any(Long.class)); - - verify(this.bitstream, Mockito.never()).setChecksum(ArgumentMatchers.any(String.class)); - - verify(this.s3Service, Mockito.never()).putObject(ArgumentMatchers.any(PutObjectRequest.class)); - - verify(file, Mockito.times(1)).delete(); - + return download; } private int computeSlashes(String internalId) { diff --git a/dspace/config/modules/storage.cfg b/dspace/config/modules/storage.cfg deleted file mode 100644 index 3b9171585ee1..000000000000 --- a/dspace/config/modules/storage.cfg +++ /dev/null @@ -1,31 +0,0 @@ -#---------------------------------------------------------------# -#-----------------STORAGE CONFIGURATIONS------------------------# -#---------------------------------------------------------------# -# Configuration properties used by the bitstore.xml config file # -# # -#---------------------------------------------------------------# - -# Use the localStore or the s3Store implementation -assetstore.storename.0 = localStore - -# For using a relative path (xx/xx/xx/xxx...) set to true, default it false -assetstore.s3.useRelativePath = false - -## Assetstore S3 configuration, only used if the above configuration -## is set to s3Store - -# S3 bucket name to store assets in, default would generate a bucket -# based on the dspace host name -assetstore.s3.bucketName = -# Subfolder to organize assets within the bucket, in case this bucket -# is shared. Optional, default is root level of bucket -assetstore.s3.subfolder = - -# please do not use these in production but rely on the aws credentials -# discovery mechanism to configure them (ENV VAR, EC2 Iam role, etc.) -assetstore.s3.awsAccessKey = -assetstore.s3.awsSecretKey = -# to force the use of a specific region when credentials are provided -# in this configuratin file. If credentials are left empty this prop -# is ignored -assetstore.s3.awsRegionName = \ No newline at end of file From 5c9aaa0a8c38410c25c49c15c7a4c25f4bc8440a Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Wed, 9 Nov 2022 15:14:08 +0100 Subject: [PATCH 033/510] [CST-5249] Renamed qa endpoints --- .../rest/QAEventRelatedRestController.java | 9 +- .../dspace/app/rest/model/QAEventRest.java | 2 +- .../dspace/app/rest/model/QASourceRest.java | 2 +- .../dspace/app/rest/model/QATopicRest.java | 2 +- .../app/rest/QAEventRestRepositoryIT.java | 158 ++++++++++-------- .../app/rest/QASourceRestRepositoryIT.java | 20 +-- .../app/rest/QATopicRestRepositoryIT.java | 54 +++--- .../app/rest/matcher/QAEventMatcher.java | 2 +- .../app/rest/matcher/QASourceMatcher.java | 4 +- .../app/rest/matcher/QATopicMatcher.java | 4 +- 10 files changed, 140 insertions(+), 117 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/QAEventRelatedRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/QAEventRelatedRestController.java index 8716d079374f..538d99b3ceb5 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/QAEventRelatedRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/QAEventRelatedRestController.java @@ -42,12 +42,13 @@ import org.springframework.web.bind.annotation.RestController; /** - * This RestController will take care to manipulate the related item eventually associated with a qa event - * "/api/integration/qaevents/{qaeventid}/related" + * This RestController will take care to manipulate the related item eventually + * associated with a qa event + * "/api/integration/qualityassuranceevents/{qaeventid}/related" */ @RestController -@RequestMapping("/api/" + QAEventRest.CATEGORY + "/qaevents" + REGEX_REQUESTMAPPING_IDENTIFIER_AS_STRING_VERSION_STRONG - + "/related") +@RequestMapping("/api/" + QAEventRest.CATEGORY + "/qualityassuranceevents" + + REGEX_REQUESTMAPPING_IDENTIFIER_AS_STRING_VERSION_STRONG + "/related") public class QAEventRelatedRestController { @Autowired diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/QAEventRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/QAEventRest.java index e02755d2ecbf..7a12ade61d80 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/QAEventRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/QAEventRest.java @@ -26,7 +26,7 @@ public class QAEventRest extends BaseObjectRest { private static final long serialVersionUID = -5001130073350654793L; - public static final String NAME = "qaevent"; + public static final String NAME = "qualityassuranceevent"; public static final String CATEGORY = RestAddressableModel.INTEGRATION; public static final String TOPIC = "topic"; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/QASourceRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/QASourceRest.java index 15c8096e02dd..a1480f409cd7 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/QASourceRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/QASourceRest.java @@ -21,7 +21,7 @@ public class QASourceRest extends BaseObjectRest { private static final long serialVersionUID = -7455358581579629244L; - public static final String NAME = "qasource"; + public static final String NAME = "qualityassurancesource"; public static final String CATEGORY = RestAddressableModel.INTEGRATION; private String id; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/QATopicRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/QATopicRest.java index 34d5655eb76a..05820b919482 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/QATopicRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/QATopicRest.java @@ -21,7 +21,7 @@ public class QATopicRest extends BaseObjectRest { private static final long serialVersionUID = -7455358581579629244L; - public static final String NAME = "qatopic"; + public static final String NAME = "qualityassurancetopic"; public static final String CATEGORY = RestAddressableModel.INTEGRATION; private String id; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/QAEventRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/QAEventRestRepositoryIT.java index 173f233de0ad..dc021f2904a1 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/QAEventRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/QAEventRestRepositoryIT.java @@ -63,10 +63,12 @@ public class QAEventRestRepositoryIT extends AbstractControllerIntegrationTest { @Test public void findAllNotImplementedTest() throws Exception { String adminToken = getAuthToken(admin.getEmail(), password); - getClient(adminToken).perform(get("/api/integration/qaevents")).andExpect(status().isMethodNotAllowed()); + getClient(adminToken).perform(get("/api/integration/qualityassuranceevents")) + .andExpect(status().isMethodNotAllowed()); String epersonToken = getAuthToken(admin.getEmail(), password); - getClient(epersonToken).perform(get("/api/integration/qaevents")).andExpect(status().isMethodNotAllowed()); - getClient().perform(get("/api/integration/qaevents")).andExpect(status().isMethodNotAllowed()); + getClient(epersonToken).perform(get("/api/integration/qualityassuranceevents")) + .andExpect(status().isMethodNotAllowed()); + getClient().perform(get("/api/integration/qualityassuranceevents")).andExpect(status().isMethodNotAllowed()); } @Test @@ -82,9 +84,11 @@ public void findOneTest() throws Exception { .withMessage("{\"abstracts[0]\": \"Descrizione delle caratteristiche...\"}").build(); context.restoreAuthSystemState(); String authToken = getAuthToken(admin.getEmail(), password); - getClient(authToken).perform(get("/api/integration/qaevents/" + event1.getEventId())).andExpect(status().isOk()) + getClient(authToken).perform(get("/api/integration/qualityassuranceevents/" + event1.getEventId())) + .andExpect(status().isOk()) .andExpect(jsonPath("$", QAEventMatcher.matchQAEventEntry(event1))); - getClient(authToken).perform(get("/api/integration/qaevents/" + event4.getEventId())).andExpect(status().isOk()) + getClient(authToken).perform(get("/api/integration/qualityassuranceevents/" + event4.getEventId())) + .andExpect(status().isOk()) .andExpect(jsonPath("$", QAEventMatcher.matchQAEventEntry(event4))); } @@ -113,11 +117,11 @@ public void findOneWithProjectionTest() throws Exception { context.restoreAuthSystemState(); String authToken = getAuthToken(admin.getEmail(), password); getClient(authToken) - .perform(get("/api/integration/qaevents/" + event1.getEventId()).param("projection", "full")) + .perform(get("/api/integration/qualityassuranceevents/" + event1.getEventId()).param("projection", "full")) .andExpect(status().isOk()) .andExpect(jsonPath("$", QAEventMatcher.matchQAEventFullEntry(event1))); getClient(authToken) - .perform(get("/api/integration/qaevents/" + event5.getEventId()).param("projection", "full")) + .perform(get("/api/integration/qualityassuranceevents/" + event5.getEventId()).param("projection", "full")) .andExpect(status().isOk()) .andExpect(jsonPath("$", QAEventMatcher.matchQAEventFullEntry(event5))); } @@ -131,7 +135,7 @@ public void findOneUnauthorizedTest() throws Exception { .withTopic("ENRICH/MISSING/PID") .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144300\"}").build(); context.restoreAuthSystemState(); - getClient().perform(get("/api/integration/qaevents/" + event1.getEventId())) + getClient().perform(get("/api/integration/qualityassuranceevents/" + event1.getEventId())) .andExpect(status().isUnauthorized()); } @@ -145,7 +149,7 @@ public void findOneForbiddenTest() throws Exception { .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144300\"}").build(); context.restoreAuthSystemState(); String authToken = getAuthToken(eperson.getEmail(), password); - getClient(authToken).perform(get("/api/integration/qaevents/" + event1.getEventId())) + getClient(authToken).perform(get("/api/integration/qualityassuranceevents/" + event1.getEventId())) .andExpect(status().isForbidden()); } @@ -169,19 +173,22 @@ public void findByTopicTest() throws Exception { context.restoreAuthSystemState(); String authToken = getAuthToken(admin.getEmail(), password); getClient(authToken) - .perform(get("/api/integration/qaevents/search/findByTopic").param("topic", "ENRICH!MISSING!PID")) - .andExpect(status().isOk()).andExpect(jsonPath("$._embedded.qaevents", Matchers.hasSize(2))) - .andExpect(jsonPath("$._embedded.qaevents", + .perform( + get("/api/integration/qualityassuranceevents/search/findByTopic").param("topic", "ENRICH!MISSING!PID")) + .andExpect(status().isOk()).andExpect(jsonPath("$._embedded.qualityassuranceevents", Matchers.hasSize(2))) + .andExpect(jsonPath("$._embedded.qualityassuranceevents", Matchers.containsInAnyOrder(QAEventMatcher.matchQAEventEntry(event1), QAEventMatcher.matchQAEventEntry(event2)))) .andExpect(jsonPath("$.page.size", is(20))).andExpect(jsonPath("$.page.totalElements", is(2))); getClient(authToken) - .perform(get("/api/integration/qaevents/search/findByTopic").param("topic", "ENRICH!MISSING!ABSTRACT")) - .andExpect(status().isOk()).andExpect(jsonPath("$._embedded.qaevents", Matchers.hasSize(1))) - .andExpect(jsonPath("$._embedded.qaevents", + .perform(get("/api/integration/qualityassuranceevents/search/findByTopic").param("topic", + "ENRICH!MISSING!ABSTRACT")) + .andExpect(status().isOk()).andExpect(jsonPath("$._embedded.qualityassuranceevents", Matchers.hasSize(1))) + .andExpect(jsonPath("$._embedded.qualityassuranceevents", Matchers.containsInAnyOrder(QAEventMatcher.matchQAEventEntry(event4)))) .andExpect(jsonPath("$.page.size", is(20))).andExpect(jsonPath("$.page.totalElements", is(1))); - getClient(authToken).perform(get("/api/integration/qaevents/search/findByTopic").param("topic", "not-existing")) + getClient(authToken) + .perform(get("/api/integration/qualityassuranceevents/search/findByTopic").param("topic", "not-existing")) .andExpect(status().isOk()).andExpect(jsonPath("$.page.size", is(20))) .andExpect(jsonPath("$.page.totalElements", is(0))); } @@ -209,31 +216,32 @@ public void findByTopicPaginatedTest() throws Exception { context.restoreAuthSystemState(); String authToken = getAuthToken(admin.getEmail(), password); getClient(authToken) - .perform(get("/api/integration/qaevents/search/findByTopic").param("topic", "ENRICH!MISSING!PID") + .perform( + get("/api/integration/qualityassuranceevents/search/findByTopic").param("topic", "ENRICH!MISSING!PID") .param("size", "2")) - .andExpect(status().isOk()).andExpect(jsonPath("$._embedded.qaevents", Matchers.hasSize(2))) - .andExpect(jsonPath("$._embedded.qaevents", + .andExpect(status().isOk()).andExpect(jsonPath("$._embedded.qualityassuranceevents", Matchers.hasSize(2))) + .andExpect(jsonPath("$._embedded.qualityassuranceevents", Matchers.containsInAnyOrder( QAEventMatcher.matchQAEventEntry(event1), QAEventMatcher.matchQAEventEntry(event2)))) .andExpect(jsonPath("$._links.self.href", Matchers.allOf( - Matchers.containsString("/api/integration/qaevents/search/findByTopic?"), + Matchers.containsString("/api/integration/qualityassuranceevents/search/findByTopic?"), Matchers.containsString("topic=ENRICH!MISSING!PID"), Matchers.containsString("size=2")))) .andExpect(jsonPath("$._links.next.href", Matchers.allOf( - Matchers.containsString("/api/integration/qaevents/search/findByTopic?"), + Matchers.containsString("/api/integration/qualityassuranceevents/search/findByTopic?"), Matchers.containsString("topic=ENRICH!MISSING!PID"), Matchers.containsString("page=1"), Matchers.containsString("size=2")))) .andExpect(jsonPath("$._links.last.href", Matchers.allOf( - Matchers.containsString("/api/integration/qaevents/search/findByTopic?"), + Matchers.containsString("/api/integration/qualityassuranceevents/search/findByTopic?"), Matchers.containsString("topic=ENRICH!MISSING!PID"), Matchers.containsString("page=2"), Matchers.containsString("size=2")))) .andExpect(jsonPath("$._links.first.href", Matchers.allOf( - Matchers.containsString("/api/integration/qaevents/search/findByTopic?"), + Matchers.containsString("/api/integration/qualityassuranceevents/search/findByTopic?"), Matchers.containsString("topic=ENRICH!MISSING!PID"), Matchers.containsString("page=0"), Matchers.containsString("size=2")))) .andExpect(jsonPath("$._links.prev.href").doesNotExist()) @@ -242,36 +250,37 @@ public void findByTopicPaginatedTest() throws Exception { .andExpect(jsonPath("$.page.totalElements", is(5))); getClient(authToken) - .perform(get("/api/integration/qaevents/search/findByTopic").param("topic", "ENRICH!MISSING!PID") + .perform( + get("/api/integration/qualityassuranceevents/search/findByTopic").param("topic", "ENRICH!MISSING!PID") .param("size", "2").param("page", "1")) - .andExpect(status().isOk()).andExpect(jsonPath("$._embedded.qaevents", Matchers.hasSize(2))) - .andExpect(jsonPath("$._embedded.qaevents", + .andExpect(status().isOk()).andExpect(jsonPath("$._embedded.qualityassuranceevents", Matchers.hasSize(2))) + .andExpect(jsonPath("$._embedded.qualityassuranceevents", Matchers.containsInAnyOrder( QAEventMatcher.matchQAEventEntry(event3), QAEventMatcher.matchQAEventEntry(event4)))) .andExpect(jsonPath("$._links.self.href", Matchers.allOf( - Matchers.containsString("/api/integration/qaevents/search/findByTopic?"), + Matchers.containsString("/api/integration/qualityassuranceevents/search/findByTopic?"), Matchers.containsString("topic=ENRICH!MISSING!PID"), Matchers.containsString("page=1"), Matchers.containsString("size=2")))) .andExpect(jsonPath("$._links.next.href", Matchers.allOf( - Matchers.containsString("/api/integration/qaevents/search/findByTopic?"), + Matchers.containsString("/api/integration/qualityassuranceevents/search/findByTopic?"), Matchers.containsString("topic=ENRICH!MISSING!PID"), Matchers.containsString("page=2"), Matchers.containsString("size=2")))) .andExpect(jsonPath("$._links.last.href", Matchers.allOf( - Matchers.containsString("/api/integration/qaevents/search/findByTopic?"), + Matchers.containsString("/api/integration/qualityassuranceevents/search/findByTopic?"), Matchers.containsString("topic=ENRICH!MISSING!PID"), Matchers.containsString("page=2"), Matchers.containsString("size=2")))) .andExpect(jsonPath("$._links.first.href", Matchers.allOf( - Matchers.containsString("/api/integration/qaevents/search/findByTopic?"), + Matchers.containsString("/api/integration/qualityassuranceevents/search/findByTopic?"), Matchers.containsString("topic=ENRICH!MISSING!PID"), Matchers.containsString("page=0"), Matchers.containsString("size=2")))) .andExpect(jsonPath("$._links.prev.href", Matchers.allOf( - Matchers.containsString("/api/integration/qaevents/search/findByTopic?"), + Matchers.containsString("/api/integration/qualityassuranceevents/search/findByTopic?"), Matchers.containsString("topic=ENRICH!MISSING!PID"), Matchers.containsString("page=0"), Matchers.containsString("size=2")))) .andExpect(jsonPath("$.page.size", is(2))) @@ -279,31 +288,32 @@ public void findByTopicPaginatedTest() throws Exception { .andExpect(jsonPath("$.page.totalElements", is(5))); getClient(authToken) - .perform(get("/api/integration/qaevents/search/findByTopic").param("topic", "ENRICH!MISSING!PID") + .perform( + get("/api/integration/qualityassuranceevents/search/findByTopic").param("topic", "ENRICH!MISSING!PID") .param("size", "2").param("page", "2")) - .andExpect(status().isOk()).andExpect(jsonPath("$._embedded.qaevents", Matchers.hasSize(1))) - .andExpect(jsonPath("$._embedded.qaevents", + .andExpect(status().isOk()).andExpect(jsonPath("$._embedded.qualityassuranceevents", Matchers.hasSize(1))) + .andExpect(jsonPath("$._embedded.qualityassuranceevents", Matchers.containsInAnyOrder( QAEventMatcher.matchQAEventEntry(event5)))) .andExpect(jsonPath("$._links.self.href", Matchers.allOf( - Matchers.containsString("/api/integration/qaevents/search/findByTopic?"), + Matchers.containsString("/api/integration/qualityassuranceevents/search/findByTopic?"), Matchers.containsString("topic=ENRICH!MISSING!PID"), Matchers.containsString("page=2"), Matchers.containsString("size=2")))) .andExpect(jsonPath("$._links.next.href").doesNotExist()) .andExpect(jsonPath("$._links.last.href", Matchers.allOf( - Matchers.containsString("/api/integration/qaevents/search/findByTopic?"), + Matchers.containsString("/api/integration/qualityassuranceevents/search/findByTopic?"), Matchers.containsString("topic=ENRICH!MISSING!PID"), Matchers.containsString("page=2"), Matchers.containsString("size=2")))) .andExpect(jsonPath("$._links.first.href", Matchers.allOf( - Matchers.containsString("/api/integration/qaevents/search/findByTopic?"), + Matchers.containsString("/api/integration/qualityassuranceevents/search/findByTopic?"), Matchers.containsString("topic=ENRICH!MISSING!PID"), Matchers.containsString("page=0"), Matchers.containsString("size=2")))) .andExpect(jsonPath("$._links.prev.href", Matchers.allOf( - Matchers.containsString("/api/integration/qaevents/search/findByTopic?"), + Matchers.containsString("/api/integration/qualityassuranceevents/search/findByTopic?"), Matchers.containsString("topic=ENRICH!MISSING!PID"), Matchers.containsString("page=1"), Matchers.containsString("size=2")))) .andExpect(jsonPath("$.page.size", is(2))) @@ -330,7 +340,9 @@ public void findByTopicUnauthorizedTest() throws Exception { .withTopic("ENRICH/MISSING/ABSTRACT") .withMessage("{\"abstracts[0]\": \"Descrizione delle caratteristiche...\"}").build(); context.restoreAuthSystemState(); - getClient().perform(get("/api/integration/qaevents/search/findByTopic").param("topic", "ENRICH!MISSING!PID")) + getClient() + .perform( + get("/api/integration/qualityassuranceevents/search/findByTopic").param("topic", "ENRICH!MISSING!PID")) .andExpect(status().isUnauthorized()); } @@ -354,7 +366,8 @@ public void findByTopicForbiddenTest() throws Exception { context.restoreAuthSystemState(); String epersonToken = getAuthToken(eperson.getEmail(), password); getClient(epersonToken) - .perform(get("/api/integration/qaevents/search/findByTopic").param("topic", "ENRICH!MISSING!PID")) + .perform( + get("/api/integration/qualityassuranceevents/search/findByTopic").param("topic", "ENRICH!MISSING!PID")) .andExpect(status().isForbidden()); } @@ -377,7 +390,7 @@ public void findByTopicBadRequestTest() throws Exception { .withMessage("{\"abstracts[0]\": \"Descrizione delle caratteristiche...\"}").build(); context.restoreAuthSystemState(); String adminToken = getAuthToken(admin.getEmail(), password); - getClient(adminToken).perform(get("/api/integration/qaevents/search/findByTopic")) + getClient(adminToken).perform(get("/api/integration/qualityassuranceevents/search/findByTopic")) .andExpect(status().isBadRequest()); } @@ -470,32 +483,34 @@ public void recordDecisionTest() throws Exception { eventProjectNoBound.setStatus(QAEvent.ACCEPTED); eventAbstract.setStatus(QAEvent.ACCEPTED); - getClient(authToken).perform(patch("/api/integration/qaevents/" + eventMissingPID1.getEventId()) + getClient(authToken).perform(patch("/api/integration/qualityassuranceevents/" + eventMissingPID1.getEventId()) .content(patchAccept) .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) .andExpect(status().isOk()) .andExpect(jsonPath("$", QAEventMatcher.matchQAEventEntry(eventMissingPID1))); - getClient(authToken).perform(patch("/api/integration/qaevents/" + eventMorePID.getEventId()) + getClient(authToken).perform(patch("/api/integration/qualityassuranceevents/" + eventMorePID.getEventId()) .content(patchAcceptUppercase) .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) .andExpect(status().isOk()) .andExpect(jsonPath("$", QAEventMatcher.matchQAEventEntry(eventMorePID))); - getClient(authToken).perform(patch("/api/integration/qaevents/" + eventMissingUnknownPID.getEventId()) + getClient(authToken) + .perform(patch("/api/integration/qualityassuranceevents/" + eventMissingUnknownPID.getEventId()) .content(patchAccept) .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) .andExpect(status().isOk()) .andExpect(jsonPath("$", QAEventMatcher.matchQAEventEntry(eventMissingUnknownPID))); - getClient(authToken).perform(patch("/api/integration/qaevents/" + eventProjectBound.getEventId()) + getClient(authToken).perform(patch("/api/integration/qualityassuranceevents/" + eventProjectBound.getEventId()) .content(patchAccept) .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) .andExpect(status().isOk()) .andExpect(jsonPath("$", QAEventMatcher.matchQAEventEntry(eventProjectBound))); - getClient(authToken).perform(patch("/api/integration/qaevents/" + eventProjectNoBound.getEventId()) + getClient(authToken) + .perform(patch("/api/integration/qualityassuranceevents/" + eventProjectNoBound.getEventId()) .content(patchAccept) .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) .andExpect(status().isOk()) .andExpect(jsonPath("$", QAEventMatcher.matchQAEventEntry(eventProjectNoBound))); - getClient(authToken).perform(patch("/api/integration/qaevents/" + eventAbstract.getEventId()) + getClient(authToken).perform(patch("/api/integration/qualityassuranceevents/" + eventAbstract.getEventId()) .content(patchAccept) .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) .andExpect(status().isOk()) @@ -535,7 +550,7 @@ public void recordDecisionTest() throws Exception { hasJsonPath("$.metadata['dc.description.abstract'][0].value", is("An abstract to add...")))); // reject pid2 eventMissingPID2.setStatus(QAEvent.REJECTED); - getClient(authToken).perform(patch("/api/integration/qaevents/" + eventMissingPID2.getEventId()) + getClient(authToken).perform(patch("/api/integration/qualityassuranceevents/" + eventMissingPID2.getEventId()) .content(patchReject) .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) .andExpect(status().isOk()) @@ -547,7 +562,8 @@ public void recordDecisionTest() throws Exception { hasNoJsonPath("$.metadata['dc.identifier.other']"))); // discard abstractToDiscard eventAbstractToDiscard.setStatus(QAEvent.DISCARDED); - getClient(authToken).perform(patch("/api/integration/qaevents/" + eventAbstractToDiscard.getEventId()) + getClient(authToken) + .perform(patch("/api/integration/qualityassuranceevents/" + eventAbstractToDiscard.getEventId()) .content(patchDiscard) .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) .andExpect(status().isOk()) @@ -558,7 +574,7 @@ public void recordDecisionTest() throws Exception { .andExpect(jsonPath("$", hasNoJsonPath("$.metadata['dc.description.abstract']"))); // no pending qa events should be longer available - getClient(authToken).perform(get("/api/integration/qatopics")).andExpect(status().isOk()) + getClient(authToken).perform(get("/api/integration/qualityassurancetopics")).andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(status().isOk()) .andExpect(jsonPath("$.page.size", is(20))).andExpect(jsonPath("$.page.totalElements", is(0))); @@ -591,23 +607,23 @@ public void setRelatedTest() throws Exception { context.restoreAuthSystemState(); String authToken = getAuthToken(admin.getEmail(), password); getClient(authToken) - .perform(get("/api/integration/qaevents/" + event.getEventId()).param("projection", "full")) + .perform(get("/api/integration/qualityassuranceevents/" + event.getEventId()).param("projection", "full")) .andExpect(status().isOk()) .andExpect(jsonPath("$", QAEventMatcher.matchQAEventFullEntry(event))); getClient(authToken) - .perform(post("/api/integration/qaevents/" + event.getEventId() + "/related").param("item", + .perform(post("/api/integration/qualityassuranceevents/" + event.getEventId() + "/related").param("item", funding.getID().toString())) .andExpect(status().isCreated()) .andExpect(jsonPath("$", ItemMatcher.matchItemProperties(funding))); // update our local event copy to reflect the association with the related item event.setRelated(funding.getID().toString()); getClient(authToken) - .perform(get("/api/integration/qaevents/" + event.getEventId()).param("projection", "full")) + .perform(get("/api/integration/qualityassuranceevents/" + event.getEventId()).param("projection", "full")) .andExpect(status().isOk()) .andExpect(jsonPath("$", QAEventMatcher.matchQAEventFullEntry(event))); getClient(authToken) - .perform(get("/api/integration/qaevents/" + event.getEventId() + "/related")) + .perform(get("/api/integration/qualityassuranceevents/" + event.getEventId() + "/related")) .andExpect(status().isOk()) .andExpect(jsonPath("$", ItemMatcher.matchItemProperties(funding))); } @@ -639,21 +655,21 @@ public void unsetRelatedTest() throws Exception { context.restoreAuthSystemState(); String authToken = getAuthToken(admin.getEmail(), password); getClient(authToken) - .perform(get("/api/integration/qaevents/" + event.getEventId()).param("projection", "full")) + .perform(get("/api/integration/qualityassuranceevents/" + event.getEventId()).param("projection", "full")) .andExpect(status().isOk()) .andExpect(jsonPath("$", QAEventMatcher.matchQAEventFullEntry(event))); getClient(authToken) - .perform(delete("/api/integration/qaevents/" + event.getEventId() + "/related")) + .perform(delete("/api/integration/qualityassuranceevents/" + event.getEventId() + "/related")) .andExpect(status().isNoContent()); // update our local event copy to reflect the association with the related item event.setRelated(null); getClient(authToken) - .perform(get("/api/integration/qaevents/" + event.getEventId()).param("projection", "full")) + .perform(get("/api/integration/qualityassuranceevents/" + event.getEventId()).param("projection", "full")) .andExpect(status().isOk()) .andExpect(jsonPath("$", QAEventMatcher.matchQAEventFullEntry(event))); getClient(authToken) - .perform(get("/api/integration/qaevents/" + event.getEventId() + "/related")) + .perform(get("/api/integration/qualityassuranceevents/" + event.getEventId() + "/related")) .andExpect(status().isNoContent()); } @@ -672,17 +688,17 @@ public void setInvalidRelatedTest() throws Exception { context.restoreAuthSystemState(); String authToken = getAuthToken(admin.getEmail(), password); getClient(authToken) - .perform(get("/api/integration/qaevents/" + event.getEventId()).param("projection", "full")) + .perform(get("/api/integration/qualityassuranceevents/" + event.getEventId()).param("projection", "full")) .andExpect(status().isOk()) .andExpect(jsonPath("$", QAEventMatcher.matchQAEventFullEntry(event))); getClient(authToken) - .perform(post("/api/integration/qaevents/" + event.getEventId() + "/related").param("item", + .perform(post("/api/integration/qualityassuranceevents/" + event.getEventId() + "/related").param("item", funding.getID().toString())) .andExpect(status().isUnprocessableEntity()); // check that no related item has been added to our event getClient(authToken) - .perform(get("/api/integration/qaevents/" + event.getEventId()).param("projection", "full")) + .perform(get("/api/integration/qualityassuranceevents/" + event.getEventId()).param("projection", "full")) .andExpect(status().isOk()) .andExpect(jsonPath("$", QAEventMatcher.matchQAEventFullEntry(event))); } @@ -701,9 +717,10 @@ public void deleteItemWithEventTest() throws Exception { context.restoreAuthSystemState(); String authToken = getAuthToken(admin.getEmail(), password); getClient(authToken) - .perform(get("/api/integration/qaevents/search/findByTopic").param("topic", "ENRICH!MISSING!PID")) - .andExpect(status().isOk()).andExpect(jsonPath("$._embedded.qaevents", Matchers.hasSize(2))) - .andExpect(jsonPath("$._embedded.qaevents", + .perform( + get("/api/integration/qualityassuranceevents/search/findByTopic").param("topic", "ENRICH!MISSING!PID")) + .andExpect(status().isOk()).andExpect(jsonPath("$._embedded.qualityassuranceevents", Matchers.hasSize(2))) + .andExpect(jsonPath("$._embedded.qualityassuranceevents", Matchers.containsInAnyOrder(QAEventMatcher.matchQAEventEntry(event1), QAEventMatcher.matchQAEventEntry(event2)))) .andExpect(jsonPath("$.page.size", is(20))).andExpect(jsonPath("$.page.totalElements", is(2))); @@ -715,9 +732,10 @@ public void deleteItemWithEventTest() throws Exception { .andExpect(status().is(404)); getClient(authToken) - .perform(get("/api/integration/qaevents/search/findByTopic").param("topic", "ENRICH!MISSING!PID")) - .andExpect(status().isOk()).andExpect(jsonPath("$._embedded.qaevents", Matchers.hasSize(1))) - .andExpect(jsonPath("$._embedded.qaevents", + .perform( + get("/api/integration/qualityassuranceevents/search/findByTopic").param("topic", "ENRICH!MISSING!PID")) + .andExpect(status().isOk()).andExpect(jsonPath("$._embedded.qualityassuranceevents", Matchers.hasSize(1))) + .andExpect(jsonPath("$._embedded.qualityassuranceevents", Matchers.containsInAnyOrder( QAEventMatcher.matchQAEventEntry(event2)))) .andExpect(jsonPath("$.page.size", is(20))).andExpect(jsonPath("$.page.totalElements", is(1))); @@ -745,17 +763,17 @@ public void testEventDeletion() throws Exception { String authToken = getAuthToken(admin.getEmail(), password); - getClient(authToken).perform(get("/api/integration/qaevents/" + event.getEventId())) + getClient(authToken).perform(get("/api/integration/qualityassuranceevents/" + event.getEventId())) .andExpect(status().isOk()) .andExpect(jsonPath("$", matchQAEventEntry(event))); List processedEvents = qaEventsDao.findAll(context); assertThat(processedEvents, empty()); - getClient(authToken).perform(delete("/api/integration/qaevents/" + event.getEventId())) + getClient(authToken).perform(delete("/api/integration/qualityassuranceevents/" + event.getEventId())) .andExpect(status().isNoContent()); - getClient(authToken).perform(get("/api/integration/qaevents/" + event.getEventId())) + getClient(authToken).perform(get("/api/integration/qualityassuranceevents/" + event.getEventId())) .andExpect(status().isNotFound()); processedEvents = qaEventsDao.findAll(context); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/QASourceRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/QASourceRestRepositoryIT.java index 1fdcd5e0df6a..ac0ccc4ccecd 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/QASourceRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/QASourceRestRepositoryIT.java @@ -80,10 +80,10 @@ public void testFindAll() throws Exception { context.restoreAuthSystemState(); String authToken = getAuthToken(admin.getEmail(), password); - getClient(authToken).perform(get("/api/integration/qasources")) + getClient(authToken).perform(get("/api/integration/qualityassurancesources")) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) - .andExpect(jsonPath("$._embedded.qasources", contains( + .andExpect(jsonPath("$._embedded.qualityassurancesources", contains( matchQASourceEntry("openaire", 3), matchQASourceEntry("test-source", 2), matchQASourceEntry("test-source-2", 0)))) @@ -103,7 +103,7 @@ public void testFindAllForbidden() throws Exception { context.restoreAuthSystemState(); String token = getAuthToken(eperson.getEmail(), password); - getClient(token).perform(get("/api/integration/qasources")) + getClient(token).perform(get("/api/integration/qualityassurancesources")) .andExpect(status().isForbidden()); } @@ -118,7 +118,7 @@ public void testFindAllUnauthorized() throws Exception { context.restoreAuthSystemState(); - getClient().perform(get("/api/integration/qasources")) + getClient().perform(get("/api/integration/qualityassurancesources")) .andExpect(status().isUnauthorized()); } @@ -138,22 +138,22 @@ public void testFindOne() throws Exception { context.restoreAuthSystemState(); String authToken = getAuthToken(admin.getEmail(), password); - getClient(authToken).perform(get("/api/integration/qasources/openaire")) + getClient(authToken).perform(get("/api/integration/qualityassurancesources/openaire")) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$", matchQASourceEntry("openaire", 3))); - getClient(authToken).perform(get("/api/integration/qasources/test-source")) + getClient(authToken).perform(get("/api/integration/qualityassurancesources/test-source")) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$", matchQASourceEntry("test-source", 2))); - getClient(authToken).perform(get("/api/integration/qasources/test-source-2")) + getClient(authToken).perform(get("/api/integration/qualityassurancesources/test-source-2")) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$", matchQASourceEntry("test-source-2", 0))); - getClient(authToken).perform(get("/api/integration/qasources/unknown-test-source")) + getClient(authToken).perform(get("/api/integration/qualityassurancesources/unknown-test-source")) .andExpect(status().isNotFound()); } @@ -169,7 +169,7 @@ public void testFindOneForbidden() throws Exception { context.restoreAuthSystemState(); String token = getAuthToken(eperson.getEmail(), password); - getClient(token).perform(get("/api/integration/qasources/openaire")) + getClient(token).perform(get("/api/integration/qualityassurancesources/openaire")) .andExpect(status().isForbidden()); } @@ -184,7 +184,7 @@ public void testFindOneUnauthorized() throws Exception { context.restoreAuthSystemState(); - getClient().perform(get("/api/integration/qasources/openaire")) + getClient().perform(get("/api/integration/qualityassurancesources/openaire")) .andExpect(status().isUnauthorized()); } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/QATopicRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/QATopicRestRepositoryIT.java index d510d713a003..55bfd0fecaf3 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/QATopicRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/QATopicRestRepositoryIT.java @@ -59,9 +59,9 @@ public void findAllTest() throws Exception { .build(); context.restoreAuthSystemState(); String authToken = getAuthToken(admin.getEmail(), password); - getClient(authToken).perform(get("/api/integration/qatopics")).andExpect(status().isOk()) + getClient(authToken).perform(get("/api/integration/qualityassurancetopics")).andExpect(status().isOk()) .andExpect(content().contentType(contentType)) - .andExpect(jsonPath("$._embedded.qatopics", + .andExpect(jsonPath("$._embedded.qualityassurancetopics", Matchers.containsInAnyOrder(QATopicMatcher.matchQATopicEntry("ENRICH/MISSING/PID", 2), QATopicMatcher.matchQATopicEntry("ENRICH/MISSING/ABSTRACT", 1), QATopicMatcher.matchQATopicEntry("ENRICH/MORE/PID", 1)))) @@ -71,13 +71,13 @@ public void findAllTest() throws Exception { @Test public void findAllUnauthorizedTest() throws Exception { - getClient().perform(get("/api/integration/qatopics")).andExpect(status().isUnauthorized()); + getClient().perform(get("/api/integration/qualityassurancetopics")).andExpect(status().isUnauthorized()); } @Test public void findAllForbiddenTest() throws Exception { String authToken = getAuthToken(eperson.getEmail(), password); - getClient(authToken).perform(get("/api/integration/qatopics")).andExpect(status().isForbidden()); + getClient(authToken).perform(get("/api/integration/qualityassurancetopics")).andExpect(status().isForbidden()); } @Test @@ -104,16 +104,19 @@ public void findAllPaginationTest() throws Exception { .build(); context.restoreAuthSystemState(); String authToken = getAuthToken(admin.getEmail(), password); - getClient(authToken).perform(get("/api/integration/qatopics").param("size", "2")) + getClient(authToken).perform(get("/api/integration/qualityassurancetopics").param("size", "2")) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) - .andExpect(jsonPath("$._embedded.qatopics", Matchers.hasSize(2))) - .andExpect(jsonPath("$.page.size", is(2))).andExpect(jsonPath("$.page.totalElements", is(3))); - getClient(authToken).perform(get("/api/integration/qatopics").param("size", "2").param("page", "1")) - .andExpect(status().isOk()) - .andExpect(content().contentType(contentType)) - .andExpect(jsonPath("$._embedded.qatopics", Matchers.hasSize(1))) + .andExpect(jsonPath("$._embedded.qualityassurancetopics", Matchers.hasSize(2))) .andExpect(jsonPath("$.page.size", is(2))).andExpect(jsonPath("$.page.totalElements", is(3))); + getClient(authToken) + .perform(get("/api/integration/qualityassurancetopics") + .param("size", "2") + .param("page", "1")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.qualityassurancetopics", Matchers.hasSize(1))) + .andExpect(jsonPath("$.page.size", is(2))).andExpect(jsonPath("$.page.totalElements", is(3))); } @Test @@ -139,9 +142,9 @@ public void findOneTest() throws Exception { .build(); context.restoreAuthSystemState(); String authToken = getAuthToken(admin.getEmail(), password); - getClient(authToken).perform(get("/api/integration/qatopics/ENRICH!MISSING!PID")) + getClient(authToken).perform(get("/api/integration/qualityassurancetopics/ENRICH!MISSING!PID")) .andExpect(jsonPath("$", QATopicMatcher.matchQATopicEntry("ENRICH/MISSING/PID", 2))); - getClient(authToken).perform(get("/api/integration/qatopics/ENRICH!MISSING!ABSTRACT")) + getClient(authToken).perform(get("/api/integration/qualityassurancetopics/ENRICH!MISSING!ABSTRACT")) .andExpect(jsonPath("$", QATopicMatcher.matchQATopicEntry("ENRICH/MISSING/ABSTRACT", 1))); } @@ -155,8 +158,9 @@ public void findOneUnauthorizedTest() throws Exception { QAEventBuilder.createTarget(context, col1, "Science and Freedom") .withTopic("ENRICH/MISSING/PID").build(); context.restoreAuthSystemState(); - getClient().perform(get("/api/integration/qatopics/ENRICH!MISSING!PID")).andExpect(status().isUnauthorized()); - getClient().perform(get("/api/integration/qatopics/ENRICH!MISSING!ABSTRACT")) + getClient().perform(get("/api/integration/qualityassurancetopics/ENRICH!MISSING!PID")) + .andExpect(status().isUnauthorized()); + getClient().perform(get("/api/integration/qualityassurancetopics/ENRICH!MISSING!ABSTRACT")) .andExpect(status().isUnauthorized()); } @@ -171,9 +175,9 @@ public void findOneForbiddenTest() throws Exception { .withTopic("ENRICH/MISSING/PID").build(); context.restoreAuthSystemState(); String authToken = getAuthToken(eperson.getEmail(), password); - getClient(authToken).perform(get("/api/integration/qatopics/ENRICH!MISSING!PID")) + getClient(authToken).perform(get("/api/integration/qualityassurancetopics/ENRICH!MISSING!PID")) .andExpect(status().isForbidden()); - getClient(authToken).perform(get("/api/integration/qatopics/ENRICH!MISSING!ABSTRACT")) + getClient(authToken).perform(get("/api/integration/qualityassurancetopics/ENRICH!MISSING!ABSTRACT")) .andExpect(status().isForbidden()); } @@ -214,28 +218,28 @@ public void findBySourceTest() throws Exception { .build(); context.restoreAuthSystemState(); String authToken = getAuthToken(admin.getEmail(), password); - getClient(authToken).perform(get("/api/integration/qatopics/search/bySource") + getClient(authToken).perform(get("/api/integration/qualityassurancetopics/search/bySource") .param("source", "openaire")) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) - .andExpect(jsonPath("$._embedded.qatopics", + .andExpect(jsonPath("$._embedded.qualityassurancetopics", Matchers.containsInAnyOrder(QATopicMatcher.matchQATopicEntry("ENRICH/MISSING/PID", 2), QATopicMatcher.matchQATopicEntry("ENRICH/MISSING/ABSTRACT", 1), QATopicMatcher.matchQATopicEntry("ENRICH/MORE/PID", 1)))) .andExpect(jsonPath("$.page.size", is(20))).andExpect(jsonPath("$.page.totalElements", is(3))); - getClient(authToken).perform(get("/api/integration/qatopics/search/bySource") + getClient(authToken).perform(get("/api/integration/qualityassurancetopics/search/bySource") .param("source", "test-source")) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) - .andExpect(jsonPath("$._embedded.qatopics", + .andExpect(jsonPath("$._embedded.qualityassurancetopics", Matchers.containsInAnyOrder(QATopicMatcher.matchQATopicEntry("TEST/TOPIC/2", 1), QATopicMatcher.matchQATopicEntry("TEST/TOPIC", 2)))) .andExpect(jsonPath("$.page.size", is(20))).andExpect(jsonPath("$.page.totalElements", is(2))); - getClient(authToken).perform(get("/api/integration/qatopics/search/bySource") + getClient(authToken).perform(get("/api/integration/qualityassurancetopics/search/bySource") .param("source", "test-source-2")) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) - .andExpect(jsonPath("$._embedded.qatopics").doesNotExist()) + .andExpect(jsonPath("$._embedded.qualityassurancetopics").doesNotExist()) .andExpect(jsonPath("$.page.size", is(20))).andExpect(jsonPath("$.page.totalElements", is(0))); } @@ -249,7 +253,7 @@ public void findBySourceUnauthorizedTest() throws Exception { QAEventBuilder.createTarget(context, col1, "Science and Freedom") .withTopic("ENRICH/MISSING/PID").build(); context.restoreAuthSystemState(); - getClient().perform(get("/api/integration/qatopics/search/bySource") + getClient().perform(get("/api/integration/qualityassurancetopics/search/bySource") .param("source", "openaire")) .andExpect(status().isUnauthorized()); } @@ -265,7 +269,7 @@ public void findBySourceForbiddenTest() throws Exception { .withTopic("ENRICH/MISSING/PID").build(); context.restoreAuthSystemState(); String authToken = getAuthToken(eperson.getEmail(), password); - getClient(authToken).perform(get("/api/integration/qatopics/search/bySource") + getClient(authToken).perform(get("/api/integration/qualityassurancetopics/search/bySource") .param("source", "openaire")) .andExpect(status().isForbidden()); } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/QAEventMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/QAEventMatcher.java index afd2ff035820..68359023e300 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/QAEventMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/QAEventMatcher.java @@ -60,7 +60,7 @@ public static Matcher matchQAEventEntry(QAEvent event) { hasJsonPath("$._links.target.href", Matchers.endsWith(event.getEventId() + "/target")), hasJsonPath("$._links.related.href", Matchers.endsWith(event.getEventId() + "/related")), hasJsonPath("$._links.topic.href", Matchers.endsWith(event.getEventId() + "/topic")), - hasJsonPath("$.type", is("qaevent"))); + hasJsonPath("$.type", is("qualityassuranceevent"))); } catch (JsonProcessingException e) { throw new RuntimeException(e); } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/QASourceMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/QASourceMatcher.java index 0340315600f7..c0466ee40833 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/QASourceMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/QASourceMatcher.java @@ -26,7 +26,7 @@ private QASourceMatcher() { } public static Matcher matchQASourceEntry(String key, int totalEvents) { return allOf( - hasJsonPath("$.type", is("qasource")), + hasJsonPath("$.type", is("qualityassurancesource")), hasJsonPath("$.id", is(key)), hasJsonPath("$.totalEvents", is(totalEvents)) ); @@ -35,7 +35,7 @@ public static Matcher matchQASourceEntry(String key, int totalEv public static Matcher matchQASourceEntry(String key) { return allOf( - hasJsonPath("$.type", is("qasource")), + hasJsonPath("$.type", is("qualityassurancesource")), hasJsonPath("$.id", is(key)) ); } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/QATopicMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/QATopicMatcher.java index 26ef1e92e920..6428a971257d 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/QATopicMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/QATopicMatcher.java @@ -26,7 +26,7 @@ private QATopicMatcher() { } public static Matcher matchQATopicEntry(String key, int totalEvents) { return allOf( - hasJsonPath("$.type", is("qatopic")), + hasJsonPath("$.type", is("qualityassurancetopic")), hasJsonPath("$.name", is(key)), hasJsonPath("$.id", is(key.replace("/", "!"))), hasJsonPath("$.totalEvents", is(totalEvents)) @@ -36,7 +36,7 @@ public static Matcher matchQATopicEntry(String key, int totalEve public static Matcher matchQATopicEntry(String key) { return allOf( - hasJsonPath("$.type", is("qatopic")), + hasJsonPath("$.type", is("qualityassurancetopic")), hasJsonPath("$.name", is(key)), hasJsonPath("$.id", is(key.replace("/", "/"))) ); From ba0819782a3b73305b8da04fec09ee233cf98bdc Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Fri, 11 Nov 2022 16:43:05 +0100 Subject: [PATCH 034/510] [DURACOM-92] Improved S3BitStoreService using embedded S3 --- dspace-api/pom.xml | 8 + .../storage/bitstore/S3BitStoreService.java | 6 +- .../S3BitStoreServiceIntegrationTest.java | 390 +++++++++++++++++ .../bitstore/S3BitStoreServiceTest.java | 396 ------------------ 4 files changed, 400 insertions(+), 400 deletions(-) create mode 100644 dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceIntegrationTest.java delete mode 100644 dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceTest.java diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index 685005160094..cfc1953d4854 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -832,6 +832,14 @@
+ + + io.findify + s3mock_2.13 + 0.2.6 + test + + diff --git a/dspace-api/src/main/java/org/dspace/storage/bitstore/S3BitStoreService.java b/dspace-api/src/main/java/org/dspace/storage/bitstore/S3BitStoreService.java index 434fd191f843..622308b00d9f 100644 --- a/dspace-api/src/main/java/org/dspace/storage/bitstore/S3BitStoreService.java +++ b/dspace-api/src/main/java/org/dspace/storage/bitstore/S3BitStoreService.java @@ -144,13 +144,11 @@ public S3BitStoreService() {} /** * This constructor is used for test purpose. - * In this way is possible to use a mocked instance of AmazonS3 * - * @param s3Service mocked AmazonS3 service + * @param s3Service AmazonS3 service */ - protected S3BitStoreService(AmazonS3 s3Service, TransferManager tm) { + protected S3BitStoreService(AmazonS3 s3Service) { this.s3Service = s3Service; - this.tm = tm; } @Override diff --git a/dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceIntegrationTest.java b/dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceIntegrationTest.java new file mode 100644 index 000000000000..f362e94dddc7 --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceIntegrationTest.java @@ -0,0 +1,390 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.storage.bitstore; + +import static com.amazonaws.regions.Regions.DEFAULT_REGION; +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.dspace.storage.bitstore.S3BitStoreService.CSA; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.startsWith; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.sql.SQLException; + +import com.amazonaws.auth.AWSStaticCredentialsProvider; +import com.amazonaws.auth.AnonymousAWSCredentials; +import com.amazonaws.client.builder.AwsClientBuilder.EndpointConfiguration; +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.AmazonS3ClientBuilder; +import com.amazonaws.services.s3.model.AmazonS3Exception; +import com.amazonaws.services.s3.model.Bucket; +import com.amazonaws.services.s3.model.ObjectMetadata; +import io.findify.s3mock.S3Mock; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +import org.dspace.AbstractIntegrationTestWithDatabase; +import org.dspace.app.matcher.LambdaMatcher; +import org.dspace.authorize.AuthorizeException; +import org.dspace.builder.BitstreamBuilder; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.ItemBuilder; +import org.dspace.content.Bitstream; +import org.dspace.content.Collection; +import org.dspace.content.Item; +import org.dspace.core.Utils; +import org.hamcrest.Matcher; +import org.hamcrest.Matchers; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + + +/** + * @author Luca Giamminonni (luca.giamminonni at 4science.com) + */ +public class S3BitStoreServiceIntegrationTest extends AbstractIntegrationTestWithDatabase { + + private static final String DEFAULT_BUCKET_NAME = "dspace-asset-localhost"; + + private S3BitStoreService s3BitStoreService; + + private AmazonS3 amazonS3Client; + + private S3Mock s3Mock; + + private Collection collection; + + private File s3Directory; + + @Before + public void setup() throws Exception { + + s3Directory = new File(System.getProperty("java.io.tmpdir"), "s3"); + + s3Mock = S3Mock.create(8001, s3Directory.getAbsolutePath()); + s3Mock.start(); + + amazonS3Client = createAmazonS3Client(); + + s3BitStoreService = new S3BitStoreService(amazonS3Client); + + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .build(); + + collection = CollectionBuilder.createCollection(context, parentCommunity) + .build(); + + context.restoreAuthSystemState(); + } + + @After + public void cleanUp() throws IOException { + FileUtils.deleteDirectory(s3Directory); + s3Mock.shutdown(); + } + + @Test + public void testBitstreamPutAndGetWithAlreadyPresentBucket() throws IOException { + + String bucketName = "testbucket"; + + amazonS3Client.createBucket(bucketName); + + s3BitStoreService.setBucketName(bucketName); + s3BitStoreService.init(); + + assertThat(amazonS3Client.listBuckets(), contains(bucketNamed(bucketName))); + + context.turnOffAuthorisationSystem(); + String content = "Test bitstream content"; + Bitstream bitstream = createBitstream(content); + context.restoreAuthSystemState(); + + s3BitStoreService.put(bitstream, toInputStream(content)); + + String expectedChecksum = generateChecksum(content); + + assertThat(bitstream.getSizeBytes(), is((long) content.length())); + assertThat(bitstream.getChecksum(), is(expectedChecksum)); + assertThat(bitstream.getChecksumAlgorithm(), is(CSA)); + + InputStream inputStream = s3BitStoreService.get(bitstream); + assertThat(IOUtils.toString(inputStream, UTF_8), is(content)); + + String key = s3BitStoreService.getFullKey(bitstream.getInternalId()); + ObjectMetadata objectMetadata = amazonS3Client.getObjectMetadata(bucketName, key); + assertThat(objectMetadata.getContentMD5(), is(expectedChecksum)); + + } + + @Test + public void testBitstreamPutAndGetWithoutSpecifingBucket() throws IOException { + + s3BitStoreService.init(); + + assertThat(s3BitStoreService.getBucketName(), is(DEFAULT_BUCKET_NAME)); + + assertThat(amazonS3Client.listBuckets(), contains(bucketNamed(DEFAULT_BUCKET_NAME))); + + context.turnOffAuthorisationSystem(); + String content = "Test bitstream content"; + Bitstream bitstream = createBitstream(content); + context.restoreAuthSystemState(); + + s3BitStoreService.put(bitstream, toInputStream(content)); + + String expectedChecksum = generateChecksum(content); + + assertThat(bitstream.getSizeBytes(), is((long) content.length())); + assertThat(bitstream.getChecksum(), is(expectedChecksum)); + assertThat(bitstream.getChecksumAlgorithm(), is(CSA)); + + InputStream inputStream = s3BitStoreService.get(bitstream); + assertThat(IOUtils.toString(inputStream, UTF_8), is(content)); + + String key = s3BitStoreService.getFullKey(bitstream.getInternalId()); + ObjectMetadata objectMetadata = amazonS3Client.getObjectMetadata(DEFAULT_BUCKET_NAME, key); + assertThat(objectMetadata.getContentMD5(), is(expectedChecksum)); + + } + + @Test + public void testBitstreamPutAndGetWithSubFolder() throws IOException { + + s3BitStoreService.setSubfolder("test/DSpace7/"); + s3BitStoreService.init(); + + context.turnOffAuthorisationSystem(); + String content = "Test bitstream content"; + Bitstream bitstream = createBitstream(content); + context.restoreAuthSystemState(); + + s3BitStoreService.put(bitstream, toInputStream(content)); + + InputStream inputStream = s3BitStoreService.get(bitstream); + assertThat(IOUtils.toString(inputStream, UTF_8), is(content)); + + String key = s3BitStoreService.getFullKey(bitstream.getInternalId()); + assertThat(key, startsWith("test/DSpace7/")); + + ObjectMetadata objectMetadata = amazonS3Client.getObjectMetadata(DEFAULT_BUCKET_NAME, key); + assertThat(objectMetadata, notNullValue()); + + } + + @Test + public void testBitstreamDeletion() throws IOException { + + s3BitStoreService.init(); + + context.turnOffAuthorisationSystem(); + String content = "Test bitstream content"; + Bitstream bitstream = createBitstream(content); + context.restoreAuthSystemState(); + + s3BitStoreService.put(bitstream, toInputStream(content)); + + assertThat(s3BitStoreService.get(bitstream), notNullValue()); + + s3BitStoreService.remove(bitstream); + + IOException exception = assertThrows(IOException.class, () -> s3BitStoreService.get(bitstream)); + assertThat(exception.getCause(), instanceOf(AmazonS3Exception.class)); + assertThat(((AmazonS3Exception) exception.getCause()).getStatusCode(), is(404)); + + } + + @Test + public void handleRegisteredIdentifierPrefixInS3() { + String trueBitStreamId = "012345"; + String registeredBitstreamId = s3BitStoreService.REGISTERED_FLAG + trueBitStreamId; + // Should be detected as registered bitstream + assertTrue(this.s3BitStoreService.isRegisteredBitstream(registeredBitstreamId)); + } + + @Test + public void stripRegisteredBitstreamPrefixWhenCalculatingPath() { + // Set paths and IDs + String s3Path = "UNIQUE_S3_PATH/test/bitstream.pdf"; + String registeredBitstreamId = s3BitStoreService.REGISTERED_FLAG + s3Path; + // Paths should be equal, since the getRelativePath method should strip the registered -R prefix + String relativeRegisteredPath = this.s3BitStoreService.getRelativePath(registeredBitstreamId); + assertEquals(s3Path, relativeRegisteredPath); + } + + @Test + public void givenBitStreamIdentifierLongerThanPossibleWhenIntermediatePathIsComputedThenIsSplittedAndTruncated() { + String path = "01234567890123456789"; + String computedPath = this.s3BitStoreService.getIntermediatePath(path); + String expectedPath = "01" + File.separator + "23" + File.separator + "45" + File.separator; + assertThat(computedPath, equalTo(expectedPath)); + } + + @Test + public void givenBitStreamIdentifierShorterThanAFolderLengthWhenIntermediatePathIsComputedThenIsSingleFolder() { + String path = "0"; + String computedPath = this.s3BitStoreService.getIntermediatePath(path); + String expectedPath = "0" + File.separator; + assertThat(computedPath, equalTo(expectedPath)); + } + + @Test + public void givenPartialBitStreamIdentifierWhenIntermediatePathIsComputedThenIsCompletlySplitted() { + String path = "01234"; + String computedPath = this.s3BitStoreService.getIntermediatePath(path); + String expectedPath = "01" + File.separator + "23" + File.separator + "4" + File.separator; + assertThat(computedPath, equalTo(expectedPath)); + } + + @Test + public void givenMaxLengthBitStreamIdentifierWhenIntermediatePathIsComputedThenIsSplittedAllAsSubfolder() { + String path = "012345"; + String computedPath = this.s3BitStoreService.getIntermediatePath(path); + String expectedPath = "01" + File.separator + "23" + File.separator + "45" + File.separator; + assertThat(computedPath, equalTo(expectedPath)); + } + + @Test + public void givenBitStreamIdentifierWhenIntermediatePathIsComputedThenNotEndingDoubleSlash() throws IOException { + StringBuilder path = new StringBuilder("01"); + String computedPath = this.s3BitStoreService.getIntermediatePath(path.toString()); + int slashes = computeSlashes(path.toString()); + assertThat(computedPath, Matchers.endsWith(File.separator)); + assertThat(computedPath.split(File.separator).length, Matchers.equalTo(slashes)); + + path.append("2"); + computedPath = this.s3BitStoreService.getIntermediatePath(path.toString()); + assertThat(computedPath, Matchers.not(Matchers.endsWith(File.separator + File.separator))); + + path.append("3"); + computedPath = this.s3BitStoreService.getIntermediatePath(path.toString()); + assertThat(computedPath, Matchers.not(Matchers.endsWith(File.separator + File.separator))); + + path.append("4"); + computedPath = this.s3BitStoreService.getIntermediatePath(path.toString()); + assertThat(computedPath, Matchers.not(Matchers.endsWith(File.separator + File.separator))); + + path.append("56789"); + computedPath = this.s3BitStoreService.getIntermediatePath(path.toString()); + assertThat(computedPath, Matchers.not(Matchers.endsWith(File.separator + File.separator))); + } + + @Test + public void givenBitStreamIdentidierWhenIntermediatePathIsComputedThenMustBeSplitted() throws IOException { + StringBuilder path = new StringBuilder("01"); + String computedPath = this.s3BitStoreService.getIntermediatePath(path.toString()); + int slashes = computeSlashes(path.toString()); + assertThat(computedPath, Matchers.endsWith(File.separator)); + assertThat(computedPath.split(File.separator).length, Matchers.equalTo(slashes)); + + path.append("2"); + computedPath = this.s3BitStoreService.getIntermediatePath(path.toString()); + slashes = computeSlashes(path.toString()); + assertThat(computedPath, Matchers.endsWith(File.separator)); + assertThat(computedPath.split(File.separator).length, Matchers.equalTo(slashes)); + + path.append("3"); + computedPath = this.s3BitStoreService.getIntermediatePath(path.toString()); + slashes = computeSlashes(path.toString()); + assertThat(computedPath, Matchers.endsWith(File.separator)); + assertThat(computedPath.split(File.separator).length, Matchers.equalTo(slashes)); + + path.append("4"); + computedPath = this.s3BitStoreService.getIntermediatePath(path.toString()); + slashes = computeSlashes(path.toString()); + assertThat(computedPath, Matchers.endsWith(File.separator)); + assertThat(computedPath.split(File.separator).length, Matchers.equalTo(slashes)); + + path.append("56789"); + computedPath = this.s3BitStoreService.getIntermediatePath(path.toString()); + slashes = computeSlashes(path.toString()); + assertThat(computedPath, Matchers.endsWith(File.separator)); + assertThat(computedPath.split(File.separator).length, Matchers.equalTo(slashes)); + } + + @Test + public void givenBitStreamIdentifierWithSlashesWhenSanitizedThenSlashesMustBeRemoved() { + String sInternalId = new StringBuilder("01") + .append(File.separator) + .append("22") + .append(File.separator) + .append("33") + .append(File.separator) + .append("4455") + .toString(); + String computedPath = this.s3BitStoreService.sanitizeIdentifier(sInternalId); + assertThat(computedPath, Matchers.not(Matchers.startsWith(File.separator))); + assertThat(computedPath, Matchers.not(Matchers.endsWith(File.separator))); + assertThat(computedPath, Matchers.not(Matchers.containsString(File.separator))); + } + + private String generateChecksum(String content) { + try { + MessageDigest m = MessageDigest.getInstance("MD5"); + m.update(content.getBytes()); + return Utils.toHex(m.digest()); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + } + + private AmazonS3 createAmazonS3Client() { + return AmazonS3ClientBuilder.standard() + .withCredentials(new AWSStaticCredentialsProvider(new AnonymousAWSCredentials())) + .withEndpointConfiguration(new EndpointConfiguration("http://127.0.0.1:8001", DEFAULT_REGION.getName())) + .build(); + } + + private Item createItem() { + return ItemBuilder.createItem(context, collection) + .withTitle("Test item") + .build(); + } + + private Bitstream createBitstream(String content) { + try { + return BitstreamBuilder + .createBitstream(context, createItem(), toInputStream(content)) + .build(); + } catch (SQLException | AuthorizeException | IOException e) { + throw new RuntimeException(e); + } + } + + private Matcher bucketNamed(String name) { + return LambdaMatcher.matches(bucket -> bucket.getName().equals(name)); + } + + private InputStream toInputStream(String content) { + return IOUtils.toInputStream(content, UTF_8); + } + + private int computeSlashes(String internalId) { + int minimum = internalId.length(); + int slashesPerLevel = minimum / S3BitStoreService.digitsPerLevel; + int odd = Math.min(1, minimum % S3BitStoreService.digitsPerLevel); + int slashes = slashesPerLevel + odd; + return Math.min(slashes, S3BitStoreService.directoryLevels); + } + +} diff --git a/dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceTest.java b/dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceTest.java deleted file mode 100644 index 8102e9777954..000000000000 --- a/dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceTest.java +++ /dev/null @@ -1,396 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.storage.bitstore; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.isEmptyOrNullString; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.startsWith; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.when; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.Charset; -import java.util.function.Supplier; - -import com.amazonaws.regions.Regions; -import com.amazonaws.services.s3.AmazonS3; -import com.amazonaws.services.s3.AmazonS3Client; -import com.amazonaws.services.s3.model.GetObjectRequest; -import com.amazonaws.services.s3.transfer.Download; -import com.amazonaws.services.s3.transfer.TransferManager; -import com.amazonaws.services.s3.transfer.Upload; -import com.amazonaws.services.s3.transfer.model.UploadResult; -import org.apache.commons.io.FileUtils; -import org.apache.commons.io.IOUtils; -import org.dspace.AbstractUnitTest; -import org.dspace.content.Bitstream; -import org.hamcrest.Matchers; -import org.junit.Before; -import org.junit.Test; -import org.mockito.ArgumentMatchers; -import org.mockito.Mock; -import org.mockito.MockedStatic; -import org.mockito.Mockito; -import org.mockito.invocation.InvocationOnMock; - - - - -/** - * @author Vincenzo Mecca (vins01-4science - vincenzo.mecca at 4science.com) - * - */ -public class S3BitStoreServiceTest extends AbstractUnitTest { - - private S3BitStoreService s3BitStoreService; - - @Mock - private AmazonS3Client s3Service; - - @Mock - private TransferManager tm; - - @Mock - private Bitstream bitstream; - - @Mock - private Bitstream externalBitstream; - - @Before - public void setUp() throws Exception { - this.s3BitStoreService = new S3BitStoreService(s3Service, tm); - } - - private Supplier mockedServiceSupplier() { - return () -> this.s3Service; - } - - @Test - public void givenBucketWhenInitThenUsesSameBucket() throws IOException { - String bucketName = "Bucket0"; - s3BitStoreService.setBucketName(bucketName); - when(this.s3Service.doesBucketExist(bucketName)).thenReturn(false); - - assertThat(s3BitStoreService.getAwsRegionName(), isEmptyOrNullString()); - - this.s3BitStoreService.init(); - - verify(this.s3Service).doesBucketExist(bucketName); - verify(this.s3Service, Mockito.times(1)).createBucket(bucketName); - assertThat(s3BitStoreService.getAwsAccessKey(), isEmptyOrNullString()); - assertThat(s3BitStoreService.getAwsSecretKey(), isEmptyOrNullString()); - assertThat(s3BitStoreService.getAwsRegionName(), isEmptyOrNullString()); - } - - @Test - public void givenEmptyBucketWhenInitThenUsesDefaultBucket() throws IOException { - assertThat(s3BitStoreService.getBucketName(), isEmptyOrNullString()); - when(this.s3Service.doesBucketExist(startsWith(S3BitStoreService.DEFAULT_BUCKET_PREFIX))).thenReturn(false); - assertThat(s3BitStoreService.getAwsRegionName(), isEmptyOrNullString()); - - this.s3BitStoreService.init(); - - verify(this.s3Service, Mockito.times(1)).createBucket(startsWith(S3BitStoreService.DEFAULT_BUCKET_PREFIX)); - assertThat(s3BitStoreService.getBucketName(), Matchers.startsWith(S3BitStoreService.DEFAULT_BUCKET_PREFIX)); - assertThat(s3BitStoreService.getAwsAccessKey(), isEmptyOrNullString()); - assertThat(s3BitStoreService.getAwsSecretKey(), isEmptyOrNullString()); - assertThat(s3BitStoreService.getAwsRegionName(), isEmptyOrNullString()); - } - - @Test - public void givenAccessKeysWhenInitThenVerifiesCorrectBuilderCreation() throws IOException { - assertThat(s3BitStoreService.getAwsAccessKey(), isEmptyOrNullString()); - assertThat(s3BitStoreService.getAwsSecretKey(), isEmptyOrNullString()); - assertThat(s3BitStoreService.getBucketName(), isEmptyOrNullString()); - assertThat(s3BitStoreService.getAwsRegionName(), isEmptyOrNullString()); - when(this.s3Service.doesBucketExist(startsWith(S3BitStoreService.DEFAULT_BUCKET_PREFIX))).thenReturn(false); - - final String awsAccessKey = "ACCESS_KEY"; - final String awsSecretKey = "SECRET_KEY"; - - this.s3BitStoreService.setAwsAccessKey(awsAccessKey); - this.s3BitStoreService.setAwsSecretKey(awsSecretKey); - - try (MockedStatic mockedS3BitStore = Mockito.mockStatic(S3BitStoreService.class)) { - mockedS3BitStore - .when(() -> - S3BitStoreService.amazonClientBuilderBy( - ArgumentMatchers.any(Regions.class), - ArgumentMatchers.argThat( - credentials -> - awsAccessKey.equals(credentials.getAWSAccessKeyId()) && - awsSecretKey.equals(credentials.getAWSSecretKey()) - ) - ) - ) - .thenReturn(this.mockedServiceSupplier()); - - this.s3BitStoreService.init(); - - mockedS3BitStore.verify( - () -> - S3BitStoreService.amazonClientBuilderBy( - ArgumentMatchers.any(Regions.class), - ArgumentMatchers.argThat( - credentials -> - awsAccessKey.equals(credentials.getAWSAccessKeyId()) && - awsSecretKey.equals(credentials.getAWSSecretKey()) - ) - ) - ); - } - - - verify(this.s3Service, Mockito.times(1)).createBucket(startsWith(S3BitStoreService.DEFAULT_BUCKET_PREFIX)); - assertThat(s3BitStoreService.getBucketName(), Matchers.startsWith(S3BitStoreService.DEFAULT_BUCKET_PREFIX)); - assertThat(s3BitStoreService.getAwsAccessKey(), Matchers.equalTo(awsAccessKey)); - assertThat(s3BitStoreService.getAwsSecretKey(), Matchers.equalTo(awsSecretKey)); - assertThat(s3BitStoreService.getAwsRegionName(), isEmptyOrNullString()); - } - - @Test - public void givenBucketBitStreamIdInputStreamWhenRetrievingFromS3ThenUsesBucketBitStreamId() throws IOException { - String bucketName = "BucketTest"; - this.s3BitStoreService.setBucketName(bucketName); - this.s3BitStoreService.setUseRelativePath(false); - this.s3BitStoreService.init(); - - Download download = mock(Download.class); - - when(tm.download(any(GetObjectRequest.class), any(File.class))) - .thenAnswer(invocation -> writeIntoFile(download, invocation, "Test file content")); - - InputStream inputStream = this.s3BitStoreService.get(bitstream); - assertThat(IOUtils.toString(inputStream, Charset.defaultCharset()), is("Test file content")); - - } - - @Test - public void givenSubFolderWhenRequestsItemFromS3ThenTheIdentifierShouldHaveProperPath() throws IOException { - String bucketName = "BucketTest"; - String bitStreamId = "012345"; - String subfolder = "/test/DSpace7/"; - this.s3BitStoreService.setBucketName(bucketName); - this.s3BitStoreService.setUseRelativePath(false); - this.s3BitStoreService.setSubfolder(subfolder); - when(bitstream.getInternalId()).thenReturn(bitStreamId); - - Download download = mock(Download.class); - - when(tm.download(any(GetObjectRequest.class), any(File.class))) - .thenAnswer(invocation -> writeIntoFile(download, invocation, "Test file content")); - - this.s3BitStoreService.init(); - InputStream inputStream = this.s3BitStoreService.get(bitstream); - assertThat(IOUtils.toString(inputStream, Charset.defaultCharset()), is("Test file content")); - - } - - @Test - public void handleRegisteredIdentifierPrefixInS3() { - String trueBitStreamId = "012345"; - String registeredBitstreamId = s3BitStoreService.REGISTERED_FLAG + trueBitStreamId; - // Should be detected as registered bitstream - assertTrue(this.s3BitStoreService.isRegisteredBitstream(registeredBitstreamId)); - } - - @Test - public void stripRegisteredBitstreamPrefixWhenCalculatingPath() { - // Set paths and IDs - String s3Path = "UNIQUE_S3_PATH/test/bitstream.pdf"; - String registeredBitstreamId = s3BitStoreService.REGISTERED_FLAG + s3Path; - // Paths should be equal, since the getRelativePath method should strip the registered -R prefix - String relativeRegisteredPath = this.s3BitStoreService.getRelativePath(registeredBitstreamId); - assertEquals(s3Path, relativeRegisteredPath); - } - - @Test - public void givenBitStreamIdentifierLongerThanPossibleWhenIntermediatePathIsComputedThenIsSplittedAndTruncated() { - String path = "01234567890123456789"; - String computedPath = this.s3BitStoreService.getIntermediatePath(path); - String expectedPath = "01" + File.separator + "23" + File.separator + "45" + File.separator; - assertThat(computedPath, equalTo(expectedPath)); - } - - @Test - public void givenBitStreamIdentifierShorterThanAFolderLengthWhenIntermediatePathIsComputedThenIsSingleFolder() { - String path = "0"; - String computedPath = this.s3BitStoreService.getIntermediatePath(path); - String expectedPath = "0" + File.separator; - assertThat(computedPath, equalTo(expectedPath)); - } - - @Test - public void givenPartialBitStreamIdentifierWhenIntermediatePathIsComputedThenIsCompletlySplitted() { - String path = "01234"; - String computedPath = this.s3BitStoreService.getIntermediatePath(path); - String expectedPath = "01" + File.separator + "23" + File.separator + "4" + File.separator; - assertThat(computedPath, equalTo(expectedPath)); - } - - @Test - public void givenMaxLengthBitStreamIdentifierWhenIntermediatePathIsComputedThenIsSplittedAllAsSubfolder() { - String path = "012345"; - String computedPath = this.s3BitStoreService.getIntermediatePath(path); - String expectedPath = "01" + File.separator + "23" + File.separator + "45" + File.separator; - assertThat(computedPath, equalTo(expectedPath)); - } - - @Test - public void givenBitStreamIdentifierWhenIntermediatePathIsComputedThenNotEndingDoubleSlash() throws IOException { - StringBuilder path = new StringBuilder("01"); - String computedPath = this.s3BitStoreService.getIntermediatePath(path.toString()); - int slashes = computeSlashes(path.toString()); - assertThat(computedPath, Matchers.endsWith(File.separator)); - assertThat(computedPath.split(File.separator).length, Matchers.equalTo(slashes)); - - path.append("2"); - computedPath = this.s3BitStoreService.getIntermediatePath(path.toString()); - assertThat(computedPath, Matchers.not(Matchers.endsWith(File.separator + File.separator))); - - path.append("3"); - computedPath = this.s3BitStoreService.getIntermediatePath(path.toString()); - assertThat(computedPath, Matchers.not(Matchers.endsWith(File.separator + File.separator))); - - path.append("4"); - computedPath = this.s3BitStoreService.getIntermediatePath(path.toString()); - assertThat(computedPath, Matchers.not(Matchers.endsWith(File.separator + File.separator))); - - path.append("56789"); - computedPath = this.s3BitStoreService.getIntermediatePath(path.toString()); - assertThat(computedPath, Matchers.not(Matchers.endsWith(File.separator + File.separator))); - } - - @Test - public void givenBitStreamIdentidierWhenIntermediatePathIsComputedThenMustBeSplitted() throws IOException { - StringBuilder path = new StringBuilder("01"); - String computedPath = this.s3BitStoreService.getIntermediatePath(path.toString()); - int slashes = computeSlashes(path.toString()); - assertThat(computedPath, Matchers.endsWith(File.separator)); - assertThat(computedPath.split(File.separator).length, Matchers.equalTo(slashes)); - - path.append("2"); - computedPath = this.s3BitStoreService.getIntermediatePath(path.toString()); - slashes = computeSlashes(path.toString()); - assertThat(computedPath, Matchers.endsWith(File.separator)); - assertThat(computedPath.split(File.separator).length, Matchers.equalTo(slashes)); - - path.append("3"); - computedPath = this.s3BitStoreService.getIntermediatePath(path.toString()); - slashes = computeSlashes(path.toString()); - assertThat(computedPath, Matchers.endsWith(File.separator)); - assertThat(computedPath.split(File.separator).length, Matchers.equalTo(slashes)); - - path.append("4"); - computedPath = this.s3BitStoreService.getIntermediatePath(path.toString()); - slashes = computeSlashes(path.toString()); - assertThat(computedPath, Matchers.endsWith(File.separator)); - assertThat(computedPath.split(File.separator).length, Matchers.equalTo(slashes)); - - path.append("56789"); - computedPath = this.s3BitStoreService.getIntermediatePath(path.toString()); - slashes = computeSlashes(path.toString()); - assertThat(computedPath, Matchers.endsWith(File.separator)); - assertThat(computedPath.split(File.separator).length, Matchers.equalTo(slashes)); - } - - @Test - public void givenBitStreamIdentifierWithSlashesWhenSanitizedThenSlashesMustBeRemoved() { - String sInternalId = new StringBuilder("01") - .append(File.separator) - .append("22") - .append(File.separator) - .append("33") - .append(File.separator) - .append("4455") - .toString(); - String computedPath = this.s3BitStoreService.sanitizeIdentifier(sInternalId); - assertThat(computedPath, Matchers.not(Matchers.startsWith(File.separator))); - assertThat(computedPath, Matchers.not(Matchers.endsWith(File.separator))); - assertThat(computedPath, Matchers.not(Matchers.containsString(File.separator))); - } - - @Test - public void givenBitStreamWhenRemoveThenCallS3DeleteMethod() throws Exception { - String bucketName = "BucketTest"; - String bitStreamId = "BitStreamId"; - this.s3BitStoreService.setBucketName(bucketName); - this.s3BitStoreService.setUseRelativePath(false); - when(bitstream.getInternalId()).thenReturn(bitStreamId); - - this.s3BitStoreService.init(); - this.s3BitStoreService.remove(bitstream); - - verify(this.s3Service, Mockito.times(1)).deleteObject(ArgumentMatchers.eq(bucketName), - ArgumentMatchers.eq(bitStreamId)); - - } - - @Test - public void givenBitStreamWhenPutThenCallS3PutMethodAndStoresInBitStream() throws Exception { - String bucketName = "BucketTest"; - String bitStreamId = "BitStreamId"; - this.s3BitStoreService.setBucketName(bucketName); - this.s3BitStoreService.setUseRelativePath(false); - when(bitstream.getInternalId()).thenReturn(bitStreamId); - - InputStream in = IOUtils.toInputStream("Test file content", Charset.defaultCharset()); - - Upload upload = Mockito.mock(Upload.class); - UploadResult uploadResult = Mockito.mock(UploadResult.class); - when(upload.waitForUploadResult()).thenReturn(uploadResult); - - when(this.tm.upload(ArgumentMatchers.any(), ArgumentMatchers.any(), ArgumentMatchers.any())) - .thenReturn(upload); - - this.s3BitStoreService.init(); - this.s3BitStoreService.put(bitstream, in); - - verify(this.bitstream).setSizeBytes(17); - verify(this.bitstream, times(2)).getInternalId(); - verify(this.bitstream).setChecksum("ac79653edeb65ab5563585f2d5f14fe9"); - verify(this.bitstream).setChecksumAlgorithm(org.dspace.storage.bitstore.S3BitStoreService.CSA); - verify(this.tm).upload(eq(bucketName), eq(bitStreamId), any(File.class)); - - verifyNoMoreInteractions(this.bitstream, this.tm); - - } - - private Download writeIntoFile(Download download, InvocationOnMock invocation, String content) { - - File file = invocation.getArgument(1, File.class); - - try { - FileUtils.write(file, content, Charset.defaultCharset()); - } catch (IOException e) { - throw new RuntimeException(e); - } - - return download; - } - - private int computeSlashes(String internalId) { - int minimum = internalId.length(); - int slashesPerLevel = minimum / S3BitStoreService.digitsPerLevel; - int odd = Math.min(1, minimum % S3BitStoreService.digitsPerLevel); - int slashes = slashesPerLevel + odd; - return Math.min(slashes, S3BitStoreService.directoryLevels); - } - -} From 4d085503c9c910254d44de249825d3c7a4d7a2a6 Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Fri, 11 Nov 2022 17:00:17 +0100 Subject: [PATCH 035/510] [DURACOM-92] Fixed dependency convergence error --- dspace-api/pom.xml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index cfc1953d4854..e20fb22f48e7 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -838,6 +838,12 @@ s3mock_2.13 0.2.6 test + + + com.amazonawsl + aws-java-sdk-s3 + + @@ -903,6 +909,11 @@ swagger-core 1.6.2 + + org.scala-lang + scala-library + 2.13.2 + From da778f330665330378e4885175b7c0dd7256585f Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Fri, 11 Nov 2022 17:17:12 +0100 Subject: [PATCH 036/510] [DURACOM-92] Fixed dependency convergence error --- dspace-api/pom.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index e20fb22f48e7..9c873847e033 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -843,6 +843,10 @@ com.amazonawsl aws-java-sdk-s3 + + com.amazonaws + aws-java-sdk-s3 + From 8e2ada65b191d55bc86002bef10e2a4707cb4d2a Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Tue, 6 Dec 2022 12:36:34 +0100 Subject: [PATCH 037/510] 97248: Fix File info Solr plugin to allow faceting --- .../org/dspace/discovery/SolrServiceFileInfoPlugin.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) 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 52e0043ff403..c53b48f80f38 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceFileInfoPlugin.java +++ b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceFileInfoPlugin.java @@ -53,10 +53,14 @@ public void additionalIndex(Context context, IndexableObject indexableObject, So if (bitstreams != null) { for (Bitstream bitstream : bitstreams) { document.addField(SOLR_FIELD_NAME_FOR_FILENAMES, bitstream.getName()); + 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); + document.addField(SOLR_FIELD_NAME_FOR_DESCRIPTIONS + "_keyword", bitstream.getName()); + document.addField(SOLR_FIELD_NAME_FOR_DESCRIPTIONS + "_filter", bitstream.getName()); } } } @@ -65,4 +69,4 @@ public void additionalIndex(Context context, IndexableObject indexableObject, So } } } -} \ No newline at end of file +} From 3e651af7605853b013fe52607b0701f797090a28 Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Tue, 6 Dec 2022 12:37:21 +0100 Subject: [PATCH 038/510] 97248: Find DSO based configurations recursively through parent objects --- .../org/dspace/discovery/SearchUtils.java | 45 ++++++++++++----- .../DiscoveryConfigurationService.java | 49 +++++++++++++++++-- .../CollectionIndexFactoryImpl.java | 4 +- .../CommunityIndexFactoryImpl.java | 4 +- .../InprogressSubmissionIndexFactoryImpl.java | 6 +-- .../indexobject/ItemIndexFactoryImpl.java | 2 +- .../repository/DiscoveryRestRepository.java | 10 ++-- 7 files changed, 89 insertions(+), 31 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/discovery/SearchUtils.java b/dspace-api/src/main/java/org/dspace/discovery/SearchUtils.java index 90afb09eca99..83cbdeaef6be 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/SearchUtils.java +++ b/dspace-api/src/main/java/org/dspace/discovery/SearchUtils.java @@ -18,6 +18,7 @@ import org.dspace.content.DSpaceObject; import org.dspace.content.Item; import org.dspace.content.WorkspaceItem; +import org.dspace.core.Context; import org.dspace.discovery.configuration.DiscoveryConfiguration; import org.dspace.discovery.configuration.DiscoveryConfigurationService; import org.dspace.kernel.ServiceManager; @@ -60,28 +61,32 @@ public static SearchService getSearchService() { } public static DiscoveryConfiguration getDiscoveryConfiguration() { - return getDiscoveryConfiguration(null, null); + return getDiscoveryConfiguration(null, null, null); } - public static DiscoveryConfiguration getDiscoveryConfiguration(DSpaceObject dso) { - return getDiscoveryConfiguration(null, dso); + public static DiscoveryConfiguration getDiscoveryConfiguration(final Context context, + DSpaceObject dso) { + return getDiscoveryConfiguration(context, null, dso); } /** * Return the discovery configuration to use in a specific scope for the king of search identified by the prefix. A * null prefix mean the normal query, other predefined values are workspace or workflow * + * + * @param context * @param prefix * the namespace of the configuration to lookup if any * @param dso * the DSpaceObject * @return the discovery configuration for the specified scope */ - public static DiscoveryConfiguration getDiscoveryConfiguration(String prefix, DSpaceObject dso) { + public static DiscoveryConfiguration getDiscoveryConfiguration(final Context context, String prefix, + DSpaceObject dso) { if (prefix != null) { return getDiscoveryConfigurationByName(dso != null ? prefix + "." + dso.getHandle() : prefix); } else { - return getDiscoveryConfigurationByName(dso != null ? dso.getHandle() : null); + return getDiscoveryConfigurationByDSO(context, dso); } } @@ -98,6 +103,11 @@ public static DiscoveryConfiguration getDiscoveryConfigurationByName( return configurationService.getDiscoveryConfiguration(configurationName); } + public static DiscoveryConfiguration getDiscoveryConfigurationByDSO( + Context context, DSpaceObject dso) { + DiscoveryConfigurationService configurationService = getConfigurationService(); + return configurationService.getDiscoveryDSOConfiguration(context, dso); + } public static DiscoveryConfigurationService getConfigurationService() { ServiceManager manager = DSpaceServicesFactory.getInstance().getServiceManager(); @@ -114,45 +124,54 @@ public static List getIgnoredMetadataFields(int type) { * A configuration object can be returned for each parent community/collection * * @param item the DSpace item + * @param context * @return a list of configuration objects * @throws SQLException An exception that provides information on a database access error or other errors. */ - public static List getAllDiscoveryConfigurations(Item item) throws SQLException { + public static List getAllDiscoveryConfigurations(Item item, + final Context context) throws SQLException { List collections = item.getCollections(); - return getAllDiscoveryConfigurations(null, collections, item); + return getAllDiscoveryConfigurations(context, null, collections, item); } /** * Return all the discovery configuration applicable to the provided workspace item + * + * @param context * @param witem a workspace item * @return a list of discovery configuration * @throws SQLException */ - public static List getAllDiscoveryConfigurations(WorkspaceItem witem) throws SQLException { + public static List getAllDiscoveryConfigurations(final Context context, + WorkspaceItem witem) throws SQLException { List collections = new ArrayList(); collections.add(witem.getCollection()); - return getAllDiscoveryConfigurations("workspace", collections, witem.getItem()); + return getAllDiscoveryConfigurations(context, "workspace", collections, witem.getItem()); } /** * Return all the discovery configuration applicable to the provided workflow item + * + * @param context * @param witem a workflow item * @return a list of discovery configuration * @throws SQLException */ - public static List getAllDiscoveryConfigurations(WorkflowItem witem) throws SQLException { + public static List getAllDiscoveryConfigurations(final Context context, + WorkflowItem witem) throws SQLException { List collections = new ArrayList(); collections.add(witem.getCollection()); - return getAllDiscoveryConfigurations("workflow", collections, witem.getItem()); + return getAllDiscoveryConfigurations(context, "workflow", collections, witem.getItem()); } - private static List getAllDiscoveryConfigurations(String prefix, + private static List getAllDiscoveryConfigurations(final Context context, + String prefix, List collections, Item item) throws SQLException { Set result = new HashSet<>(); for (Collection collection : collections) { - DiscoveryConfiguration configuration = getDiscoveryConfiguration(prefix, collection); + DiscoveryConfiguration configuration = getDiscoveryConfiguration(context, prefix, collection); result.add(configuration); } 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 636e7ccd2ae4..b00ff7356376 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 @@ -7,12 +7,20 @@ */ package org.dspace.discovery.configuration; +import java.sql.SQLException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.dspace.content.DSpaceObject; +import org.dspace.content.factory.ContentServiceFactory; +import org.dspace.content.service.DSpaceObjectService; +import org.dspace.core.Context; +import org.dspace.core.ReloadableEntity; import org.dspace.discovery.IndexableObject; import org.dspace.discovery.indexobject.IndexableDSpaceObject; import org.dspace.services.factory.DSpaceServicesFactory; @@ -22,6 +30,8 @@ */ public class DiscoveryConfigurationService { + private static final Logger log = LogManager.getLogger(); + private Map map; private Map> toIgnoreMetadataFields = new HashMap<>(); @@ -41,25 +51,53 @@ public void setToIgnoreMetadataFields(Map> toIgnoreMetadat this.toIgnoreMetadataFields = toIgnoreMetadataFields; } - public DiscoveryConfiguration getDiscoveryConfiguration(IndexableObject dso) { + public DiscoveryConfiguration getDiscoveryConfiguration(final Context context, + IndexableObject dso) { String name; if (dso == null) { name = "default"; } else if (dso instanceof IndexableDSpaceObject) { - name = ((IndexableDSpaceObject) dso).getIndexedObject().getHandle(); + return getDiscoveryDSOConfiguration(context, ((IndexableDSpaceObject) dso).getIndexedObject()); } else { name = dso.getUniqueIndexID(); } - return getDiscoveryConfiguration(name); } + public DiscoveryConfiguration getDiscoveryDSOConfiguration(final Context context, + DSpaceObject dso) { + String name; + if (dso == null) { + name = "default"; + } else { + name = dso.getHandle(); + } + + DiscoveryConfiguration configuration = getDiscoveryConfiguration(name, false); + if (configuration != null) { + return configuration; + } + DSpaceObjectService dSpaceObjectService = + ContentServiceFactory.getInstance().getDSpaceObjectService(dso); + DSpaceObject parentObject = null; + try { + parentObject = dSpaceObjectService.getParentObject(context, dso); + } catch (SQLException e) { + log.error(e); + } + return getDiscoveryDSOConfiguration(context, parentObject); + } + public DiscoveryConfiguration getDiscoveryConfiguration(final String name) { + return getDiscoveryConfiguration(name, true); + } + + public DiscoveryConfiguration getDiscoveryConfiguration(final String name, boolean useDefault) { DiscoveryConfiguration result; result = StringUtils.isBlank(name) ? null : getMap().get(name); - if (result == null) { + if (result == null && useDefault) { //No specific configuration, get the default one result = getMap().get("default"); } @@ -68,11 +106,12 @@ public DiscoveryConfiguration getDiscoveryConfiguration(final String name) { } public DiscoveryConfiguration getDiscoveryConfigurationByNameOrDso(final String configurationName, + final Context context, final IndexableObject dso) { if (StringUtils.isNotBlank(configurationName) && getMap().containsKey(configurationName)) { return getMap().get(configurationName); } else { - return getDiscoveryConfiguration(dso); + return getDiscoveryConfiguration(context, dso); } } diff --git a/dspace-api/src/main/java/org/dspace/discovery/indexobject/CollectionIndexFactoryImpl.java b/dspace-api/src/main/java/org/dspace/discovery/indexobject/CollectionIndexFactoryImpl.java index c2bacfe5024e..817be7848df7 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/indexobject/CollectionIndexFactoryImpl.java +++ b/dspace-api/src/main/java/org/dspace/discovery/indexobject/CollectionIndexFactoryImpl.java @@ -86,7 +86,7 @@ public SolrInputDocument buildDocument(Context context, IndexableCollection inde final Collection collection = indexableCollection.getIndexedObject(); // Retrieve configuration - DiscoveryConfiguration discoveryConfiguration = SearchUtils.getDiscoveryConfiguration(collection); + DiscoveryConfiguration discoveryConfiguration = SearchUtils.getDiscoveryConfiguration(context, collection); DiscoveryHitHighlightingConfiguration highlightingConfiguration = discoveryConfiguration .getHitHighlightingConfiguration(); List highlightedMetadataFields = new ArrayList<>(); @@ -173,4 +173,4 @@ public List getCollectionLocations(Context context, Collection collectio return locations; } -} \ No newline at end of file +} diff --git a/dspace-api/src/main/java/org/dspace/discovery/indexobject/CommunityIndexFactoryImpl.java b/dspace-api/src/main/java/org/dspace/discovery/indexobject/CommunityIndexFactoryImpl.java index 8521b7dda0de..e92819601839 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/indexobject/CommunityIndexFactoryImpl.java +++ b/dspace-api/src/main/java/org/dspace/discovery/indexobject/CommunityIndexFactoryImpl.java @@ -69,7 +69,7 @@ public SolrInputDocument buildDocument(Context context, IndexableCommunity index final Community community = indexableObject.getIndexedObject(); // Retrieve configuration - DiscoveryConfiguration discoveryConfiguration = SearchUtils.getDiscoveryConfiguration(community); + DiscoveryConfiguration discoveryConfiguration = SearchUtils.getDiscoveryConfiguration(context, community); DiscoveryHitHighlightingConfiguration highlightingConfiguration = discoveryConfiguration .getHitHighlightingConfiguration(); List highlightedMetadataFields = new ArrayList<>(); @@ -135,4 +135,4 @@ public List getLocations(Context context, IndexableCommunity indexableDS return locations; } -} \ No newline at end of file +} diff --git a/dspace-api/src/main/java/org/dspace/discovery/indexobject/InprogressSubmissionIndexFactoryImpl.java b/dspace-api/src/main/java/org/dspace/discovery/indexobject/InprogressSubmissionIndexFactoryImpl.java index d0b0f363e64b..c3629b6362c3 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/indexobject/InprogressSubmissionIndexFactoryImpl.java +++ b/dspace-api/src/main/java/org/dspace/discovery/indexobject/InprogressSubmissionIndexFactoryImpl.java @@ -73,11 +73,11 @@ public void storeInprogressItemFields(Context context, SolrInputDocument doc, // Add item metadata List discoveryConfigurations; if (inProgressSubmission instanceof WorkflowItem) { - discoveryConfigurations = SearchUtils.getAllDiscoveryConfigurations((WorkflowItem) inProgressSubmission); + discoveryConfigurations = SearchUtils.getAllDiscoveryConfigurations(context, (WorkflowItem) inProgressSubmission); } else if (inProgressSubmission instanceof WorkspaceItem) { - discoveryConfigurations = SearchUtils.getAllDiscoveryConfigurations((WorkspaceItem) inProgressSubmission); + discoveryConfigurations = SearchUtils.getAllDiscoveryConfigurations(context, (WorkspaceItem) inProgressSubmission); } else { - discoveryConfigurations = SearchUtils.getAllDiscoveryConfigurations(item); + discoveryConfigurations = SearchUtils.getAllDiscoveryConfigurations(item, context); } indexableItemService.addDiscoveryFields(doc, context, item, discoveryConfigurations); indexableCollectionService.storeCommunityCollectionLocations(doc, locations); diff --git a/dspace-api/src/main/java/org/dspace/discovery/indexobject/ItemIndexFactoryImpl.java b/dspace-api/src/main/java/org/dspace/discovery/indexobject/ItemIndexFactoryImpl.java index e9f18ae949ab..b417237f763e 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/indexobject/ItemIndexFactoryImpl.java +++ b/dspace-api/src/main/java/org/dspace/discovery/indexobject/ItemIndexFactoryImpl.java @@ -147,7 +147,7 @@ public SolrInputDocument buildDocument(Context context, IndexableItem indexableI } // Add the item metadata - List discoveryConfigurations = SearchUtils.getAllDiscoveryConfigurations(item); + List discoveryConfigurations = SearchUtils.getAllDiscoveryConfigurations(item, context); addDiscoveryFields(doc, context, indexableItem.getIndexedObject(), discoveryConfigurations); //mandatory facet to show status on mydspace diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/DiscoveryRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/DiscoveryRestRepository.java index 52224ef57987..1962d44162e0 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/DiscoveryRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/DiscoveryRestRepository.java @@ -84,7 +84,7 @@ public SearchConfigurationRest getSearchConfiguration(final String dsoScope, fin IndexableObject scopeObject = scopeResolver.resolveScope(context, dsoScope); DiscoveryConfiguration discoveryConfiguration = searchConfigurationService - .getDiscoveryConfigurationByNameOrDso(configuration, scopeObject); + .getDiscoveryConfigurationByNameOrDso(configuration, context, scopeObject); return discoverConfigurationConverter.convert(discoveryConfiguration, utils.obtainProjection()); } @@ -96,7 +96,7 @@ public SearchResultsRest getSearchObjects(final String query, final List Context context = obtainContext(); IndexableObject scopeObject = scopeResolver.resolveScope(context, dsoScope); DiscoveryConfiguration discoveryConfiguration = searchConfigurationService - .getDiscoveryConfigurationByNameOrDso(configuration, scopeObject); + .getDiscoveryConfigurationByNameOrDso(configuration, context, scopeObject); DiscoverResult searchResult = null; DiscoverQuery discoverQuery = null; @@ -121,7 +121,7 @@ public FacetConfigurationRest getFacetsConfiguration(final String dsoScope, fina IndexableObject scopeObject = scopeResolver.resolveScope(context, dsoScope); DiscoveryConfiguration discoveryConfiguration = searchConfigurationService - .getDiscoveryConfigurationByNameOrDso(configuration, scopeObject); + .getDiscoveryConfigurationByNameOrDso(configuration, context, scopeObject); return discoverFacetConfigurationConverter.convert(configuration, dsoScope, discoveryConfiguration); } @@ -138,7 +138,7 @@ public FacetResultsRest getFacetObjects(String facetName, String prefix, String IndexableObject scopeObject = scopeResolver.resolveScope(context, dsoScope); DiscoveryConfiguration discoveryConfiguration = searchConfigurationService - .getDiscoveryConfigurationByNameOrDso(configuration, scopeObject); + .getDiscoveryConfigurationByNameOrDso(configuration, context, scopeObject); DiscoverQuery discoverQuery = queryBuilder.buildFacetQuery(context, scopeObject, discoveryConfiguration, prefix, query, searchFilters, dsoTypes, page, facetName); @@ -157,7 +157,7 @@ public SearchResultsRest getAllFacets(String query, List dsoTypes, Strin Pageable page = PageRequest.of(1, 1); IndexableObject scopeObject = scopeResolver.resolveScope(context, dsoScope); DiscoveryConfiguration discoveryConfiguration = searchConfigurationService - .getDiscoveryConfigurationByNameOrDso(configuration, scopeObject); + .getDiscoveryConfigurationByNameOrDso(configuration, context, scopeObject); DiscoverResult searchResult = null; DiscoverQuery discoverQuery = null; From 82bc777e45dce2525e2754fc338d27e7630bad1d Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Tue, 13 Dec 2022 12:32:15 +0100 Subject: [PATCH 039/510] Fix issue with indexing and add tests --- .../org/dspace/discovery/SearchUtils.java | 32 +- .../discovery/SolrServiceFileInfoPlugin.java | 6 +- .../DiscoveryConfigurationService.java | 9 +- .../InprogressSubmissionIndexFactoryImpl.java | 6 +- .../org/dspace/builder/CommunityBuilder.java | 24 +- .../config/spring/api/discovery.xml | 3198 +++++++++++++++++ .../DiscoveryScopeBasedRestControllerIT.java | 595 +++ .../app/rest/matcher/FacetEntryMatcher.java | 11 + .../app/rest/matcher/FacetValueMatcher.java | 10 + machine.cfg | 19 + 10 files changed, 3889 insertions(+), 21 deletions(-) create mode 100644 dspace-server-webapp/src/test/data/dspaceFolder/config/spring/api/discovery.xml create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryScopeBasedRestControllerIT.java create mode 100644 machine.cfg diff --git a/dspace-api/src/main/java/org/dspace/discovery/SearchUtils.java b/dspace-api/src/main/java/org/dspace/discovery/SearchUtils.java index 83cbdeaef6be..4085e1bbdf37 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/SearchUtils.java +++ b/dspace-api/src/main/java/org/dspace/discovery/SearchUtils.java @@ -18,6 +18,8 @@ import org.dspace.content.DSpaceObject; import org.dspace.content.Item; import org.dspace.content.WorkspaceItem; +import org.dspace.content.factory.ContentServiceFactory; +import org.dspace.content.service.DSpaceObjectService; import org.dspace.core.Context; import org.dspace.discovery.configuration.DiscoveryConfiguration; import org.dspace.discovery.configuration.DiscoveryConfigurationService; @@ -72,7 +74,7 @@ public static DiscoveryConfiguration getDiscoveryConfiguration(final Context con /** * Return the discovery configuration to use in a specific scope for the king of search identified by the prefix. A * null prefix mean the normal query, other predefined values are workspace or workflow - * + * * * @param context * @param prefix @@ -90,9 +92,28 @@ public static DiscoveryConfiguration getDiscoveryConfiguration(final Context con } } + public static Set addDiscoveryConfigurationForParents( + Context context, Set configurations, String prefix, DSpaceObject dso) + throws SQLException { + if (dso == null) { + configurations.add(getDiscoveryConfigurationByName(null)); + return configurations; + } + if (prefix != null) { + configurations.add(getDiscoveryConfigurationByName(prefix + "." + dso.getHandle())); + } else { + configurations.add(getDiscoveryConfigurationByName(dso.getHandle())); + } + + DSpaceObjectService dSpaceObjectService = ContentServiceFactory.getInstance() + .getDSpaceObjectService(dso); + DSpaceObject parentObject = dSpaceObjectService.getParentObject(context, dso); + return addDiscoveryConfigurationForParents(context, configurations, prefix, parentObject); + } + /** * Return the discovery configuration identified by the specified name - * + * * @param configurationName the configuration name assigned to the bean in the * discovery.xml * @return the discovery configuration @@ -128,8 +149,8 @@ public static List getIgnoredMetadataFields(int type) { * @return a list of configuration objects * @throws SQLException An exception that provides information on a database access error or other errors. */ - public static List getAllDiscoveryConfigurations(Item item, - final Context context) throws SQLException { + public static List getAllDiscoveryConfigurations(Item item, Context context) + throws SQLException { List collections = item.getCollections(); return getAllDiscoveryConfigurations(context, null, collections, item); } @@ -171,8 +192,7 @@ private static List getAllDiscoveryConfigurations(final Set result = new HashSet<>(); for (Collection collection : collections) { - DiscoveryConfiguration configuration = getDiscoveryConfiguration(context, prefix, collection); - result.add(configuration); + addDiscoveryConfigurationForParents(context, result, prefix, collection); } //Add alwaysIndex configurations 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 c53b48f80f38..6bda2fc52d84 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceFileInfoPlugin.java +++ b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceFileInfoPlugin.java @@ -59,8 +59,10 @@ public void additionalIndex(Context context, IndexableObject indexableObject, So String description = bitstream.getDescription(); if ((description != null) && !description.isEmpty()) { document.addField(SOLR_FIELD_NAME_FOR_DESCRIPTIONS, description); - document.addField(SOLR_FIELD_NAME_FOR_DESCRIPTIONS + "_keyword", bitstream.getName()); - document.addField(SOLR_FIELD_NAME_FOR_DESCRIPTIONS + "_filter", bitstream.getName()); + document.addField(SOLR_FIELD_NAME_FOR_DESCRIPTIONS + "_keyword", + bitstream.getName()); + document.addField(SOLR_FIELD_NAME_FOR_DESCRIPTIONS + "_filter", + bitstream.getName()); } } } 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 b00ff7356376..22443aec22e7 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 @@ -20,7 +20,6 @@ import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.DSpaceObjectService; import org.dspace.core.Context; -import org.dspace.core.ReloadableEntity; import org.dspace.discovery.IndexableObject; import org.dspace.discovery.indexobject.IndexableDSpaceObject; import org.dspace.services.factory.DSpaceServicesFactory; @@ -135,9 +134,9 @@ public static void main(String[] args) { System.out.println(DSpaceServicesFactory.getInstance().getServiceManager().getServicesNames().size()); DiscoveryConfigurationService mainService = DSpaceServicesFactory.getInstance().getServiceManager() .getServiceByName( - DiscoveryConfigurationService.class - .getName(), - DiscoveryConfigurationService.class); + DiscoveryConfigurationService.class + .getName(), + DiscoveryConfigurationService.class); for (String key : mainService.getMap().keySet()) { System.out.println(key); @@ -165,7 +164,7 @@ public static void main(String[] args) { System.out.println("Recent submissions configuration:"); DiscoveryRecentSubmissionsConfiguration recentSubmissionConfiguration = discoveryConfiguration - .getRecentSubmissionConfiguration(); + .getRecentSubmissionConfiguration(); System.out.println("\tMetadata sort field: " + recentSubmissionConfiguration.getMetadataSortField()); System.out.println("\tMax recent submissions: " + recentSubmissionConfiguration.getMax()); diff --git a/dspace-api/src/main/java/org/dspace/discovery/indexobject/InprogressSubmissionIndexFactoryImpl.java b/dspace-api/src/main/java/org/dspace/discovery/indexobject/InprogressSubmissionIndexFactoryImpl.java index c3629b6362c3..ebedfc34b743 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/indexobject/InprogressSubmissionIndexFactoryImpl.java +++ b/dspace-api/src/main/java/org/dspace/discovery/indexobject/InprogressSubmissionIndexFactoryImpl.java @@ -73,9 +73,11 @@ public void storeInprogressItemFields(Context context, SolrInputDocument doc, // Add item metadata List discoveryConfigurations; if (inProgressSubmission instanceof WorkflowItem) { - discoveryConfigurations = SearchUtils.getAllDiscoveryConfigurations(context, (WorkflowItem) inProgressSubmission); + discoveryConfigurations = SearchUtils.getAllDiscoveryConfigurations(context, + (WorkflowItem) inProgressSubmission); } else if (inProgressSubmission instanceof WorkspaceItem) { - discoveryConfigurations = SearchUtils.getAllDiscoveryConfigurations(context, (WorkspaceItem) inProgressSubmission); + discoveryConfigurations = SearchUtils.getAllDiscoveryConfigurations(context, + (WorkspaceItem) inProgressSubmission); } else { discoveryConfigurations = SearchUtils.getAllDiscoveryConfigurations(item, context); } diff --git a/dspace-api/src/test/java/org/dspace/builder/CommunityBuilder.java b/dspace-api/src/test/java/org/dspace/builder/CommunityBuilder.java index 5ba36af8f4a3..1f0e8fbd6617 100644 --- a/dspace-api/src/test/java/org/dspace/builder/CommunityBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/CommunityBuilder.java @@ -32,27 +32,38 @@ public class CommunityBuilder extends AbstractDSpaceObjectBuilder { private Community community; + protected CommunityBuilder(Context context) { super(context); } public static CommunityBuilder createCommunity(final Context context) { CommunityBuilder builder = new CommunityBuilder(context); - return builder.create(); + return builder.create(null); + } + public static CommunityBuilder createCommunity(final Context context, String handle) { + CommunityBuilder builder = new CommunityBuilder(context); + return builder.create(handle); } - private CommunityBuilder create() { - return createSubCommunity(context, null); + private CommunityBuilder create(String handle) { + return createSubCommunity(context, null, handle); } public static CommunityBuilder createSubCommunity(final Context context, final Community parent) { CommunityBuilder builder = new CommunityBuilder(context); - return builder.createSub(parent); + return builder.createSub(parent, null); } - private CommunityBuilder createSub(final Community parent) { + public static CommunityBuilder createSubCommunity(final Context context, final Community parent, + final String handle) { + CommunityBuilder builder = new CommunityBuilder(context); + return builder.createSub(parent, handle); + } + + private CommunityBuilder createSub(final Community parent, String handle) { try { - community = communityService.create(parent, context); + community = communityService.create(parent, context, handle); } catch (Exception e) { e.printStackTrace(); return null; @@ -102,6 +113,7 @@ public CommunityBuilder addParentCommunity(final Context context, final Communit @Override public Community build() { try { + communityService.update(context, community); context.dispatchEvents(); diff --git a/dspace-server-webapp/src/test/data/dspaceFolder/config/spring/api/discovery.xml b/dspace-server-webapp/src/test/data/dspaceFolder/config/spring/api/discovery.xml new file mode 100644 index 000000000000..6ffcbe661c87 --- /dev/null +++ b/dspace-server-webapp/src/test/data/dspaceFolder/config/spring/api/discovery.xml @@ -0,0 +1,3198 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + dc.rights + + + + + + + + + + + + + + + dc.rights + + + + + + + + dc.description.provenance + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + search.resourcetype:Item OR search.resourcetype:Collection OR search.resourcetype:Community + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + dc.title + dc.contributor.author + dc.creator + dc.subject + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + search.resourcetype:Item + + withdrawn:true OR discoverable:false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + dc.title + dc.contributor.author + dc.creator + dc.subject + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + search.resourcetype:Item + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + dc.title + dc.contributor.author + dc.creator + dc.subject + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + search.resourcetype:Item OR search.resourcetype:Collection OR search.resourcetype:Community + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + search.resourcetype:Item OR search.resourcetype:WorkspaceItem OR search.resourcetype:XmlWorkflowItem + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + search.resourcetype:PoolTask OR search.resourcetype:ClaimedTask + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + search.resourcetype:XmlWorkflowItem + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + search.resourcetype:Item AND entityType_keyword:Publication + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + search.resourcetype:Item AND entityType_keyword:Person + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + search.resourcetype:Item AND entityType_keyword:Project + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + search.resourcetype:Item AND entityType_keyword:OrgUnit + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + search.resourcetype:Item AND entityType_keyword:JournalIssue + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + search.resourcetype:Item AND entityType_keyword:JournalVolume + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + search.resourcetype:Item AND entityType_keyword:Journal + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + search.resourcetype:Item AND (entityType_keyword:OrgUnit OR entityType_keyword:Person) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + search.resourcetype:Item AND entityType_keyword:OrgUnit AND dc.type:FundingOrganization + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + search.resourcetype:Item OR search.resourcetype:Collection OR search.resourcetype:Community + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + dc.title + dc.contributor.author + dc.creator + dc.subject + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + search.resourcetype:Item OR search.resourcetype:Collection OR search.resourcetype:Community + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + dc.title + dc.contributor.author + dc.creator + dc.subject + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + search.resourcetype:Item OR search.resourcetype:Collection OR search.resourcetype:Community + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + dc.title + dc.contributor.author + dc.creator + dc.subject + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + search.resourcetype:Item OR search.resourcetype:Collection OR search.resourcetype:Community + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + dc.title + dc.contributor.author + dc.creator + dc.subject + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + search.resourcetype:Item OR search.resourcetype:Collection OR search.resourcetype:Community + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + dc.title + dc.contributor.author + dc.creator + dc.subject + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + search.resourcetype:Item OR search.resourcetype:Collection OR search.resourcetype:Community + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + dc.title + dc.contributor.author + dc.creator + dc.subject + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + search.resourcetype:Item OR search.resourcetype:Collection OR search.resourcetype:Community + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + dc.title + dc.contributor.author + dc.creator + dc.subject + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + dc.title + + + + + + + + + + + relation.isAuthorOfPublication + + + + + + + + + + + relation.isProjectOfPublication + + + + + + + + + + + + relation.isOrgUnitOfPublication + + + + + + + + + + + relation.isPublicationOfJournalIssue + + + + + + + + + + + relation.isJournalOfPublication + + + + + + + + + + + dc.contributor.author + dc.creator + + + + + + + + + + + + + + + dspace.entity.type + + + + + + + + + + + + + + dc.subject.* + + + + + + + + + + + + + + dc.date.issued + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + dc.type + + + + + + + + + dc.identifier + + + + + + + + + placeholder.placeholder.placeholder + + + + + + + + + + placeholder.placeholder.placeholder + + + + + + + + + person.jobTitle + + + + + + + + + + + + + + + person.knowsLanguage + + + + + + + + + + + + + person.birthDate + + + + + + + + + + + + + + + + + person.familyName + + + + + + + + + + + person.givenName + + + + + + + + + + + relation.isOrgUnitOfPerson + + + + + + + + + + + relation.isProjectOfPerson + + + + + + + + + + + relation.isPublicationOfAuthor + + + + + + + + + + + + organization.address.addressCountry + + + + + + + + + + + + + + + organization.address.addressLocality + + + + + + + + + + + + + + + organization.foundingDate + + + + + + + + + + + + + + + + organization.legalName + + + + + + + + + + + relation.isPersonOfOrgUnit + + + + + + + + + + + relation.isProjectOfOrgUnit + + + + + + + + + + + relation.isPublicationOfOrgUnit + + + + + + + + + + + creativework.keywords + + + + + + + + + + + + + + + creativework.datePublished + + + + + + + + + + + + + + + + publicationissue.issueNumber + + + + + + + + + + + relation.isPublicationOfJournalIssue + + + + + + + + + + + publicationVolume.volumeNumber + + + + + + + + + + + relation.isIssueOfJournalVolume + + + + + + + + + + + relation.isJournalOfVolume + + + + + + + + + + + creativework.publisher + + + + + + + + + + + + + + + creativework.editor + + + + + + + + + + + + + + + relation.isVolumeOfJournal + + + + + + + + + + + + + + placeholder.placeholder.placeholder + + + + + + + + + + relation.isOrgUnitOfProject + + + + + + + + + + + + relation.isPersonOfProject + + + + + + + + + + + + relation.isPublicationOfProject + + + + + + + + + + + relation.isContributorOfPublication + + + + + + + + + + + relation.isPublicationOfContributor + + + + + + + + + + + relation.isFundingAgencyOfProject + + + + + + + + + + + relation.isProjectOfFundingAgency + + + + + + + + + + + dc.test.parentcommunity1field + + + + + + + + + + + + + + + dc.test.subcommunity11field + + + + + + + + + + + + + + + dc.test.collection111field + + + + + + + + + + + + + + + dc.test.collection121field + + + + + + + + + + + + + + + dc.test.subcommunity21field + + + + + + + + + + + + + + dc.test.collection211field + + + + + + + + + + + + + + dc.test.collection221field + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryScopeBasedRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryScopeBasedRestControllerIT.java new file mode 100644 index 000000000000..a0edf1a0c70a --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryScopeBasedRestControllerIT.java @@ -0,0 +1,595 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest; + +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.is; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import org.dspace.app.rest.matcher.FacetEntryMatcher; +import org.dspace.app.rest.matcher.FacetValueMatcher; +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.ItemBuilder; +import org.dspace.builder.MetadataFieldBuilder; +import org.dspace.content.Collection; +import org.dspace.content.Community; +import org.dspace.content.Item; +import org.dspace.content.service.CollectionService; +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +public class DiscoveryScopeBasedRestControllerIT extends AbstractControllerIntegrationTest { + + @Autowired + CollectionService collectionService; + + private Community community1; + private Community subcommunity11; + private Community subcommunity12; + private Collection collection111; + private Collection collection112; + private Collection collection121; + private Collection collection122; + + private Community community2; + private Community subcommunity21; + private Community subcommunity22; + private Collection collection211; + private Collection collection212; + private Collection collection221; + private Collection collection222; + + @Before + public void setUp() throws Exception { + super.setUp(); + + context.turnOffAuthorisationSystem(); + + MetadataFieldBuilder.createMetadataField(context, "test", "parentcommunity1field", "").build(); + MetadataFieldBuilder.createMetadataField(context, "test", "subcommunity11field", "").build(); + MetadataFieldBuilder.createMetadataField(context, "test", "collection111field", "").build(); + MetadataFieldBuilder.createMetadataField(context, "test", "collection121field", "").build(); + MetadataFieldBuilder.createMetadataField(context, "test", "subcommunity21field", "").build(); + MetadataFieldBuilder.createMetadataField(context, "test", "collection211field", "").build(); + MetadataFieldBuilder.createMetadataField(context, "test", "collection221field", "").build(); + + community1 = CommunityBuilder.createCommunity(context, "123456789/discovery-parent-community-1") + .build(); + subcommunity11 = CommunityBuilder + .createSubCommunity(context, community1, "123456789/discovery-sub-community-1-1") + .build(); + subcommunity12 = CommunityBuilder + .createSubCommunity(context, community1, "123456789/discovery-sub-community-1-2") + .build(); + collection111 = CollectionBuilder + .createCollection(context, subcommunity11, "123456789/discovery-collection-1-1-1") + .build(); + collection112 = CollectionBuilder + .createCollection(context, subcommunity11, "123456789/discovery-collection-1-1-2") + .build(); + collection121 = CollectionBuilder + .createCollection(context, subcommunity12, "123456789/discovery-collection-1-2-1") + .build(); + + collection122 = CollectionBuilder + .createCollection(context, subcommunity12, "123456789/discovery-collection-1-2-2") + .build(); + + community2 = CommunityBuilder.createCommunity(context, "123456789/discovery-parent-community-2") + .build(); + + + subcommunity21 = CommunityBuilder + .createSubCommunity(context, community2, "123456789/discovery-sub-community-2-1") + .build(); + subcommunity22 = CommunityBuilder + .createSubCommunity(context, community2, "123456789/discovery-sub-community-2-2") + .build(); + collection211 = CollectionBuilder + .createCollection(context, subcommunity21, "123456789/discovery-collection-2-1-1") + .build(); + collection212 = CollectionBuilder + .createCollection(context, subcommunity21, "123456789/discovery-collection-2-1-2") + .build(); + collection221 = CollectionBuilder + .createCollection(context, subcommunity22, "123456789/discovery-collection-2-2-1") + .build(); + collection222 = CollectionBuilder + .createCollection(context, subcommunity22, "123456789/discovery-collection-2-2-2") + .build(); + + + Item item111 = ItemBuilder.createItem(context, collection111) + .withMetadata("dc", "contributor", "author", "author-item111") + .withMetadata("dc", "test", "parentcommunity1field", "parentcommunity1field-item111") + .withMetadata("dc", "test", "subcommunity11field", "subcommunity11field-item111") + .withMetadata("dc", "test", "collection111field", "collection111field-item111") + .withMetadata("dc", "test", "collection121field", "collection121field-item111") + .withMetadata("dc", "test", "subcommunity21field", "subcommunity21field-item111") + .withMetadata("dc", "test", "collection211field", "collection211field-item111") + .withMetadata("dc", "test", "collection221field", "collection221field-item111") + .build(); + + Item item112 = ItemBuilder.createItem(context, collection112) + .withMetadata("dc", "contributor", "author", "author-item112") + .withMetadata("dc", "test", "parentcommunity1field", "parentcommunity1field-item112") + .withMetadata("dc", "test", "subcommunity11field", "subcommunity11field-item112") + .withMetadata("dc", "test", "collection111field", "collection111field-item112") + .withMetadata("dc", "test", "collection121field", "collection121field-item112") + .withMetadata("dc", "test", "subcommunity21field", "subcommunity21field-item112") + .withMetadata("dc", "test", "collection211field", "collection211field-item112") + .withMetadata("dc", "test", "collection221field", "collection221field-item112") + .build(); + + Item item121 = ItemBuilder.createItem(context, collection121) + .withMetadata("dc", "contributor", "author", "author-item121") + .withMetadata("dc", "test", "parentcommunity1field", "parentcommunity1field-item121") + .withMetadata("dc", "test", "subcommunity11field", "subcommunity11field-item121") + .withMetadata("dc", "test", "collection111field", "collection111field-item121") + .withMetadata("dc", "test", "collection121field", "collection121field-item121") + .withMetadata("dc", "test", "subcommunity21field", "subcommunity21field-item121") + .withMetadata("dc", "test", "collection211field", "collection211field-item121") + .withMetadata("dc", "test", "collection221field", "collection221field-item121") + .build(); + + Item item122 = ItemBuilder.createItem(context, collection122) + .withMetadata("dc", "contributor", "author", "author-item122") + .withMetadata("dc", "test", "parentcommunity1field", "parentcommunity1field-item122") + .withMetadata("dc", "test", "subcommunity11field", "subcommunity11field-item122") + .withMetadata("dc", "test", "collection111field", "collection111field-item122") + .withMetadata("dc", "test", "collection121field", "collection121field-item122") + .withMetadata("dc", "test", "subcommunity21field", "subcommunity21field-item122") + .withMetadata("dc", "test", "collection211field", "collection211field-item122") + .withMetadata("dc", "test", "collection221field", "collection221field-item122") + .build(); + + Item item211 = ItemBuilder.createItem(context, collection211) + .withMetadata("dc", "contributor", "author", "author-item211") + .withMetadata("dc", "test", "parentcommunity1field", "parentcommunity1field-item211") + .withMetadata("dc", "test", "subcommunity11field", "subcommunity11field-item211") + .withMetadata("dc", "test", "collection111field", "collection111field-item211") + .withMetadata("dc", "test", "collection121field", "collection121field-item211") + .withMetadata("dc", "test", "subcommunity21field", "subcommunity21field-item211") + .withMetadata("dc", "test", "collection211field", "collection211field-item211") + .withMetadata("dc", "test", "collection221field", "collection221field-item211") + .build(); + + Item item212 = ItemBuilder.createItem(context, collection212) + .withMetadata("dc", "contributor", "author", "author-item212") + .withMetadata("dc", "test", "parentcommunity1field", "parentcommunity1field-item212") + .withMetadata("dc", "test", "subcommunity11field", "subcommunity11field-item212") + .withMetadata("dc", "test", "collection111field", "collection111field-item212") + .withMetadata("dc", "test", "collection121field", "collection121field-item212") + .withMetadata("dc", "test", "subcommunity21field", "subcommunity21field-item212") + .withMetadata("dc", "test", "collection211field", "collection211field-item212") + .withMetadata("dc", "test", "collection221field", "collection221field-item212") + .build(); + + Item item221 = ItemBuilder.createItem(context, collection221) + .withMetadata("dc", "contributor", "author", "author-item221") + .withMetadata("dc", "test", "parentcommunity1field", "parentcommunity1field-item221") + .withMetadata("dc", "test", "subcommunity11field", "subcommunity11field-item221") + .withMetadata("dc", "test", "collection111field", "collection111field-item221") + .withMetadata("dc", "test", "collection121field", "collection121field-item221") + .withMetadata("dc", "test", "subcommunity21field", "subcommunity21field-item221") + .withMetadata("dc", "test", "collection211field", "collection211field-item221") + .withMetadata("dc", "test", "collection221field", "collection221field-item221") + .build(); + + Item item222 = ItemBuilder.createItem(context, collection222) + .withMetadata("dc", "contributor", "author", "author-item222") + .withMetadata("dc", "test", "parentcommunity1field", "parentcommunity1field-item222") + .withMetadata("dc", "test", "subcommunity11field", "subcommunity11field-item222") + .withMetadata("dc", "test", "collection111field", "collection111field-item222") + .withMetadata("dc", "test", "collection121field", "collection121field-item222") + .withMetadata("dc", "test", "subcommunity21field", "subcommunity21field-item222") + .withMetadata("dc", "test", "collection211field", "collection211field-item222") + .withMetadata("dc", "test", "collection221field", "collection221field-item222") + .build(); + + Item mappedItem111222 = ItemBuilder + .createItem(context, collection111) + .withMetadata("dc", "contributor", "author", "author-mappedItem111222") + .withMetadata("dc", "test", "parentcommunity1field", "parentcommunity1field-mappedItem111222") + .withMetadata("dc", "test", "subcommunity11field", "subcommunity11field-mappedItem111222") + .withMetadata("dc", "test", "collection111field", "collection111field-mappedItem111222") + .withMetadata("dc", "test", "collection121field", "collection121field-mappedItem111222") + .withMetadata("dc", "test", "subcommunity21field", "subcommunity21field-mappedItem111222") + .withMetadata("dc", "test", "collection211field", "collection211field-mappedItem111222") + .withMetadata("dc", "test", "collection221field", "collection221field-mappedItem111222") + .build(); + + + Item mappedItem122211 = ItemBuilder + .createItem(context, collection122) + .withMetadata("dc", "contributor", "author", "author-mappedItem122211") + .withMetadata("dc", "test", "parentcommunity1field", "parentcommunity1field-mappedItem122211") + .withMetadata("dc", "test", "subcommunity11field", "subcommunity11field-mappedItem122211") + .withMetadata("dc", "test", "collection111field", "collection111field-mappedItem122211") + .withMetadata("dc", "test", "collection121field", "collection121field-mappedItem122211") + .withMetadata("dc", "test", "subcommunity21field", "subcommunity21field-mappedItem122211") + .withMetadata("dc", "test", "collection211field", "collection211field-mappedItem122211") + .withMetadata("dc", "test", "collection221field", "collection221field-mappedItem122211") + .build(); + + + collectionService.addItem(context, collection222, mappedItem111222); + collectionService.addItem(context, collection211, mappedItem122211); + + + context.dispatchEvents(); + context.restoreAuthSystemState(); + } + + @Test + public void ScopeBasedIndexingAndSearchTestParentCommunity1() throws Exception { + + getClient().perform(get("/api/discover/facets").param("scope", String.valueOf(community1.getID()))) + + .andExpect(status().isOk()) + .andExpect(jsonPath("$.type", is("discover"))) + .andExpect(jsonPath("$._links.self.href", containsString("api/discover/facets"))) + .andExpect(jsonPath("$._embedded.facets", containsInAnyOrder( + FacetEntryMatcher.authorFacet(false), + FacetEntryMatcher.matchFacet("parentcommunity1field", "text", false))) + ); + + getClient().perform(get("/api/discover/facets/parentcommunity1field") + .param("scope", String.valueOf(community1.getID()))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.type", is("discover"))) + .andExpect(jsonPath("$._embedded.values", + containsInAnyOrder( + FacetValueMatcher.matchEntry("parentcommunity1field", + "parentcommunity1field-item111", 1), + FacetValueMatcher.matchEntry("parentcommunity1field", + "parentcommunity1field-item112", 1), + FacetValueMatcher.matchEntry("parentcommunity1field", + "parentcommunity1field-item121", 1), + FacetValueMatcher.matchEntry("parentcommunity1field", + "parentcommunity1field-item122", 1), + FacetValueMatcher.matchEntry("parentcommunity1field", + "parentcommunity1field-mappedItem111222", + 1), + FacetValueMatcher.matchEntry("parentcommunity1field", + "parentcommunity1field-mappedItem122211", 1) + ) + )); + + + } + + @Test + public void ScopeBasedIndexingAndSearchTestSubCommunity11() throws Exception { + + getClient().perform(get("/api/discover/facets").param("scope", String.valueOf(subcommunity11.getID()))) + + .andExpect(status().isOk()) + .andExpect(jsonPath("$.type", is("discover"))) + .andExpect(jsonPath("$._links.self.href", containsString("api/discover/facets"))) + .andExpect(jsonPath("$._embedded.facets", containsInAnyOrder( + FacetEntryMatcher.authorFacet(false), + FacetEntryMatcher.matchFacet("subcommunity11field", "text", false))) + ); + + getClient().perform(get("/api/discover/facets/subcommunity11field") + .param("scope", String.valueOf(subcommunity11.getID()))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.type", is("discover"))) + .andExpect(jsonPath("$._embedded.values", + containsInAnyOrder( + FacetValueMatcher.matchEntry("subcommunity11field", + "subcommunity11field-item111", 1), + FacetValueMatcher.matchEntry("subcommunity11field", + "subcommunity11field-item112", 1), + FacetValueMatcher.matchEntry("subcommunity11field", + "subcommunity11field-mappedItem111222", 1) + ) + )); + } + + @Test + public void ScopeBasedIndexingAndSearchTestCollection111() throws Exception { + + getClient().perform(get("/api/discover/facets").param("scope", String.valueOf(collection111.getID()))) + + .andExpect(status().isOk()) + .andExpect(jsonPath("$.type", is("discover"))) + .andExpect(jsonPath("$._links.self.href", containsString("api/discover/facets"))) + .andExpect(jsonPath("$._embedded.facets", containsInAnyOrder( + FacetEntryMatcher.authorFacet(false), + FacetEntryMatcher.matchFacet("collection111field", "text", false))) + ); + + getClient().perform(get("/api/discover/facets/collection111field") + .param("scope", String.valueOf(collection111.getID()))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.type", is("discover"))) + .andExpect(jsonPath("$._embedded.values", + containsInAnyOrder( + FacetValueMatcher.matchEntry("collection111field", + "collection111field-item111", 1), + FacetValueMatcher.matchEntry("collection111field", + "collection111field-mappedItem111222", 1) + ) + )); + } + + @Test + public void ScopeBasedIndexingAndSearchTestCollection112() throws Exception { + + getClient().perform(get("/api/discover/facets").param("scope", String.valueOf(collection112.getID()))) + + .andExpect(status().isOk()) + .andExpect(jsonPath("$.type", is("discover"))) + .andExpect(jsonPath("$._links.self.href", containsString("api/discover/facets"))) + .andExpect(jsonPath("$._embedded.facets", containsInAnyOrder( + FacetEntryMatcher.authorFacet(false), + FacetEntryMatcher.matchFacet("subcommunity11field", "text", false))) + ); + + getClient().perform(get("/api/discover/facets/subcommunity11field") + .param("scope", String.valueOf(collection112.getID()))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.type", is("discover"))) + .andExpect(jsonPath("$._embedded.values", + containsInAnyOrder( + FacetValueMatcher.matchEntry("subcommunity11field", + "subcommunity11field-item112", 1) + ) + )); + } + + @Test + public void ScopeBasedIndexingAndSearchTestSubcommunity12() throws Exception { + + getClient().perform(get("/api/discover/facets").param("scope", String.valueOf(subcommunity12.getID()))) + + .andExpect(status().isOk()) + .andExpect(jsonPath("$.type", is("discover"))) + .andExpect(jsonPath("$._links.self.href", containsString("api/discover/facets"))) + .andExpect(jsonPath("$._embedded.facets", containsInAnyOrder( + FacetEntryMatcher.authorFacet(false), + FacetEntryMatcher.matchFacet("parentcommunity1field", "text", false))) + ); + + getClient().perform(get("/api/discover/facets/parentcommunity1field") + .param("scope", String.valueOf(subcommunity12.getID()))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.type", is("discover"))) + .andExpect(jsonPath("$._embedded.values", + containsInAnyOrder( + FacetValueMatcher.matchEntry("parentcommunity1field", + "parentcommunity1field-item121", 1), + FacetValueMatcher.matchEntry("parentcommunity1field", + "parentcommunity1field-item122", 1), + FacetValueMatcher.matchEntry("parentcommunity1field", + "parentcommunity1field-mappedItem122211", 1) + ) + )); + } + + @Test + public void ScopeBasedIndexingAndSearchTestCollection121() throws Exception { + + getClient().perform(get("/api/discover/facets").param("scope", String.valueOf(collection121.getID()))) + + .andExpect(status().isOk()) + .andExpect(jsonPath("$.type", is("discover"))) + .andExpect(jsonPath("$._links.self.href", containsString("api/discover/facets"))) + .andExpect(jsonPath("$._embedded.facets", containsInAnyOrder( + FacetEntryMatcher.authorFacet(false), + FacetEntryMatcher.matchFacet("collection121field", "text", false))) + ); + + getClient().perform(get("/api/discover/facets/collection121field") + .param("scope", String.valueOf(collection121.getID()))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.type", is("discover"))) + .andExpect(jsonPath("$._embedded.values", + containsInAnyOrder( + FacetValueMatcher.matchEntry("collection121field", + "collection121field-item121", 1) + ) + )); + } + + @Test + public void ScopeBasedIndexingAndSearchTestCollection122() throws Exception { + + getClient().perform(get("/api/discover/facets").param("scope", String.valueOf(collection122.getID()))) + + .andExpect(status().isOk()) + .andExpect(jsonPath("$.type", is("discover"))) + .andExpect(jsonPath("$._links.self.href", containsString("api/discover/facets"))) + .andExpect(jsonPath("$._embedded.facets", containsInAnyOrder( + FacetEntryMatcher.authorFacet(false), + FacetEntryMatcher.matchFacet("parentcommunity1field", "text", false))) + ); + + getClient().perform(get("/api/discover/facets/parentcommunity1field") + .param("scope", String.valueOf(collection122.getID()))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.type", is("discover"))) + .andExpect(jsonPath("$._embedded.values", + containsInAnyOrder( + FacetValueMatcher.matchEntry("parentcommunity1field", + "parentcommunity1field-item122", 1), + FacetValueMatcher.matchEntry("parentcommunity1field", + "parentcommunity1field-mappedItem122211", 1) + ) + )); + } + + @Test + public void ScopeBasedIndexingAndSearchTestParentCommunity2() throws Exception { + + getClient().perform(get("/api/discover/facets").param("scope", String.valueOf(community2.getID()))) + + .andExpect(status().isOk()) + .andExpect(jsonPath("$.type", is("discover"))) + .andExpect(jsonPath("$._links.self.href", containsString("api/discover/facets"))) + .andExpect(jsonPath("$._embedded.facets", containsInAnyOrder( + FacetEntryMatcher.authorFacet(false), + FacetEntryMatcher.subjectFacet(false), + FacetEntryMatcher.dateIssuedFacet(false), + FacetEntryMatcher.hasContentInOriginalBundleFacet(false), + FacetEntryMatcher.entityTypeFacet(false) + )) + ); + } + + @Test + public void ScopeBasedIndexingAndSearchTestSubCommunity21() throws Exception { + + getClient().perform(get("/api/discover/facets").param("scope", String.valueOf(subcommunity21.getID()))) + + .andExpect(status().isOk()) + .andExpect(jsonPath("$.type", is("discover"))) + .andExpect(jsonPath("$._links.self.href", containsString("api/discover/facets"))) + .andExpect(jsonPath("$._embedded.facets", containsInAnyOrder( + FacetEntryMatcher.authorFacet(false), + FacetEntryMatcher.matchFacet("subcommunity21field", "text", false))) + ); + + getClient().perform(get("/api/discover/facets/subcommunity21field") + .param("scope", String.valueOf(subcommunity21.getID()))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.type", is("discover"))) + .andExpect(jsonPath("$._embedded.values", + containsInAnyOrder( + FacetValueMatcher.matchEntry("subcommunity21field", + "subcommunity21field-item211", 1), + FacetValueMatcher.matchEntry("subcommunity21field", + "subcommunity21field-item212", 1), + FacetValueMatcher.matchEntry("subcommunity21field", + "subcommunity21field-mappedItem122211", 1) + ) + )); + } + + @Test + public void ScopeBasedIndexingAndSearchTestCollection211() throws Exception { + + getClient().perform(get("/api/discover/facets").param("scope", String.valueOf(collection211.getID()))) + + .andExpect(status().isOk()) + .andExpect(jsonPath("$.type", is("discover"))) + .andExpect(jsonPath("$._links.self.href", containsString("api/discover/facets"))) + .andExpect(jsonPath("$._embedded.facets", containsInAnyOrder( + FacetEntryMatcher.authorFacet(false), + FacetEntryMatcher.matchFacet("collection211field", "text", false))) + ); + + getClient().perform(get("/api/discover/facets/collection211field") + .param("scope", String.valueOf(collection211.getID()))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.type", is("discover"))) + .andExpect(jsonPath("$._embedded.values", + containsInAnyOrder( + FacetValueMatcher.matchEntry("collection211field", + "collection211field-item211", 1), + FacetValueMatcher.matchEntry("collection211field", + "collection211field-mappedItem122211", 1) + ) + )); + } + + @Test + public void ScopeBasedIndexingAndSearchTestCollection212() throws Exception { + + getClient().perform(get("/api/discover/facets").param("scope", String.valueOf(collection212.getID()))) + + .andExpect(status().isOk()) + .andExpect(jsonPath("$.type", is("discover"))) + .andExpect(jsonPath("$._links.self.href", containsString("api/discover/facets"))) + .andExpect(jsonPath("$._embedded.facets", containsInAnyOrder( + FacetEntryMatcher.authorFacet(false), + FacetEntryMatcher.matchFacet("subcommunity21field", "text", false))) + ); + + getClient().perform(get("/api/discover/facets/subcommunity21field") + .param("scope", String.valueOf(collection212.getID()))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.type", is("discover"))) + .andExpect(jsonPath("$._embedded.values", + containsInAnyOrder( + FacetValueMatcher.matchEntry("subcommunity21field", + "subcommunity21field-item212", 1) + ) + )); + } + + @Test + public void ScopeBasedIndexingAndSearchTestSubcommunity22() throws Exception { + getClient().perform(get("/api/discover/facets").param("scope", String.valueOf(subcommunity22.getID()))) + + .andExpect(status().isOk()) + .andExpect(jsonPath("$.type", is("discover"))) + .andExpect(jsonPath("$._links.self.href", containsString("api/discover/facets"))) + .andExpect(jsonPath("$._embedded.facets", containsInAnyOrder( + FacetEntryMatcher.authorFacet(false), + FacetEntryMatcher.subjectFacet(false), + FacetEntryMatcher.dateIssuedFacet(false), + FacetEntryMatcher.hasContentInOriginalBundleFacet(false), + FacetEntryMatcher.entityTypeFacet(false) + )) + ); + } + + @Test + public void ScopeBasedIndexingAndSearchTestCollection221() throws Exception { + + getClient().perform(get("/api/discover/facets").param("scope", String.valueOf(collection221.getID()))) + + .andExpect(status().isOk()) + .andExpect(jsonPath("$.type", is("discover"))) + .andExpect(jsonPath("$._links.self.href", containsString("api/discover/facets"))) + .andExpect(jsonPath("$._embedded.facets", containsInAnyOrder( + FacetEntryMatcher.authorFacet(false), + FacetEntryMatcher.matchFacet("collection221field", "text", false))) + ); + + getClient().perform(get("/api/discover/facets/collection221field") + .param("scope", String.valueOf(collection221.getID()))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.type", is("discover"))) + .andExpect(jsonPath("$._embedded.values", + containsInAnyOrder( + FacetValueMatcher.matchEntry("collection221field", + "collection221field-item221", 1) + ) + )); + } + + @Test + public void ScopeBasedIndexingAndSearchTestCollection222() throws Exception { + + getClient().perform(get("/api/discover/facets").param("scope", String.valueOf(collection222.getID()))) + + .andExpect(status().isOk()) + .andExpect(jsonPath("$.type", is("discover"))) + .andExpect(jsonPath("$._links.self.href", containsString("api/discover/facets"))) + .andExpect(jsonPath("$._embedded.facets", containsInAnyOrder( + FacetEntryMatcher.authorFacet(false), + FacetEntryMatcher.subjectFacet(false), + FacetEntryMatcher.dateIssuedFacet(false), + FacetEntryMatcher.hasContentInOriginalBundleFacet(false), + FacetEntryMatcher.entityTypeFacet(false) + )) + ); + } + + +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/FacetEntryMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/FacetEntryMatcher.java index 5e3c477506b9..34b7b8b30d6a 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/FacetEntryMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/FacetEntryMatcher.java @@ -99,6 +99,17 @@ public static Matcher hasContentInOriginalBundleFacet(boolean ha ); } + public static Matcher matchFacet(String name, String facetType, boolean hasNext) { + return allOf( + hasJsonPath("$.name", is(name)), + hasJsonPath("$.facetType", is(facetType)), + hasJsonPath("$.facetLimit", any(Integer.class)), + hasJsonPath("$._links.self.href", containsString("api/discover/facets/" + name)), + hasJsonPath("$._links", matchNextLink(hasNext, "api/discover/facets/" + name)) + ); + } + + /** * Check that a facet over the dc.type exists and match the default configuration * diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/FacetValueMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/FacetValueMatcher.java index a68356da5322..1efafb5406ce 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/FacetValueMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/FacetValueMatcher.java @@ -52,6 +52,16 @@ public static Matcher entrySubject(String label, int count) { ); } + public static Matcher matchEntry(String facet, String label, int count) { + return allOf( + hasJsonPath("$.label", is(label)), + hasJsonPath("$.type", is("discover")), + hasJsonPath("$.count", is(count)), + hasJsonPath("$._links.search.href", containsString("api/discover/search/objects")), + hasJsonPath("$._links.search.href", containsString("f." + facet + "=" + label + ",equals")) + ); + } + public static Matcher entrySubject(String label, String authority, int count) { return allOf( diff --git a/machine.cfg b/machine.cfg new file mode 100644 index 000000000000..14f0d1d0b067 --- /dev/null +++ b/machine.cfg @@ -0,0 +1,19 @@ +dspace.shortname = or-platform-7 + +dspace.dir=/Users/yana/dspaces/or-platform-7 + +dspace.server.url =http://localhost:8080/server-or7 +dspace.ui.url = http://localhost:4000 + +# URL for connecting to database +# * Postgres template: jdbc:postgrook naar de toekomst toe wilt dat zeggen dat de backend gewoon in orde is en mogelijk enkel nog eesql://localhost:5432/dspace +# * Oracle template: jdbc:oracle:thin:@//localhost:1521/xe +#db.url = ${db.url} +#db.url = jdbc:postgresql://localhost:5432/or-platform-7 +db.url = jdbc:postgresql://localhost:5434/or-platform-7-4 + + + +solr.server = http://localhost:8983/solr + + From c538b9cbedd2d7ab7ab88b912a5eeb75a180e10d Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Tue, 13 Dec 2022 14:27:29 +0100 Subject: [PATCH 040/510] Add docs and remove unused site configuration --- .../org/dspace/discovery/SearchUtils.java | 19 ++- .../DiscoveryConfigurationService.java | 15 ++ .../config/spring/api/discovery.xml | 133 +----------------- .../DiscoveryScopeBasedRestControllerIT.java | 56 ++++++-- dspace/config/spring/api/discovery.xml | 129 ----------------- 5 files changed, 77 insertions(+), 275 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/discovery/SearchUtils.java b/dspace-api/src/main/java/org/dspace/discovery/SearchUtils.java index 4085e1bbdf37..418720be4a48 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/SearchUtils.java +++ b/dspace-api/src/main/java/org/dspace/discovery/SearchUtils.java @@ -77,6 +77,7 @@ public static DiscoveryConfiguration getDiscoveryConfiguration(final Context con * * * @param context + * the database context * @param prefix * the namespace of the configuration to lookup if any * @param dso @@ -92,6 +93,15 @@ public static DiscoveryConfiguration getDiscoveryConfiguration(final Context con } } + /** + * Retrieve the configuration for the current dspace object and all its parents and add it to the provided set + * @param context - The database context + * @param configurations - The set of configurations to add the retrieved configurations to + * @param prefix - The namespace of the configuration to lookup if any + * @param dso - The DSpace Object + * @return the set of configurations with additional retrieved ones for the dspace object and parents + * @throws SQLException + */ public static Set addDiscoveryConfigurationForParents( Context context, Set configurations, String prefix, DSpaceObject dso) throws SQLException { @@ -124,6 +134,13 @@ public static DiscoveryConfiguration getDiscoveryConfigurationByName( return configurationService.getDiscoveryConfiguration(configurationName); } + + /** + * Return the discovery configuration for the provided DSO + * @param context - The database context + * @param dso - The DSpace object to retrieve the configuration for + * @return the discovery configuration for the provided DSO + */ public static DiscoveryConfiguration getDiscoveryConfigurationByDSO( Context context, DSpaceObject dso) { DiscoveryConfigurationService configurationService = getConfigurationService(); @@ -145,7 +162,7 @@ public static List getIgnoredMetadataFields(int type) { * A configuration object can be returned for each parent community/collection * * @param item the DSpace item - * @param context + * @param context the database context * @return a list of configuration objects * @throws SQLException An exception that provides information on a database access error or other errors. */ 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 22443aec22e7..c0eba58669e9 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 @@ -63,6 +63,13 @@ public DiscoveryConfiguration getDiscoveryConfiguration(final Context context, return getDiscoveryConfiguration(name); } + /** + * Retrieve the discovery configuration for the provided DSO. When no direct match is found, the parent object will + * be checked until there is no parent left, in which case the "default" configuration will be returned. + * @param context - The database context + * @param dso - The DSpace object to retrieve the configuration for + * @return the discovery configuration for the provided DSO. + */ public DiscoveryConfiguration getDiscoveryDSOConfiguration(final Context context, DSpaceObject dso) { String name; @@ -91,6 +98,14 @@ public DiscoveryConfiguration getDiscoveryConfiguration(final String name) { return getDiscoveryConfiguration(name, true); } + /** + * Retrieve the configuration for the provided name. When useDefault is set to true, the "default" configuration + * will be returned when no match is found. When useDefault is set to false, null will be returned when no match is + * found. + * @param name - The name of the configuration to retrieve + * @param useDefault - Whether the default configuration should be used when no match is found + * @return the configuration for the provided name + */ public DiscoveryConfiguration getDiscoveryConfiguration(final String name, boolean useDefault) { DiscoveryConfiguration result; diff --git a/dspace-server-webapp/src/test/data/dspaceFolder/config/spring/api/discovery.xml b/dspace-server-webapp/src/test/data/dspaceFolder/config/spring/api/discovery.xml index 6ffcbe661c87..e029c65aa000 100644 --- a/dspace-server-webapp/src/test/data/dspaceFolder/config/spring/api/discovery.xml +++ b/dspace-server-webapp/src/test/data/dspaceFolder/config/spring/api/discovery.xml @@ -50,9 +50,6 @@ --> - - - @@ -77,6 +74,7 @@ + @@ -543,121 +541,6 @@
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - search.resourcetype:Item OR search.resourcetype:Collection OR search.resourcetype:Community - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryScopeBasedRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryScopeBasedRestControllerIT.java index a0edf1a0c70a..15c1019584b1 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryScopeBasedRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryScopeBasedRestControllerIT.java @@ -29,12 +29,42 @@ import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; +/** + * This class tests the correct inheritance of Discovery configurations for sub communities and collections. + * To thoroughly test this, a community and collection structure is set up to where different communities have custom + * configurations configured for them. + * + * The following structure is uses: + * - Parent Community 1 - Custom configuration: discovery-parent-community-1 + * -- Subcommunity 11 - Custom configuration: discovery-sub-community-1-1 + * -- Collection 111 - Custom configuration: discovery-collection-1-1-1 + * -- Collection 112 + * -- Subcommunity 12 + * -- Collection 121 - Custom configuration: discovery-collection-1-2-1 + * -- Collection 122 + * - Parent Community 2 + * -- Subcommunity 21 - Custom configuration: discovery-sub-community-2-1 + * -- Collection 211 - Custom configuration: discovery-collection-2-1-1 + * -- Collection 212 + * -- Subcommunity 22 + * -- Collection 221 - Custom configuration: discovery-collection-2-2-1 + * -- Collection 222 + * + * Each custom configuration contains a unique index for a unique metadata field, to verify if correct information is + * indexed and provided for the different search scopes. + * + * Each collection has an item in it. Next to these items, there are two mapped items, one in collection 111 and 222, + * and one in collection 122 and 211. + * + * The tests will verify that for each object, the correct facets are provided and that all the necessary fields to + * power these facets are indexed properly. + */ public class DiscoveryScopeBasedRestControllerIT extends AbstractControllerIntegrationTest { @Autowired CollectionService collectionService; - private Community community1; + private Community parentCommunity1; private Community subcommunity11; private Community subcommunity12; private Collection collection111; @@ -42,7 +72,7 @@ public class DiscoveryScopeBasedRestControllerIT extends AbstractControllerInteg private Collection collection121; private Collection collection122; - private Community community2; + private Community parentCommunity2; private Community subcommunity21; private Community subcommunity22; private Collection collection211; @@ -64,13 +94,13 @@ public void setUp() throws Exception { MetadataFieldBuilder.createMetadataField(context, "test", "collection211field", "").build(); MetadataFieldBuilder.createMetadataField(context, "test", "collection221field", "").build(); - community1 = CommunityBuilder.createCommunity(context, "123456789/discovery-parent-community-1") - .build(); + parentCommunity1 = CommunityBuilder.createCommunity(context, "123456789/discovery-parent-community-1") + .build(); subcommunity11 = CommunityBuilder - .createSubCommunity(context, community1, "123456789/discovery-sub-community-1-1") + .createSubCommunity(context, parentCommunity1, "123456789/discovery-sub-community-1-1") .build(); subcommunity12 = CommunityBuilder - .createSubCommunity(context, community1, "123456789/discovery-sub-community-1-2") + .createSubCommunity(context, parentCommunity1, "123456789/discovery-sub-community-1-2") .build(); collection111 = CollectionBuilder .createCollection(context, subcommunity11, "123456789/discovery-collection-1-1-1") @@ -86,15 +116,15 @@ public void setUp() throws Exception { .createCollection(context, subcommunity12, "123456789/discovery-collection-1-2-2") .build(); - community2 = CommunityBuilder.createCommunity(context, "123456789/discovery-parent-community-2") - .build(); + parentCommunity2 = CommunityBuilder.createCommunity(context, "123456789/discovery-parent-community-2") + .build(); subcommunity21 = CommunityBuilder - .createSubCommunity(context, community2, "123456789/discovery-sub-community-2-1") + .createSubCommunity(context, parentCommunity2, "123456789/discovery-sub-community-2-1") .build(); subcommunity22 = CommunityBuilder - .createSubCommunity(context, community2, "123456789/discovery-sub-community-2-2") + .createSubCommunity(context, parentCommunity2, "123456789/discovery-sub-community-2-2") .build(); collection211 = CollectionBuilder .createCollection(context, subcommunity21, "123456789/discovery-collection-2-1-1") @@ -235,7 +265,7 @@ public void setUp() throws Exception { @Test public void ScopeBasedIndexingAndSearchTestParentCommunity1() throws Exception { - getClient().perform(get("/api/discover/facets").param("scope", String.valueOf(community1.getID()))) + getClient().perform(get("/api/discover/facets").param("scope", String.valueOf(parentCommunity1.getID()))) .andExpect(status().isOk()) .andExpect(jsonPath("$.type", is("discover"))) @@ -246,7 +276,7 @@ public void ScopeBasedIndexingAndSearchTestParentCommunity1() throws Exception { ); getClient().perform(get("/api/discover/facets/parentcommunity1field") - .param("scope", String.valueOf(community1.getID()))) + .param("scope", String.valueOf(parentCommunity1.getID()))) .andExpect(status().isOk()) .andExpect(jsonPath("$.type", is("discover"))) .andExpect(jsonPath("$._embedded.values", @@ -435,7 +465,7 @@ public void ScopeBasedIndexingAndSearchTestCollection122() throws Exception { @Test public void ScopeBasedIndexingAndSearchTestParentCommunity2() throws Exception { - getClient().perform(get("/api/discover/facets").param("scope", String.valueOf(community2.getID()))) + getClient().perform(get("/api/discover/facets").param("scope", String.valueOf(parentCommunity2.getID()))) .andExpect(status().isOk()) .andExpect(jsonPath("$.type", is("discover"))) diff --git a/dspace/config/spring/api/discovery.xml b/dspace/config/spring/api/discovery.xml index 4392e02cb3dc..ae1992fbff35 100644 --- a/dspace/config/spring/api/discovery.xml +++ b/dspace/config/spring/api/discovery.xml @@ -50,9 +50,6 @@ --> - - - @@ -534,120 +531,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - search.resourcetype:Item OR search.resourcetype:Collection OR search.resourcetype:Community - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - From 463edac869855150b3bb1c6e2f31c8a97482a633 Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Tue, 13 Dec 2022 17:08:02 +0100 Subject: [PATCH 041/510] Remove local file --- machine.cfg | 19 ------------------- 1 file changed, 19 deletions(-) delete mode 100644 machine.cfg diff --git a/machine.cfg b/machine.cfg deleted file mode 100644 index 14f0d1d0b067..000000000000 --- a/machine.cfg +++ /dev/null @@ -1,19 +0,0 @@ -dspace.shortname = or-platform-7 - -dspace.dir=/Users/yana/dspaces/or-platform-7 - -dspace.server.url =http://localhost:8080/server-or7 -dspace.ui.url = http://localhost:4000 - -# URL for connecting to database -# * Postgres template: jdbc:postgrook naar de toekomst toe wilt dat zeggen dat de backend gewoon in orde is en mogelijk enkel nog eesql://localhost:5432/dspace -# * Oracle template: jdbc:oracle:thin:@//localhost:1521/xe -#db.url = ${db.url} -#db.url = jdbc:postgresql://localhost:5432/or-platform-7 -db.url = jdbc:postgresql://localhost:5434/or-platform-7-4 - - - -solr.server = http://localhost:8983/solr - - From 14534b4eafb8f5333440a624f07395b2cb2f14eb Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Tue, 13 Dec 2022 17:47:35 +0100 Subject: [PATCH 042/510] Move context to first argument in getDiscoveryConfigurationByNameOrDso --- .../configuration/DiscoveryConfigurationService.java | 4 ++-- .../app/rest/repository/DiscoveryRestRepository.java | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) 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 c0eba58669e9..d7bc3b0f353b 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 @@ -119,8 +119,8 @@ public DiscoveryConfiguration getDiscoveryConfiguration(final String name, boole return result; } - public DiscoveryConfiguration getDiscoveryConfigurationByNameOrDso(final String configurationName, - final Context context, + public DiscoveryConfiguration getDiscoveryConfigurationByNameOrDso(final Context context, + final String configurationName, final IndexableObject dso) { if (StringUtils.isNotBlank(configurationName) && getMap().containsKey(configurationName)) { return getMap().get(configurationName); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/DiscoveryRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/DiscoveryRestRepository.java index 1962d44162e0..e337e76ef253 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/DiscoveryRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/DiscoveryRestRepository.java @@ -84,7 +84,7 @@ public SearchConfigurationRest getSearchConfiguration(final String dsoScope, fin IndexableObject scopeObject = scopeResolver.resolveScope(context, dsoScope); DiscoveryConfiguration discoveryConfiguration = searchConfigurationService - .getDiscoveryConfigurationByNameOrDso(configuration, context, scopeObject); + .getDiscoveryConfigurationByNameOrDso(context, configuration, scopeObject); return discoverConfigurationConverter.convert(discoveryConfiguration, utils.obtainProjection()); } @@ -96,7 +96,7 @@ public SearchResultsRest getSearchObjects(final String query, final List Context context = obtainContext(); IndexableObject scopeObject = scopeResolver.resolveScope(context, dsoScope); DiscoveryConfiguration discoveryConfiguration = searchConfigurationService - .getDiscoveryConfigurationByNameOrDso(configuration, context, scopeObject); + .getDiscoveryConfigurationByNameOrDso(context, configuration, scopeObject); DiscoverResult searchResult = null; DiscoverQuery discoverQuery = null; @@ -121,7 +121,7 @@ public FacetConfigurationRest getFacetsConfiguration(final String dsoScope, fina IndexableObject scopeObject = scopeResolver.resolveScope(context, dsoScope); DiscoveryConfiguration discoveryConfiguration = searchConfigurationService - .getDiscoveryConfigurationByNameOrDso(configuration, context, scopeObject); + .getDiscoveryConfigurationByNameOrDso(context, configuration, scopeObject); return discoverFacetConfigurationConverter.convert(configuration, dsoScope, discoveryConfiguration); } @@ -138,7 +138,7 @@ public FacetResultsRest getFacetObjects(String facetName, String prefix, String IndexableObject scopeObject = scopeResolver.resolveScope(context, dsoScope); DiscoveryConfiguration discoveryConfiguration = searchConfigurationService - .getDiscoveryConfigurationByNameOrDso(configuration, context, scopeObject); + .getDiscoveryConfigurationByNameOrDso(context, configuration, scopeObject); DiscoverQuery discoverQuery = queryBuilder.buildFacetQuery(context, scopeObject, discoveryConfiguration, prefix, query, searchFilters, dsoTypes, page, facetName); @@ -157,7 +157,7 @@ public SearchResultsRest getAllFacets(String query, List dsoTypes, Strin Pageable page = PageRequest.of(1, 1); IndexableObject scopeObject = scopeResolver.resolveScope(context, dsoScope); DiscoveryConfiguration discoveryConfiguration = searchConfigurationService - .getDiscoveryConfigurationByNameOrDso(configuration, context, scopeObject); + .getDiscoveryConfigurationByNameOrDso(context, configuration, scopeObject); DiscoverResult searchResult = null; DiscoverQuery discoverQuery = null; From 38b30c394c982c4760a8afc9676bfbe139de5e10 Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Wed, 14 Dec 2022 10:32:54 +0100 Subject: [PATCH 043/510] Fix openSearchController issue --- .../src/main/java/org/dspace/app/rest/OpenSearchController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 79ca3817534d..665504139cb3 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 @@ -176,7 +176,7 @@ public void search(HttpServletRequest request, if (dsoObject != null) { container = scopeResolver.resolveScope(context, dsoObject); DiscoveryConfiguration discoveryConfiguration = searchConfigurationService - .getDiscoveryConfigurationByNameOrDso("site", container); + .getDiscoveryConfiguration(context, container); queryArgs.setDiscoveryConfigurationName(discoveryConfiguration.getId()); queryArgs.addFilterQueries(discoveryConfiguration.getDefaultFilterQueries() .toArray( From 69500ad5d579f6891bbf35c35e29b18f120b20e9 Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Thu, 15 Dec 2022 11:55:05 +0100 Subject: [PATCH 044/510] Fix discovery test config and make ids for relationship profiles unique --- .../config/spring/api/discovery.xml | 779 ++++++++++++++++-- dspace/config/spring/api/discovery.xml | 14 +- 2 files changed, 724 insertions(+), 69 deletions(-) diff --git a/dspace-server-webapp/src/test/data/dspaceFolder/config/spring/api/discovery.xml b/dspace-server-webapp/src/test/data/dspaceFolder/config/spring/api/discovery.xml index e029c65aa000..a5d7682d4cfd 100644 --- a/dspace-server-webapp/src/test/data/dspaceFolder/config/spring/api/discovery.xml +++ b/dspace-server-webapp/src/test/data/dspaceFolder/config/spring/api/discovery.xml @@ -48,12 +48,15 @@ the key is used to refer to the page (the "site" or a community/collection handle) the value-ref is a reference to an identifier of the DiscoveryConfiguration format --> - - - - - - + + + + + + + + + @@ -61,17 +64,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -176,7 +210,145 @@ + (search.resourcetype:Item AND latestVersion:true) OR search.resourcetype:Collection OR search.resourcetype:Community + -withdrawn:true AND -discoverable:false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + dc.title + dc.contributor.author + dc.creator + dc.subject + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + search.resourcetype:Item OR search.resourcetype:Collection OR search.resourcetype:Community + -withdrawn:true AND -discoverable:false @@ -313,7 +485,7 @@ - search.resourcetype:Item + search.resourcetype:Item AND latestVersion:true withdrawn:true OR discoverable:false @@ -455,7 +627,7 @@ - search.resourcetype:Item + search.resourcetype:Item AND latestVersion:true @@ -541,10 +713,11 @@ + + class="org.dspace.discovery.configuration.DiscoveryConfiguration" + scope="prototype"> @@ -579,7 +752,7 @@ - search.resourcetype:Item OR search.resourcetype:WorkspaceItem OR search.resourcetype:XmlWorkflowItem + (search.resourcetype:Item AND latestVersion:true) OR search.resourcetype:WorkspaceItem OR search.resourcetype:XmlWorkflowItem @@ -616,8 +789,8 @@ + class="org.dspace.discovery.configuration.DiscoveryConfiguration" + scope="prototype"> @@ -691,8 +864,8 @@ + class="org.dspace.discovery.configuration.DiscoveryConfiguration" + scope="prototype"> @@ -814,7 +987,79 @@ + search.resourcetype:Item AND latestVersion:true AND entityType_keyword:Publication + -withdrawn:true AND -discoverable:false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + search.resourcetype:Item AND entityType_keyword:Publication + -withdrawn:true AND -discoverable:false @@ -875,7 +1120,8 @@ - search.resourcetype:Item AND entityType_keyword:Person + search.resourcetype:Item AND latestVersion:true AND entityType_keyword:Person + -withdrawn:true AND -discoverable:false @@ -893,23 +1139,28 @@ - - + + - + + + - - - - - + + + + + + + + @@ -918,7 +1169,10 @@ - + + + + @@ -928,7 +1182,9 @@ - search.resourcetype:Item AND entityType_keyword:Project + + search.resourcetype:Item AND entityType_keyword:Person + -withdrawn:true AND -discoverable:false @@ -946,27 +1202,200 @@ - - + + - - - + - - - - - - + + + + + + + + + + + + + + + + + + + + + + + search.resourcetype:Item AND latestVersion:true AND entityType_keyword:Project + -withdrawn:true AND -discoverable:false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + search.resourcetype:Item AND entityType_keyword:Project + -withdrawn:true AND -discoverable:false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + search.resourcetype:Item AND latestVersion:true AND entityType_keyword:OrgUnit + -withdrawn:true AND -discoverable:false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -990,7 +1419,9 @@ + search.resourcetype:Item AND entityType_keyword:OrgUnit + -withdrawn:true AND -discoverable:false @@ -1049,7 +1480,69 @@ + search.resourcetype:Item AND latestVersion:true AND entityType_keyword:JournalIssue + -withdrawn:true AND -discoverable:false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + search.resourcetype:Item AND entityType_keyword:JournalIssue + -withdrawn:true AND -discoverable:false @@ -1107,7 +1600,68 @@ + search.resourcetype:Item AND latestVersion:true AND entityType_keyword:JournalVolume + -withdrawn:true AND -discoverable:false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + search.resourcetype:Item AND entityType_keyword:JournalVolume + -withdrawn:true AND -discoverable:false @@ -1165,7 +1719,68 @@ + search.resourcetype:Item AND latestVersion:true AND entityType_keyword:Journal + -withdrawn:true AND -discoverable:false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + search.resourcetype:Item AND entityType_keyword:Journal + -withdrawn:true AND -discoverable:false @@ -1238,7 +1853,8 @@ - search.resourcetype:Item AND (entityType_keyword:OrgUnit OR entityType_keyword:Person) + search.resourcetype:Item AND latestVersion:true AND (entityType_keyword:OrgUnit OR entityType_keyword:Person) + -withdrawn:true AND -discoverable:false @@ -1293,7 +1909,8 @@ - search.resourcetype:Item AND entityType_keyword:OrgUnit AND dc.type:FundingOrganization + search.resourcetype:Item AND latestVersion:true AND entityType_keyword:OrgUnit AND dc.type:FundingOrganization + -withdrawn:true AND -discoverable:false @@ -1302,6 +1919,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + search.resourcetype:Item + search.entitytype:${researcher-profile.entity-type:Person} + -withdrawn:true AND -discoverable:false + + + + + + + + + @@ -2288,7 +2943,7 @@ - relation.isAuthorOfPublication + relation.isAuthorOfPublication.latestForDiscovery @@ -2299,7 +2954,7 @@ - relation.isProjectOfPublication + relation.isProjectOfPublication.latestForDiscovery @@ -2311,7 +2966,7 @@ - relation.isOrgUnitOfPublication + relation.isOrgUnitOfPublication.latestForDiscovery @@ -2322,7 +2977,7 @@ - relation.isPublicationOfJournalIssue + relation.isPublicationOfJournalIssue.latestForDiscovery @@ -2333,7 +2988,7 @@ - relation.isJournalOfPublication + relation.isJournalOfPublication.latestForDiscovery @@ -2539,7 +3194,7 @@ - relation.isOrgUnitOfPerson + relation.isOrgUnitOfPerson.latestForDiscovery @@ -2550,7 +3205,7 @@ - relation.isProjectOfPerson + relation.isProjectOfPerson.latestForDiscovery @@ -2562,7 +3217,7 @@ - relation.isPublicationOfAuthor + relation.isPublicationOfAuthor.latestForDiscovery @@ -2634,7 +3289,7 @@ - relation.isPersonOfOrgUnit + relation.isPersonOfOrgUnit.latestForDiscovery @@ -2645,7 +3300,7 @@ - relation.isProjectOfOrgUnit + relation.isProjectOfOrgUnit.latestForDiscovery @@ -2657,7 +3312,7 @@ - relation.isPublicationOfOrgUnit + relation.isPublicationOfOrgUnit.latestForDiscovery @@ -2711,7 +3366,7 @@ - relation.isPublicationOfJournalIssue + relation.isPublicationOfJournalIssue.latestForDiscovery @@ -2734,7 +3389,7 @@ - relation.isIssueOfJournalVolume + relation.isIssueOfJournalVolume.latestForDiscovery @@ -2745,7 +3400,7 @@ - relation.isJournalOfVolume + relation.isJournalOfVolume.latestForDiscovery @@ -2786,7 +3441,7 @@ - relation.isVolumeOfJournal + relation.isVolumeOfJournal.latestForDiscovery @@ -2811,7 +3466,7 @@ - relation.isOrgUnitOfProject + relation.isOrgUnitOfProject.latestForDiscovery @@ -2823,7 +3478,7 @@ - relation.isPersonOfProject + relation.isPersonOfProject.latestForDiscovery @@ -2835,7 +3490,7 @@ - relation.isPublicationOfProject + relation.isPublicationOfProject.latestForDiscovery @@ -2846,7 +3501,7 @@ - relation.isContributorOfPublication + relation.isContributorOfPublication.latestForDiscovery @@ -2857,7 +3512,7 @@ - relation.isPublicationOfContributor + relation.isPublicationOfContributor.latestForDiscovery @@ -2868,7 +3523,7 @@ - relation.isFundingAgencyOfProject + relation.isFundingAgencyOfProject.latestForDiscovery @@ -2879,7 +3534,7 @@ - relation.isProjectOfFundingAgency + relation.isProjectOfFundingAgency.latestForDiscovery diff --git a/dspace/config/spring/api/discovery.xml b/dspace/config/spring/api/discovery.xml index 5e2cae5e9fef..37d5f2548a4e 100644 --- a/dspace/config/spring/api/discovery.xml +++ b/dspace/config/spring/api/discovery.xml @@ -996,7 +996,7 @@ - + @@ -1129,7 +1129,7 @@ - + @@ -1246,7 +1246,7 @@ - + @@ -1366,7 +1366,7 @@ - + @@ -1491,7 +1491,7 @@ - + @@ -1611,7 +1611,7 @@ - + @@ -1730,7 +1730,7 @@ - + From 1300cdc75b25181fdeebda20661aaa02b2d92bfc Mon Sep 17 00:00:00 2001 From: Yury Bondarenko Date: Mon, 19 Dec 2022 11:20:53 +0100 Subject: [PATCH 045/510] 97248: Cache discovery configurations by UUID --- .../DiscoveryConfigurationService.java | 47 +++++++++++++------ 1 file changed, 33 insertions(+), 14 deletions(-) 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 d7bc3b0f353b..7d5b435555f9 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 @@ -12,6 +12,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.UUID; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; @@ -34,6 +35,12 @@ public class DiscoveryConfigurationService { private Map map; private Map> toIgnoreMetadataFields = new HashMap<>(); + /** + * Discovery configurations, cached by DSO UUID. When a DSO doesn't have its own configuration, we take the one of + * the first parent that does. This cache ensures we don't have to go up the hierarchy every time. + */ + private final Map uuidMap = new HashMap<>(); + public Map getMap() { return map; } @@ -72,26 +79,38 @@ public DiscoveryConfiguration getDiscoveryConfiguration(final Context context, */ public DiscoveryConfiguration getDiscoveryDSOConfiguration(final Context context, DSpaceObject dso) { - String name; + // Fall back to default configuration if (dso == null) { - name = "default"; - } else { - name = dso.getHandle(); + return getDiscoveryConfiguration("default", false); } - DiscoveryConfiguration configuration = getDiscoveryConfiguration(name, false); - if (configuration != null) { - return configuration; + // Attempt to retrieve cached configuration by UUID + if (uuidMap.containsKey(dso.getID())) { + return uuidMap.get(dso.getID()); } - DSpaceObjectService dSpaceObjectService = + + DiscoveryConfiguration configuration; + + // Attempt to retrieve configuration by DSO handle + configuration = getDiscoveryConfiguration(dso.getHandle(), false); + + if (configuration == null) { + // Recurse up the Comm/Coll hierarchy until a configuration is found + DSpaceObjectService dSpaceObjectService = ContentServiceFactory.getInstance().getDSpaceObjectService(dso); - DSpaceObject parentObject = null; - try { - parentObject = dSpaceObjectService.getParentObject(context, dso); - } catch (SQLException e) { - log.error(e); + DSpaceObject parentObject = null; + try { + parentObject = dSpaceObjectService.getParentObject(context, dso); + } catch (SQLException e) { + log.error(e); + } + configuration = getDiscoveryDSOConfiguration(context, parentObject); } - return getDiscoveryDSOConfiguration(context, parentObject); + + // Cache the resulting configuration + uuidMap.put(dso.getID(), configuration); + + return configuration; } public DiscoveryConfiguration getDiscoveryConfiguration(final String name) { From fc9f692bed09f252ab0fcd4d9cd48eb805a7d1f5 Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Thu, 12 Jan 2023 17:22:45 +0100 Subject: [PATCH 046/510] [DURACOM-92] Tested and improved S3BitStoreService.about method --- dspace-api/pom.xml | 1 + .../org/dspace/checker/CheckerCommand.java | 2 +- .../storage/bitstore/BaseBitStoreService.java | 32 +++--- .../storage/bitstore/BitStoreService.java | 5 +- .../bitstore/BitstreamStorageServiceImpl.java | 31 ++---- .../storage/bitstore/DSBitStoreService.java | 13 +-- .../storage/bitstore/S3BitStoreService.java | 100 ++++++------------ .../service/BitstreamStorageService.java | 2 +- ...tionTest.java => S3BitStoreServiceIT.java} | 55 +++++++++- 9 files changed, 123 insertions(+), 118 deletions(-) rename dspace-api/src/test/java/org/dspace/storage/bitstore/{S3BitStoreServiceIntegrationTest.java => S3BitStoreServiceIT.java} (87%) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index 9c873847e033..814add2085a6 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -917,6 +917,7 @@ org.scala-lang scala-library 2.13.2 + test diff --git a/dspace-api/src/main/java/org/dspace/checker/CheckerCommand.java b/dspace-api/src/main/java/org/dspace/checker/CheckerCommand.java index 6b16d51bfe1e..87b0de4a6571 100644 --- a/dspace-api/src/main/java/org/dspace/checker/CheckerCommand.java +++ b/dspace-api/src/main/java/org/dspace/checker/CheckerCommand.java @@ -245,7 +245,7 @@ protected void processBitstream(MostRecentChecksum info) throws SQLException { info.setProcessStartDate(new Date()); try { - Map checksumMap = bitstreamStorageService.computeChecksum(context, info.getBitstream()); + Map checksumMap = bitstreamStorageService.computeChecksum(context, info.getBitstream()); if (MapUtils.isNotEmpty(checksumMap)) { info.setBitstreamFound(true); if (checksumMap.containsKey("checksum")) { diff --git a/dspace-api/src/main/java/org/dspace/storage/bitstore/BaseBitStoreService.java b/dspace-api/src/main/java/org/dspace/storage/bitstore/BaseBitStoreService.java index 209c1e21e74d..5b367d7a8136 100644 --- a/dspace-api/src/main/java/org/dspace/storage/bitstore/BaseBitStoreService.java +++ b/dspace-api/src/main/java/org/dspace/storage/bitstore/BaseBitStoreService.java @@ -14,6 +14,8 @@ import java.security.DigestInputStream; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.util.HashMap; +import java.util.List; import java.util.Map; import org.apache.commons.lang3.StringUtils; @@ -153,22 +155,24 @@ protected boolean isLonger(String internalId, int endIndex) { * Retrieves a map of useful metadata about the File (size, checksum, modified) * * @param file The File to analyze - * @param attrs The map where we are storing values + * @param attrs The list of requested metadata values * @return Map of updated metadatas / attrs * @throws IOException */ - public Map about(File file, Map attrs) throws IOException { + public Map about(File file, List attrs) throws IOException { + + Map metadata = new HashMap(); + try { if (file != null && file.exists()) { - this.putValueIfExistsKey(attrs, SIZE_BYTES, file.length()); - if (attrs.containsKey(CHECKSUM)) { - attrs.put(CHECKSUM, Utils.toHex(this.generateChecksumFrom(file))); - attrs.put(CHECKSUM_ALGORITHM, CSA); + this.putValueIfExistsKey(attrs, metadata, SIZE_BYTES, file.length()); + if (attrs.contains(CHECKSUM)) { + metadata.put(CHECKSUM, Utils.toHex(this.generateChecksumFrom(file))); + metadata.put(CHECKSUM_ALGORITHM, CSA); } - this.putValueIfExistsKey(attrs, MODIFIED, String.valueOf(file.lastModified())); - return attrs; + this.putValueIfExistsKey(attrs, metadata, MODIFIED, String.valueOf(file.lastModified())); } - return null; + return metadata; } catch (Exception e) { log.error("about( FilePath: " + file.getAbsolutePath() + ", Map: " + attrs.toString() + ")", e); throw new IOException(e); @@ -204,13 +208,9 @@ private byte[] generateChecksumFrom(FileInputStream fis) throws IOException, NoS } } - protected void putValueIfExistsKey(Map attrs, String key, Object value) { - this.putEntryIfExistsKey(attrs, key, Map.entry(key, value)); - } - - protected void putEntryIfExistsKey(Map attrs, String key, Map.Entry entry) { - if (attrs.containsKey(key)) { - attrs.put(entry.getKey(), entry.getValue()); + protected void putValueIfExistsKey(List attrs, Map metadata, String key, Object value) { + if (attrs.contains(key)) { + metadata.put(key, value); } } diff --git a/dspace-api/src/main/java/org/dspace/storage/bitstore/BitStoreService.java b/dspace-api/src/main/java/org/dspace/storage/bitstore/BitStoreService.java index b6ac540c5047..5a02ad1d5617 100644 --- a/dspace-api/src/main/java/org/dspace/storage/bitstore/BitStoreService.java +++ b/dspace-api/src/main/java/org/dspace/storage/bitstore/BitStoreService.java @@ -9,6 +9,7 @@ import java.io.IOException; import java.io.InputStream; +import java.util.List; import java.util.Map; import org.dspace.content.Bitstream; @@ -62,13 +63,13 @@ public interface BitStoreService { * Obtain technical metadata about an asset in the asset store. * * @param bitstream The bitstream to describe - * @param attrs A Map whose keys consist of desired metadata fields + * @param attrs A List of desired metadata fields * @return attrs * A Map with key/value pairs of desired metadata * If file not found, then return null * @throws java.io.IOException If a problem occurs while obtaining metadata */ - public Map about(Bitstream bitstream, Map attrs) throws IOException; + public Map about(Bitstream bitstream, List attrs) throws IOException; /** * Remove an asset from the asset store. diff --git a/dspace-api/src/main/java/org/dspace/storage/bitstore/BitstreamStorageServiceImpl.java b/dspace-api/src/main/java/org/dspace/storage/bitstore/BitstreamStorageServiceImpl.java index 0bd71088da44..3124fd89ca21 100644 --- a/dspace-api/src/main/java/org/dspace/storage/bitstore/BitstreamStorageServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/storage/bitstore/BitstreamStorageServiceImpl.java @@ -165,12 +165,9 @@ public UUID register(Context context, Bitstream bitstream, int assetstore, bitstream.setStoreNumber(assetstore); bitstreamService.update(context, bitstream); - Map wantedMetadata = new HashMap(); - wantedMetadata.put("size_bytes", null); - wantedMetadata.put("checksum", null); - wantedMetadata.put("checksum_algorithm", null); + List wantedMetadata = List.of("size_bytes", "checksum", "checksum_algorithm"); + Map receivedMetadata = this.getStore(assetstore).about(bitstream, wantedMetadata); - Map receivedMetadata = this.getStore(assetstore).about(bitstream, wantedMetadata); if (MapUtils.isEmpty(receivedMetadata)) { String message = "Not able to register bitstream:" + bitstream.getID() + " at path: " + bitstreamPath; log.error(message); @@ -200,13 +197,8 @@ public UUID register(Context context, Bitstream bitstream, int assetstore, } @Override - public Map computeChecksum(Context context, Bitstream bitstream) throws IOException { - Map wantedMetadata = new HashMap(); - wantedMetadata.put("checksum", null); - wantedMetadata.put("checksum_algorithm", null); - - Map receivedMetadata = this.getStore(bitstream.getStoreNumber()).about(bitstream, wantedMetadata); - return receivedMetadata; + public Map computeChecksum(Context context, Bitstream bitstream) throws IOException { + return this.getStore(bitstream.getStoreNumber()).about(bitstream, List.of("checksum", "checksum_algorithm")); } @Override @@ -232,10 +224,9 @@ public void cleanup(boolean deleteDbRecords, boolean verbose) throws SQLExceptio List storage = bitstreamService.findDeletedBitstreams(context); for (Bitstream bitstream : storage) { UUID bid = bitstream.getID(); - Map wantedMetadata = new HashMap(); - wantedMetadata.put("size_bytes", null); - wantedMetadata.put("modified", null); - Map receivedMetadata = this.getStore(bitstream.getStoreNumber()).about(bitstream, wantedMetadata); + List wantedMetadata = List.of("size_bytes", "modified"); + Map receivedMetadata = this.getStore(bitstream.getStoreNumber()) + .about(bitstream, wantedMetadata); // Make sure entries which do not exist are removed @@ -328,13 +319,11 @@ public void cleanup(boolean deleteDbRecords, boolean verbose) throws SQLExceptio @Nullable @Override public Long getLastModified(Bitstream bitstream) throws IOException { - Map attrs = new HashMap(); - attrs.put("modified", null); - attrs = this.getStore(bitstream.getStoreNumber()).about(bitstream, attrs); - if (attrs == null || !attrs.containsKey("modified")) { + Map metadata = this.getStore(bitstream.getStoreNumber()).about(bitstream, List.of("modified")); + if (metadata == null || !metadata.containsKey("modified")) { return null; } - return Long.valueOf(attrs.get("modified").toString()); + return Long.valueOf(metadata.get("modified").toString()); } /** 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 1fdf1e84e115..6fef7365e482 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 @@ -15,6 +15,7 @@ import java.security.DigestInputStream; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.util.List; import java.util.Map; import org.apache.logging.log4j.Logger; @@ -126,13 +127,13 @@ public void put(Bitstream bitstream, InputStream in) throws IOException { /** * Obtain technical metadata about an asset in the asset store. * - * @param bitstream The asset to describe - * @param attrs A Map whose keys consist of desired metadata fields - * @return attrs - * A Map with key/value pairs of desired metadata - * @throws java.io.IOException If a problem occurs while obtaining metadata + * @param bitstream The asset to describe + * @param attrs A List of desired metadata fields + * @return attrs A Map with key/value pairs of desired metadata + * @throws java.io.IOException If a problem occurs while obtaining + * metadata */ - public Map about(Bitstream bitstream, Map attrs) throws IOException { + public Map about(Bitstream bitstream, List attrs) throws IOException { try { // potentially expensive, since it may calculate the checksum File file = getFile(bitstream); diff --git a/dspace-api/src/main/java/org/dspace/storage/bitstore/S3BitStoreService.java b/dspace-api/src/main/java/org/dspace/storage/bitstore/S3BitStoreService.java index 622308b00d9f..d2c9839905cd 100644 --- a/dspace-api/src/main/java/org/dspace/storage/bitstore/S3BitStoreService.java +++ b/dspace-api/src/main/java/org/dspace/storage/bitstore/S3BitStoreService.java @@ -7,6 +7,8 @@ */ package org.dspace.storage.bitstore; +import static java.lang.String.valueOf; + import java.io.File; import java.io.FileOutputStream; import java.io.IOException; @@ -14,6 +16,8 @@ import java.security.DigestInputStream; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.UUID; import java.util.function.Supplier; @@ -26,7 +30,6 @@ import com.amazonaws.regions.Region; import com.amazonaws.regions.Regions; import com.amazonaws.services.s3.AmazonS3; -import com.amazonaws.services.s3.AmazonS3Client; import com.amazonaws.services.s3.AmazonS3ClientBuilder; import com.amazonaws.services.s3.model.AmazonS3Exception; import com.amazonaws.services.s3.model.GetObjectRequest; @@ -42,6 +45,7 @@ import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; import org.apache.commons.codec.binary.Base64; +import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.apache.http.HttpStatus; import org.apache.logging.log4j.LogManager; @@ -208,7 +212,7 @@ public void init() throws IOException { } try { - if (!s3Service.doesBucketExist(bucketName)) { + if (!s3Service.doesBucketExistV2(bucketName)) { s3Service.createBucket(bucketName); log.info("Creating new S3 Bucket: " + bucketName); } @@ -294,10 +298,6 @@ public void put(Bitstream bitstream, InputStream in) throws IOException { ) { Utils.bufferedCopy(dis, fos); in.close(); - byte[] md5Digest = dis.getMessageDigest().digest(); - String md5Base64 = Base64.encodeBase64String(md5Digest); - ObjectMetadata objMetadata = new ObjectMetadata(); - objMetadata.setContentMD5(md5Base64); Upload upload = tm.upload(bucketName, key, scratchFile); @@ -306,7 +306,7 @@ public void put(Bitstream bitstream, InputStream in) throws IOException { bitstream.setSizeBytes(scratchFile.length()); // we cannot use the S3 ETAG here as it could be not a MD5 in case of multipart upload (large files) or if // the bucket is encrypted - bitstream.setChecksum(Utils.toHex(md5Digest)); + bitstream.setChecksum(Utils.toHex(dis.getMessageDigest().digest())); bitstream.setChecksumAlgorithm(CSA); } catch (AmazonClientException | IOException | InterruptedException e) { @@ -329,86 +329,53 @@ public void put(Bitstream bitstream, InputStream in) throws IOException { * (Does not use getContentMD5, as that is 128-bit MD5 digest calculated on caller's side) * * @param bitstream The asset to describe - * @param attrs A Map whose keys consist of desired metadata fields + * @param attrs A List of desired metadata fields * @return attrs * A Map with key/value pairs of desired metadata * If file not found, then return null * @throws java.io.IOException If a problem occurs while obtaining metadata */ @Override - public Map about(Bitstream bitstream, Map attrs) throws IOException { + public Map about(Bitstream bitstream, List attrs) throws IOException { + String key = getFullKey(bitstream.getInternalId()); // If this is a registered bitstream, strip the -R prefix before retrieving if (isRegisteredBitstream(key)) { key = key.substring(REGISTERED_FLAG.length()); } + + Map metadata = new HashMap<>(); + try { + ObjectMetadata objectMetadata = s3Service.getObjectMetadata(bucketName, key); if (objectMetadata != null) { - if (attrs.containsKey("size_bytes")) { - attrs.put("size_bytes", objectMetadata.getContentLength()); - } - if (attrs.containsKey("modified")) { - attrs.put("modified", String.valueOf(objectMetadata.getLastModified().getTime())); - } + putValueIfExistsKey(attrs, metadata, "size_bytes", objectMetadata.getContentLength()); + putValueIfExistsKey(attrs, metadata, "modified", valueOf(objectMetadata.getLastModified().getTime())); } - try ( - InputStream in = get(bitstream); - // Read through a digest input stream that will work out the MD5 - DigestInputStream dis = new DigestInputStream(in, MessageDigest.getInstance(CSA)); - ) { - in.close(); - byte[] md5Digest = dis.getMessageDigest().digest(); - String md5Base64 = Base64.encodeBase64String(md5Digest); - attrs.put("checksum", md5Base64); - attrs.put("checksum_algorithm", CSA); - } catch (NoSuchAlgorithmException nsae) { - // Should never happen - log.warn("Caught NoSuchAlgorithmException", nsae); + + putValueIfExistsKey(attrs, metadata, "checksum_algorithm", CSA); + + if (attrs.contains("checksum")) { + try (InputStream in = get(bitstream)) { + byte[] md5Digest = MessageDigest.getInstance(CSA).digest(IOUtils.toByteArray(in)); + metadata.put("checksum", Base64.encodeBase64String(md5Digest)); + } catch (NoSuchAlgorithmException nsae) { + // Should never happen + log.warn("Caught NoSuchAlgorithmException", nsae); + } } - return attrs; + + return metadata; } catch (AmazonS3Exception e) { if (e.getStatusCode() == HttpStatus.SC_NOT_FOUND) { - return null; + return metadata; } } catch (AmazonClientException e) { log.error("about(" + key + ", attrs)", e); throw new IOException(e); } - return null; - } - - private boolean isMD5Checksum(String eTag) { - // if the etag is NOT an MD5 it end with -x where x is the number of part used in the multipart upload - return StringUtils.contains(eTag, "-"); - } - - /** - * Populates map values by checking key existence - *
- * Adds technical metadata about an asset in the asset store, like: - *
    - *
  • size_bytes
  • - *
  • checksum
  • - *
  • checksum_algorithm
  • - *
  • modified
  • - *
- * - * @param objectMetadata containing technical data - * @param attrs map with keys populated - * @return Map of enriched attrs with values - */ - public Map about(ObjectMetadata objectMetadata, Map attrs) { - if (objectMetadata != null) { - this.putValueIfExistsKey(attrs, SIZE_BYTES, objectMetadata.getContentLength()); - - // put CHECKSUM_ALGORITHM if exists CHECKSUM - this.putValueIfExistsKey(attrs, CHECKSUM, objectMetadata.getETag()); - this.putEntryIfExistsKey(attrs, CHECKSUM, Map.entry(CHECKSUM_ALGORITHM, CSA)); - - this.putValueIfExistsKey(attrs, MODIFIED, String.valueOf(objectMetadata.getLastModified().getTime())); - } - return attrs; + return metadata; } /** @@ -572,13 +539,14 @@ public static void main(String[] args) throws Exception { String accessKey = command.getOptionValue("a"); String secretKey = command.getOptionValue("s"); - String assetFile = command.getOptionValue("f"); S3BitStoreService store = new S3BitStoreService(); AWSCredentials awsCredentials = new BasicAWSCredentials(accessKey, secretKey); - store.s3Service = new AmazonS3Client(awsCredentials); + store.s3Service = AmazonS3ClientBuilder.standard() + .withCredentials(new AWSStaticCredentialsProvider(awsCredentials)) + .build(); //Todo configurable region Region usEast1 = Region.getRegion(Regions.US_EAST_1); diff --git a/dspace-api/src/main/java/org/dspace/storage/bitstore/service/BitstreamStorageService.java b/dspace-api/src/main/java/org/dspace/storage/bitstore/service/BitstreamStorageService.java index 209ef5d16be6..7f5ed8f9129f 100644 --- a/dspace-api/src/main/java/org/dspace/storage/bitstore/service/BitstreamStorageService.java +++ b/dspace-api/src/main/java/org/dspace/storage/bitstore/service/BitstreamStorageService.java @@ -102,7 +102,7 @@ public interface BitstreamStorageService { public UUID register(Context context, Bitstream bitstream, int assetstore, String bitstreamPath) throws SQLException, IOException, AuthorizeException; - public Map computeChecksum(Context context, Bitstream bitstream) throws IOException; + public Map computeChecksum(Context context, Bitstream bitstream) throws IOException; /** * Does the internal_id column in the bitstream row indicate the bitstream diff --git a/dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceIntegrationTest.java b/dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceIT.java similarity index 87% rename from dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceIntegrationTest.java rename to dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceIT.java index f362e94dddc7..7e14f82be289 100644 --- a/dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceIntegrationTest.java +++ b/dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceIT.java @@ -13,6 +13,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasEntry; import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; @@ -27,6 +28,8 @@ import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.sql.SQLException; +import java.util.List; +import java.util.Map; import com.amazonaws.auth.AWSStaticCredentialsProvider; import com.amazonaws.auth.AnonymousAWSCredentials; @@ -37,6 +40,7 @@ import com.amazonaws.services.s3.model.Bucket; import com.amazonaws.services.s3.model.ObjectMetadata; import io.findify.s3mock.S3Mock; +import org.apache.commons.codec.binary.Base64; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.dspace.AbstractIntegrationTestWithDatabase; @@ -60,7 +64,7 @@ /** * @author Luca Giamminonni (luca.giamminonni at 4science.com) */ -public class S3BitStoreServiceIntegrationTest extends AbstractIntegrationTestWithDatabase { +public class S3BitStoreServiceIT extends AbstractIntegrationTestWithDatabase { private static final String DEFAULT_BUCKET_NAME = "dspace-asset-localhost"; @@ -122,7 +126,7 @@ public void testBitstreamPutAndGetWithAlreadyPresentBucket() throws IOException s3BitStoreService.put(bitstream, toInputStream(content)); - String expectedChecksum = generateChecksum(content); + String expectedChecksum = Utils.toHex(generateChecksum(content)); assertThat(bitstream.getSizeBytes(), is((long) content.length())); assertThat(bitstream.getChecksum(), is(expectedChecksum)); @@ -153,7 +157,7 @@ public void testBitstreamPutAndGetWithoutSpecifingBucket() throws IOException { s3BitStoreService.put(bitstream, toInputStream(content)); - String expectedChecksum = generateChecksum(content); + String expectedChecksum = Utils.toHex(generateChecksum(content)); assertThat(bitstream.getSizeBytes(), is((long) content.length())); assertThat(bitstream.getChecksum(), is(expectedChecksum)); @@ -214,6 +218,47 @@ public void testBitstreamDeletion() throws IOException { } + @Test + public void testAbout() throws IOException { + + s3BitStoreService.init(); + + context.turnOffAuthorisationSystem(); + String content = "Test bitstream content"; + Bitstream bitstream = createBitstream(content); + context.restoreAuthSystemState(); + + s3BitStoreService.put(bitstream, toInputStream(content)); + + Map about = s3BitStoreService.about(bitstream, List.of()); + assertThat(about.size(), is(0)); + + about = s3BitStoreService.about(bitstream, List.of("size_bytes")); + assertThat(about, hasEntry("size_bytes", 22L)); + assertThat(about.size(), is(1)); + + about = s3BitStoreService.about(bitstream, List.of("size_bytes", "modified")); + assertThat(about, hasEntry("size_bytes", 22L)); + assertThat(about, hasEntry(is("modified"), notNullValue())); + assertThat(about.size(), is(2)); + + String expectedChecksum = Base64.encodeBase64String(generateChecksum(content)); + + about = s3BitStoreService.about(bitstream, List.of("size_bytes", "modified", "checksum")); + assertThat(about, hasEntry("size_bytes", 22L)); + assertThat(about, hasEntry(is("modified"), notNullValue())); + assertThat(about, hasEntry("checksum", expectedChecksum)); + assertThat(about.size(), is(3)); + + about = s3BitStoreService.about(bitstream, List.of("size_bytes", "modified", "checksum", "checksum_algorithm")); + assertThat(about, hasEntry("size_bytes", 22L)); + assertThat(about, hasEntry(is("modified"), notNullValue())); + assertThat(about, hasEntry("checksum", expectedChecksum)); + assertThat(about, hasEntry("checksum_algorithm", CSA)); + assertThat(about.size(), is(4)); + + } + @Test public void handleRegisteredIdentifierPrefixInS3() { String trueBitStreamId = "012345"; @@ -338,11 +383,11 @@ public void givenBitStreamIdentifierWithSlashesWhenSanitizedThenSlashesMustBeRem assertThat(computedPath, Matchers.not(Matchers.containsString(File.separator))); } - private String generateChecksum(String content) { + private byte[] generateChecksum(String content) { try { MessageDigest m = MessageDigest.getInstance("MD5"); m.update(content.getBytes()); - return Utils.toHex(m.digest()); + return m.digest(); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } From 191d3700bb083129c7bd925be2a16df852f9049e Mon Sep 17 00:00:00 2001 From: Francesco Pio Scognamiglio Date: Thu, 9 Feb 2023 14:45:42 +0100 Subject: [PATCH 047/510] [CST-6402] manage saf import via remote url --- .../org/dspace/app/itemimport/ItemImport.java | 27 ++++++++++++++++--- .../ItemImportScriptConfiguration.java | 6 ++++- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImport.java b/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImport.java index 6870b94eee1d..2b1089e3e01f 100644 --- a/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImport.java +++ b/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImport.java @@ -11,6 +11,7 @@ import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; +import java.net.URL; import java.nio.file.Files; import java.sql.SQLException; import java.util.ArrayList; @@ -74,6 +75,7 @@ public class ItemImport extends DSpaceRunnable { protected boolean isQuiet = false; protected boolean commandLineCollections = false; protected boolean zip = false; + protected boolean remoteUrl = false; protected String zipfilename = null; protected boolean help = false; protected File workDir = null; @@ -253,6 +255,17 @@ public void internalRun() throws Exception { * @param context */ protected void validate(Context context) { + // check zip type: uploaded file or remote url + if (commandLine.hasOption('z')) { + zipfilename = commandLine.getOptionValue('z'); + } else if (commandLine.hasOption('u')) { + remoteUrl = true; + zipfilename = commandLine.getOptionValue('u'); + } + if (StringUtils.isBlank(zipfilename)) { + throw new UnsupportedOperationException("Must run with either name of zip file or url of zip file"); + } + if (command == null) { handler.logError("Must run with either add, replace, or remove (run with -h flag for details)"); throw new UnsupportedOperationException("Must run with either add, replace, or remove"); @@ -306,17 +319,24 @@ protected void process(Context context, ItemImportService itemImportService, * @throws Exception */ protected void readZip(Context context, ItemImportService itemImportService) throws Exception { - Optional optionalFileStream = handler.getFileStream(context, zipfilename); + Optional optionalFileStream = Optional.empty(); + if (!remoteUrl) { + // manage zip via upload + optionalFileStream = handler.getFileStream(context, zipfilename); + } else { + // manage zip via remote url + optionalFileStream = Optional.ofNullable(new URL(zipfilename).openStream()); + } if (optionalFileStream.isPresent()) { workFile = new File(itemImportService.getTempWorkDir() + File.separator + zipfilename + "-" + context.getCurrentUser().getID()); FileUtils.copyInputStreamToFile(optionalFileStream.get(), workFile); - workDir = new File(itemImportService.getTempWorkDir() + File.separator + TEMP_DIR); - sourcedir = itemImportService.unzip(workFile, workDir.getAbsolutePath()); } else { throw new IllegalArgumentException( "Error reading file, the file couldn't be found for filename: " + zipfilename); } + workDir = new File(itemImportService.getTempWorkDir() + File.separator + TEMP_DIR); + sourcedir = itemImportService.unzip(workFile, workDir.getAbsolutePath()); } /** @@ -356,7 +376,6 @@ protected void setMapFile() throws IOException { */ protected void setZip() { zip = true; - zipfilename = commandLine.getOptionValue('z'); } /** diff --git a/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportScriptConfiguration.java b/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportScriptConfiguration.java index a3149040c49b..cfe97ad89bd6 100644 --- a/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportScriptConfiguration.java +++ b/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportScriptConfiguration.java @@ -64,7 +64,11 @@ public Options getOptions() { options.addOption(Option.builder("z").longOpt("zip") .desc("name of zip file") .type(InputStream.class) - .hasArg().required().build()); + .hasArg().build()); + options.addOption(Option.builder("u").longOpt("url") + .desc("url of zip file") + .type(InputStream.class) + .hasArg().build()); options.addOption(Option.builder("c").longOpt("collection") .desc("destination collection(s) Handle or database ID") .hasArg().required(false).build()); From 47fab88c17d8ac821f99c0d597cc6e8d3c153a8a Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Fri, 24 Feb 2023 17:30:28 +0100 Subject: [PATCH 048/510] 89779: VersionedHandleIdentifierProviderWithCanonicalHandles fix pt1 --- .../spring/spring-dspace-core-services.xml | 15 --- ...VersionedHandleIdentifierProviderTest.java | 100 ++++++++++++++++++ .../config/spring/api/identifier-service.xml | 2 - 3 files changed, 100 insertions(+), 17 deletions(-) create mode 100644 dspace-api/src/test/java/org/dspace/identifier/VersionedHandleIdentifierProviderTest.java diff --git a/dspace-api/src/main/resources/spring/spring-dspace-core-services.xml b/dspace-api/src/main/resources/spring/spring-dspace-core-services.xml index 87bfcbc86c98..3ce641d99c34 100644 --- a/dspace-api/src/main/resources/spring/spring-dspace-core-services.xml +++ b/dspace-api/src/main/resources/spring/spring-dspace-core-services.xml @@ -13,15 +13,6 @@ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"> - - - - @@ -31,12 +22,6 @@ - - - - diff --git a/dspace-api/src/test/java/org/dspace/identifier/VersionedHandleIdentifierProviderTest.java b/dspace-api/src/test/java/org/dspace/identifier/VersionedHandleIdentifierProviderTest.java new file mode 100644 index 000000000000..9db55bb31234 --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/identifier/VersionedHandleIdentifierProviderTest.java @@ -0,0 +1,100 @@ +package org.dspace.identifier; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import java.sql.SQLException; +import java.util.List; + +import org.dspace.AbstractIntegrationTestWithDatabase; +import org.dspace.authorize.AuthorizeException; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.ItemBuilder; +import org.dspace.builder.VersionBuilder; +import org.dspace.content.Collection; +import org.dspace.content.Item; +import org.dspace.identifier.service.IdentifierService; +import org.dspace.kernel.ServiceManager; +import org.dspace.services.ConfigurationService; +import org.dspace.services.factory.DSpaceServicesFactory; +import org.dspace.utils.DSpace; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +public class VersionedHandleIdentifierProviderTest extends AbstractIntegrationTestWithDatabase { + private ServiceManager serviceManager; + + private String handlePrefix; + + private Collection collection; + private Item itemV1; + private Item itemV2; + private Item itemV3; + + @Before + @Override + public void setUp() throws Exception { + super.setUp(); + context.turnOffAuthorisationSystem(); + + ConfigurationService configurationService = new DSpace().getConfigurationService(); + handlePrefix = configurationService.getProperty("handle.prefix"); + + serviceManager = DSpaceServicesFactory.getInstance().getServiceManager(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + collection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection") + .build(); + } + + private void registerProvider(Class type) { + // Register our new provider + serviceManager.registerServiceClass(type.getName(), type); + IdentifierProvider identifierProvider = + (IdentifierProvider) serviceManager.getServiceByName(type.getName(), type); + + // Overwrite the identifier-service's providers with the new one to ensure only this provider is used + IdentifierServiceImpl identifierService = serviceManager.getServicesByType(IdentifierServiceImpl.class).get(0); + identifierService.setProviders(List.of(identifierProvider)); + } + + private void createVersions() throws SQLException, AuthorizeException { + itemV1 = ItemBuilder.createItem(context, collection) + .withTitle("First version") + .build(); + itemV2 = VersionBuilder.createVersion(context, itemV1, "Second version").build().getItem(); + itemV3 = VersionBuilder.createVersion(context, itemV1, "Third version").build().getItem(); + } + + @Test + public void testDefaultVersionedHandleProvider() throws Exception { + registerProvider(VersionedHandleIdentifierProvider.class); + createVersions(); + + assertEquals(handlePrefix + "/1", itemV1.getHandle()); + assertEquals(handlePrefix + "/1.2", itemV2.getHandle()); + assertEquals(handlePrefix + "/1.3", itemV3.getHandle()); + } + + @Test + public void testCanonicalVersionedHandleProvider() throws Exception { + registerProvider(VersionedHandleIdentifierProviderWithCanonicalHandles.class); + createVersions(); + + assertEquals(handlePrefix + "/1.3", itemV1.getHandle()); + assertEquals(handlePrefix + "/1.2", itemV2.getHandle()); + assertEquals(handlePrefix + "/1", itemV3.getHandle()); + } + + @After + @Override + public void destroy() throws Exception { + super.destroy(); + // serviceManager.getApplicationContext().refresh(); + } +} diff --git a/dspace/config/spring/api/identifier-service.xml b/dspace/config/spring/api/identifier-service.xml index e9f08003bd63..dd5716a62e9b 100644 --- a/dspace/config/spring/api/identifier-service.xml +++ b/dspace/config/spring/api/identifier-service.xml @@ -17,11 +17,9 @@ The VersionedHandleIdentifierProvider creates a new versioned handle for every new version. --> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - dc.rights - - - - - - - - - - - - - - - dc.rights - - - - - - - - dc.description.provenance - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - search.resourcetype:Item OR search.resourcetype:Collection OR search.resourcetype:Community - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - dc.title - dc.contributor.author - dc.creator - dc.subject - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - search.resourcetype:Item - - withdrawn:true OR discoverable:false - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - dc.title - dc.contributor.author - dc.creator - dc.subject - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - search.resourcetype:Item - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - dc.title - dc.contributor.author - dc.creator - dc.subject - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - search.resourcetype:Item OR search.resourcetype:WorkspaceItem OR search.resourcetype:XmlWorkflowItem - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - search.resourcetype:PoolTask OR search.resourcetype:ClaimedTask - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - search.resourcetype:XmlWorkflowItem - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - search.resourcetype:Item AND entityType_keyword:Publication - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - search.resourcetype:Item AND entityType_keyword:Person - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - search.resourcetype:Item AND entityType_keyword:Project - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - search.resourcetype:Item AND entityType_keyword:OrgUnit - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - search.resourcetype:Item AND entityType_keyword:JournalIssue - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - search.resourcetype:Item AND entityType_keyword:JournalVolume - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - search.resourcetype:Item AND entityType_keyword:Journal - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - search.resourcetype:Item AND (entityType_keyword:OrgUnit OR entityType_keyword:Person) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - search.resourcetype:Item AND entityType_keyword:OrgUnit AND dc.type:FundingOrganization - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - search.resourcetype:Item OR search.resourcetype:Collection OR search.resourcetype:Community - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - dc.title - dc.contributor.author - dc.creator - dc.subject - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - search.resourcetype:Item OR search.resourcetype:Collection OR search.resourcetype:Community - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - dc.title - dc.contributor.author - dc.creator - dc.subject - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - search.resourcetype:Item OR search.resourcetype:Collection OR search.resourcetype:Community - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - dc.title - dc.contributor.author - dc.creator - dc.subject - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - search.resourcetype:Item OR search.resourcetype:Collection OR search.resourcetype:Community - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - dc.title - dc.contributor.author - dc.creator - dc.subject - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - search.resourcetype:Item OR search.resourcetype:Collection OR search.resourcetype:Community - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - dc.title - dc.contributor.author - dc.creator - dc.subject - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - search.resourcetype:Item OR search.resourcetype:Collection OR search.resourcetype:Community - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - dc.title - dc.contributor.author - dc.creator - dc.subject - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - search.resourcetype:Item OR search.resourcetype:Collection OR search.resourcetype:Community - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - dc.title - dc.contributor.author - dc.creator - dc.subject - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - dc.title - - - - - - - - - - - relation.isAuthorOfPublication - - - - - - - - - - - relation.isProjectOfPublication - - - - - - - - - - - - relation.isOrgUnitOfPublication - - - - - - - - - - - relation.isPublicationOfJournalIssue - - - - - - - - - - - relation.isJournalOfPublication - - - - - - - - - - - dc.contributor.author - dc.creator - - - - - - - - - - - - - - - dspace.entity.type - - - - - - - - - - - - - - dc.subject.* - - - - - - - - - - - - - - dc.date.issued - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - dc.type - - - - - - - - - dc.identifier - - - - - - - - - placeholder.placeholder.placeholder - - - - - - - - - - placeholder.placeholder.placeholder - - - - - - - - - person.jobTitle - - - - - - - - - - - - - - - person.knowsLanguage - - - - - - - - - - - - - person.birthDate - - - - - - - - - - - - - - - - - person.familyName - - - - - - - - - - - person.givenName - - - - - - - - - - - relation.isOrgUnitOfPerson - - - - - - - - - - - relation.isProjectOfPerson - - - - - - - - - - - relation.isPublicationOfAuthor - - - - - - - - - - - - organization.address.addressCountry - - - - - - - - - - - - - - - organization.address.addressLocality - - - - - - - - - - - - - - - organization.foundingDate - - - - - - - - - - - - - - - - organization.legalName - - - - - - - - - - - relation.isPersonOfOrgUnit - - - - - - - - - - - relation.isProjectOfOrgUnit - - - - - - - - - - - relation.isPublicationOfOrgUnit - - - - - - - - - - - creativework.keywords - - - - - - - - - - - - - - - creativework.datePublished - - - - - - - - - - - - - - - - publicationissue.issueNumber - - - - - - - - - - - relation.isPublicationOfJournalIssue - - - - - - - - - - - publicationVolume.volumeNumber - - - - - - - - - - - relation.isIssueOfJournalVolume - - - - - - - - - - - relation.isJournalOfVolume - - - - - - - - - - - creativework.publisher - - - - - - - - - - - - - - - creativework.editor - - - - - - - - - - - - - - - relation.isVolumeOfJournal - - - - - - - - - - - - - - placeholder.placeholder.placeholder - - - - - - - - - - relation.isOrgUnitOfProject - - - - - - - - - - - - relation.isPersonOfProject - - - - - - - - - - - - relation.isPublicationOfProject - - - - - - - - - - - relation.isContributorOfPublication - - - - - - - - - - - relation.isPublicationOfContributor - - - - - - - - - - - relation.isFundingAgencyOfProject - - - - - - - - - - - relation.isProjectOfFundingAgency - - - - - - - - - - - dc.test.parentcommunity1field - - - - - - - - - - - - - - - dc.test.subcommunity11field - - - - - - - - - - - - - - - dc.test.collection111field - - - - - - - - - - - - - - - dc.test.collection121field - - - - - - - - - - - - - - - dc.test.subcommunity21field - - - - - - - - - - - - - - dc.test.collection211field - - - - - - - - - - - - - - dc.test.collection221field - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryScopeBasedRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryScopeBasedRestControllerIT.java index 15c1019584b1..0c8735545e8b 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryScopeBasedRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryScopeBasedRestControllerIT.java @@ -272,7 +272,7 @@ public void ScopeBasedIndexingAndSearchTestParentCommunity1() throws Exception { .andExpect(jsonPath("$._links.self.href", containsString("api/discover/facets"))) .andExpect(jsonPath("$._embedded.facets", containsInAnyOrder( FacetEntryMatcher.authorFacet(false), - FacetEntryMatcher.matchFacet("parentcommunity1field", "text", false))) + FacetEntryMatcher.matchFacet(false, "parentcommunity1field", "text"))) ); getClient().perform(get("/api/discover/facets/parentcommunity1field") @@ -310,7 +310,7 @@ public void ScopeBasedIndexingAndSearchTestSubCommunity11() throws Exception { .andExpect(jsonPath("$._links.self.href", containsString("api/discover/facets"))) .andExpect(jsonPath("$._embedded.facets", containsInAnyOrder( FacetEntryMatcher.authorFacet(false), - FacetEntryMatcher.matchFacet("subcommunity11field", "text", false))) + FacetEntryMatcher.matchFacet(false, "subcommunity11field", "text"))) ); getClient().perform(get("/api/discover/facets/subcommunity11field") @@ -339,7 +339,7 @@ public void ScopeBasedIndexingAndSearchTestCollection111() throws Exception { .andExpect(jsonPath("$._links.self.href", containsString("api/discover/facets"))) .andExpect(jsonPath("$._embedded.facets", containsInAnyOrder( FacetEntryMatcher.authorFacet(false), - FacetEntryMatcher.matchFacet("collection111field", "text", false))) + FacetEntryMatcher.matchFacet(false, "collection111field", "text"))) ); getClient().perform(get("/api/discover/facets/collection111field") @@ -366,7 +366,7 @@ public void ScopeBasedIndexingAndSearchTestCollection112() throws Exception { .andExpect(jsonPath("$._links.self.href", containsString("api/discover/facets"))) .andExpect(jsonPath("$._embedded.facets", containsInAnyOrder( FacetEntryMatcher.authorFacet(false), - FacetEntryMatcher.matchFacet("subcommunity11field", "text", false))) + FacetEntryMatcher.matchFacet(false, "subcommunity11field", "text"))) ); getClient().perform(get("/api/discover/facets/subcommunity11field") @@ -391,7 +391,7 @@ public void ScopeBasedIndexingAndSearchTestSubcommunity12() throws Exception { .andExpect(jsonPath("$._links.self.href", containsString("api/discover/facets"))) .andExpect(jsonPath("$._embedded.facets", containsInAnyOrder( FacetEntryMatcher.authorFacet(false), - FacetEntryMatcher.matchFacet("parentcommunity1field", "text", false))) + FacetEntryMatcher.matchFacet(false, "parentcommunity1field", "text"))) ); getClient().perform(get("/api/discover/facets/parentcommunity1field") @@ -420,7 +420,7 @@ public void ScopeBasedIndexingAndSearchTestCollection121() throws Exception { .andExpect(jsonPath("$._links.self.href", containsString("api/discover/facets"))) .andExpect(jsonPath("$._embedded.facets", containsInAnyOrder( FacetEntryMatcher.authorFacet(false), - FacetEntryMatcher.matchFacet("collection121field", "text", false))) + FacetEntryMatcher.matchFacet(false, "collection121field", "text"))) ); getClient().perform(get("/api/discover/facets/collection121field") @@ -445,7 +445,7 @@ public void ScopeBasedIndexingAndSearchTestCollection122() throws Exception { .andExpect(jsonPath("$._links.self.href", containsString("api/discover/facets"))) .andExpect(jsonPath("$._embedded.facets", containsInAnyOrder( FacetEntryMatcher.authorFacet(false), - FacetEntryMatcher.matchFacet("parentcommunity1field", "text", false))) + FacetEntryMatcher.matchFacet(false, "parentcommunity1field", "text"))) ); getClient().perform(get("/api/discover/facets/parentcommunity1field") @@ -490,7 +490,7 @@ public void ScopeBasedIndexingAndSearchTestSubCommunity21() throws Exception { .andExpect(jsonPath("$._links.self.href", containsString("api/discover/facets"))) .andExpect(jsonPath("$._embedded.facets", containsInAnyOrder( FacetEntryMatcher.authorFacet(false), - FacetEntryMatcher.matchFacet("subcommunity21field", "text", false))) + FacetEntryMatcher.matchFacet(false, "subcommunity21field", "text"))) ); getClient().perform(get("/api/discover/facets/subcommunity21field") @@ -519,7 +519,7 @@ public void ScopeBasedIndexingAndSearchTestCollection211() throws Exception { .andExpect(jsonPath("$._links.self.href", containsString("api/discover/facets"))) .andExpect(jsonPath("$._embedded.facets", containsInAnyOrder( FacetEntryMatcher.authorFacet(false), - FacetEntryMatcher.matchFacet("collection211field", "text", false))) + FacetEntryMatcher.matchFacet(false, "collection211field", "text"))) ); getClient().perform(get("/api/discover/facets/collection211field") @@ -546,7 +546,7 @@ public void ScopeBasedIndexingAndSearchTestCollection212() throws Exception { .andExpect(jsonPath("$._links.self.href", containsString("api/discover/facets"))) .andExpect(jsonPath("$._embedded.facets", containsInAnyOrder( FacetEntryMatcher.authorFacet(false), - FacetEntryMatcher.matchFacet("subcommunity21field", "text", false))) + FacetEntryMatcher.matchFacet(false, "subcommunity21field", "text"))) ); getClient().perform(get("/api/discover/facets/subcommunity21field") @@ -588,7 +588,7 @@ public void ScopeBasedIndexingAndSearchTestCollection221() throws Exception { .andExpect(jsonPath("$._links.self.href", containsString("api/discover/facets"))) .andExpect(jsonPath("$._embedded.facets", containsInAnyOrder( FacetEntryMatcher.authorFacet(false), - FacetEntryMatcher.matchFacet("collection221field", "text", false))) + FacetEntryMatcher.matchFacet(false, "collection221field", "text"))) ); getClient().perform(get("/api/discover/facets/collection221field") diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/FacetEntryMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/FacetEntryMatcher.java index 34b7b8b30d6a..60b5f417ed1e 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/FacetEntryMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/FacetEntryMatcher.java @@ -99,7 +99,7 @@ public static Matcher hasContentInOriginalBundleFacet(boolean ha ); } - public static Matcher matchFacet(String name, String facetType, boolean hasNext) { + public static Matcher matchFacet(boolean hasNext, String name, String facetType) { return allOf( hasJsonPath("$.name", is(name)), hasJsonPath("$.facetType", is(facetType)), From e433720cd005ddf6b6e13ce09988c72500a74115 Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Wed, 5 Apr 2023 14:07:52 +0200 Subject: [PATCH 075/510] Add test-discovery xml --- .../config/spring/api/test-discovery.xml | 1115 +++++++++++++++++ 1 file changed, 1115 insertions(+) create mode 100644 dspace-server-webapp/src/test/data/dspaceFolder/config/spring/api/test-discovery.xml diff --git a/dspace-server-webapp/src/test/data/dspaceFolder/config/spring/api/test-discovery.xml b/dspace-server-webapp/src/test/data/dspaceFolder/config/spring/api/test-discovery.xml new file mode 100644 index 000000000000..8b11a87e2d0b --- /dev/null +++ b/dspace-server-webapp/src/test/data/dspaceFolder/config/spring/api/test-discovery.xml @@ -0,0 +1,1115 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + dc.rights + + + + + + + + + + + + + + + dc.rights + + + + + + + + dc.description.provenance + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + search.resourcetype:Item OR search.resourcetype:Collection OR search.resourcetype:Community + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + dc.title + dc.contributor.author + dc.creator + dc.subject + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + search.resourcetype:Item OR search.resourcetype:Collection OR search.resourcetype:Community + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + dc.title + dc.contributor.author + dc.creator + dc.subject + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + search.resourcetype:Item OR search.resourcetype:Collection OR search.resourcetype:Community + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + dc.title + dc.contributor.author + dc.creator + dc.subject + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + search.resourcetype:Item OR search.resourcetype:Collection OR search.resourcetype:Community + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + dc.title + dc.contributor.author + dc.creator + dc.subject + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + search.resourcetype:Item OR search.resourcetype:Collection OR search.resourcetype:Community + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + dc.title + dc.contributor.author + dc.creator + dc.subject + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + search.resourcetype:Item OR search.resourcetype:Collection OR search.resourcetype:Community + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + dc.title + dc.contributor.author + dc.creator + dc.subject + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + search.resourcetype:Item OR search.resourcetype:Collection OR search.resourcetype:Community + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + dc.title + dc.contributor.author + dc.creator + dc.subject + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + dc.test.parentcommunity1field + + + + + + + + + + + + + + + dc.test.subcommunity11field + + + + + + + + + + + + + + + dc.test.collection111field + + + + + + + + + + + + + + + dc.test.collection121field + + + + + + + + + + + + + + + dc.test.subcommunity21field + + + + + + + + + + + + + + dc.test.collection211field + + + + + + + + + + + + + + dc.test.collection221field + + + + + + + + + + + From cf831ed7d5892fb86b1569b8c02c406e546e2e8e Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Fri, 7 Apr 2023 16:26:12 +0200 Subject: [PATCH 076/510] Fix merge issues --- dspace-api/src/main/java/org/dspace/browse/SolrBrowseDAO.java | 2 +- .../data/dspaceFolder/config/spring/api/test-discovery.xml | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) 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 e02367f6eb8e..f99aab852bf5 100644 --- a/dspace-api/src/main/java/org/dspace/browse/SolrBrowseDAO.java +++ b/dspace-api/src/main/java/org/dspace/browse/SolrBrowseDAO.java @@ -239,7 +239,7 @@ private void addLocationScopeFilter(DiscoverQuery query) { } private void addDefaultFilterQueries(DiscoverQuery query) { - DiscoveryConfiguration discoveryConfiguration = SearchUtils.getDiscoveryConfiguration(container); + DiscoveryConfiguration discoveryConfiguration = SearchUtils.getDiscoveryConfiguration(context, container); discoveryConfiguration.getDefaultFilterQueries().forEach(query::addFilterQueries); } diff --git a/dspace-server-webapp/src/test/data/dspaceFolder/config/spring/api/test-discovery.xml b/dspace-server-webapp/src/test/data/dspaceFolder/config/spring/api/test-discovery.xml index 8b11a87e2d0b..4a91ef051e88 100644 --- a/dspace-server-webapp/src/test/data/dspaceFolder/config/spring/api/test-discovery.xml +++ b/dspace-server-webapp/src/test/data/dspaceFolder/config/spring/api/test-discovery.xml @@ -39,10 +39,13 @@ + + + From 9ef37940905154e5f838fbe559c9588617060e7f Mon Sep 17 00:00:00 2001 From: corrado lombardi Date: Wed, 12 Apr 2023 15:29:52 +0200 Subject: [PATCH 077/510] updated checksum calculation --- .../java/org/dspace/storage/bitstore/S3BitStoreService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/storage/bitstore/S3BitStoreService.java b/dspace-api/src/main/java/org/dspace/storage/bitstore/S3BitStoreService.java index d2c9839905cd..bf46687ee97e 100644 --- a/dspace-api/src/main/java/org/dspace/storage/bitstore/S3BitStoreService.java +++ b/dspace-api/src/main/java/org/dspace/storage/bitstore/S3BitStoreService.java @@ -359,7 +359,7 @@ public Map about(Bitstream bitstream, List attrs) throws if (attrs.contains("checksum")) { try (InputStream in = get(bitstream)) { byte[] md5Digest = MessageDigest.getInstance(CSA).digest(IOUtils.toByteArray(in)); - metadata.put("checksum", Base64.encodeBase64String(md5Digest)); + metadata.put("checksum", Utils.toHex(md5Digest)); } catch (NoSuchAlgorithmException nsae) { // Should never happen log.warn("Caught NoSuchAlgorithmException", nsae); From d8357d77aa4cf329c6c8aa7f1b7aa09e1de65359 Mon Sep 17 00:00:00 2001 From: corrado lombardi Date: Wed, 12 Apr 2023 18:45:27 +0200 Subject: [PATCH 078/510] used utils.copy to copy input stream content in order to prevent oom problems --- .../org/dspace/storage/bitstore/S3BitStoreService.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/storage/bitstore/S3BitStoreService.java b/dspace-api/src/main/java/org/dspace/storage/bitstore/S3BitStoreService.java index bf46687ee97e..7a09dd2e76df 100644 --- a/dspace-api/src/main/java/org/dspace/storage/bitstore/S3BitStoreService.java +++ b/dspace-api/src/main/java/org/dspace/storage/bitstore/S3BitStoreService.java @@ -44,8 +44,7 @@ import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; -import org.apache.commons.codec.binary.Base64; -import org.apache.commons.io.IOUtils; +import org.apache.commons.io.output.NullOutputStream; import org.apache.commons.lang3.StringUtils; import org.apache.http.HttpStatus; import org.apache.logging.log4j.LogManager; @@ -357,8 +356,11 @@ public Map about(Bitstream bitstream, List attrs) throws putValueIfExistsKey(attrs, metadata, "checksum_algorithm", CSA); if (attrs.contains("checksum")) { - try (InputStream in = get(bitstream)) { - byte[] md5Digest = MessageDigest.getInstance(CSA).digest(IOUtils.toByteArray(in)); + try (InputStream in = get(bitstream); + DigestInputStream dis = new DigestInputStream(in, MessageDigest.getInstance(CSA)) + ) { + Utils.copy(dis, NullOutputStream.NULL_OUTPUT_STREAM); + byte[] md5Digest = dis.getMessageDigest().digest(); metadata.put("checksum", Utils.toHex(md5Digest)); } catch (NoSuchAlgorithmException nsae) { // Should never happen From 4685c9fc8d362cd2668d70cdb335bba00a32e432 Mon Sep 17 00:00:00 2001 From: corrado lombardi Date: Wed, 12 Apr 2023 19:20:40 +0200 Subject: [PATCH 079/510] updated test expectation --- .../java/org/dspace/storage/bitstore/S3BitStoreServiceIT.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceIT.java b/dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceIT.java index 7e14f82be289..60793ac610bd 100644 --- a/dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceIT.java +++ b/dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceIT.java @@ -242,7 +242,7 @@ public void testAbout() throws IOException { assertThat(about, hasEntry(is("modified"), notNullValue())); assertThat(about.size(), is(2)); - String expectedChecksum = Base64.encodeBase64String(generateChecksum(content)); + String expectedChecksum = Utils.toHex(generateChecksum(content)); about = s3BitStoreService.about(bitstream, List.of("size_bytes", "modified", "checksum")); assertThat(about, hasEntry("size_bytes", 22L)); From 77d552b02997ee4c36dc277f23b54880a3605ee9 Mon Sep 17 00:00:00 2001 From: Chandaluri Vamsi Krishna Date: Thu, 13 Apr 2023 09:50:57 +0530 Subject: [PATCH 080/510] Added logic for default description Signed-off-by: Chandaluri Vamsi Krishna --- .../org/dspace/app/util/SyndicationFeed.java | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/util/SyndicationFeed.java b/dspace-api/src/main/java/org/dspace/app/util/SyndicationFeed.java index 8f155b63307d..5b0520a3e1d1 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/SyndicationFeed.java +++ b/dspace-api/src/main/java/org/dspace/app/util/SyndicationFeed.java @@ -91,6 +91,7 @@ public class SyndicationFeed { // default DC fields for entry protected String defaultTitleField = "dc.title"; + protected String defaultDescriptionField = "dc.description"; protected String defaultAuthorField = "dc.contributor.author"; protected String defaultDateField = "dc.date.issued"; private static final String[] defaultDescriptionFields = @@ -196,15 +197,15 @@ public void populate(HttpServletRequest request, Context context, IndexableObjec // dso is null for the whole site, or a search without scope if (dso == null) { defaultTitle = configurationService.getProperty("dspace.name"); - feed.setDescription(localize(labels, MSG_FEED_DESCRIPTION)); + defaultDescriptionField = localize(labels, MSG_FEED_DESCRIPTION); objectURL = resolveURL(request, null); } else { Bitstream logo = null; if (dso instanceof IndexableCollection) { Collection col = ((IndexableCollection) dso).getIndexedObject(); defaultTitle = col.getName(); - feed.setDescription(collectionService.getMetadataFirstValue(col, - CollectionService.MD_SHORT_DESCRIPTION, Item.ANY)); + defaultDescriptionField = collectionService.getMetadataFirstValue(col, + CollectionService.MD_SHORT_DESCRIPTION, Item.ANY); logo = col.getLogo(); String cols = configurationService.getProperty("webui.feed.podcast.collections"); if (cols != null && cols.length() > 1 && cols.contains(col.getHandle())) { @@ -214,8 +215,8 @@ public void populate(HttpServletRequest request, Context context, IndexableObjec } else if (dso instanceof IndexableCommunity) { Community comm = ((IndexableCommunity) dso).getIndexedObject(); defaultTitle = comm.getName(); - feed.setDescription(communityService.getMetadataFirstValue(comm, - CommunityService.MD_SHORT_DESCRIPTION, Item.ANY)); + defaultDescriptionField = communityService.getMetadataFirstValue(comm, + CommunityService.MD_SHORT_DESCRIPTION, Item.ANY); logo = comm.getLogo(); String comms = configurationService.getProperty("webui.feed.podcast.communities"); if (comms != null && comms.length() > 1 && comms.contains(comm.getHandle())) { @@ -230,6 +231,12 @@ public void populate(HttpServletRequest request, Context context, IndexableObjec } feed.setTitle(labels.containsKey(MSG_FEED_TITLE) ? localize(labels, MSG_FEED_TITLE) : defaultTitle); + + if (defaultDescriptionField == null || defaultDescriptionField == "") { + defaultDescriptionField = "No Description"; + } + + feed.setDescription(defaultDescriptionField); feed.setLink(objectURL); feed.setPublishedDate(new Date()); feed.setUri(objectURL); From 9a345d3e6426bbd8d2b70e02586447cbe2439cff Mon Sep 17 00:00:00 2001 From: Chandaluri Vamsi Krishna Date: Thu, 13 Apr 2023 09:52:14 +0530 Subject: [PATCH 081/510] Testing of default description Signed-off-by: Chandaluri Vamsi Krishna --- .../opensearch/OpenSearchControllerIT.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/opensearch/OpenSearchControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/opensearch/OpenSearchControllerIT.java index ac03e946e320..1ddea619d2fc 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/opensearch/OpenSearchControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/opensearch/OpenSearchControllerIT.java @@ -249,4 +249,24 @@ public void serviceDocumentTest() throws Exception { */ } + + @Test + public void emptyDescriptionTest() throws Exception { + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection collection1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1") + .build(); + + getClient().perform(get("/opensearch/search") + .param("format", "rss") + .param("scope", collection1.getID().toString()) + .param("query", "*")) + .andExpect(status().isOk()) + .andExpect(xpath("rss/channel/description").string("No Description")); + } } From b2b4dda243b4dc4689e8d727a7f933ba6d0d603c Mon Sep 17 00:00:00 2001 From: corrado lombardi Date: Thu, 13 Apr 2023 08:35:38 +0200 Subject: [PATCH 082/510] checkstyle fix --- .../java/org/dspace/storage/bitstore/S3BitStoreServiceIT.java | 1 - 1 file changed, 1 deletion(-) diff --git a/dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceIT.java b/dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceIT.java index 60793ac610bd..7aae1cf2719c 100644 --- a/dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceIT.java +++ b/dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceIT.java @@ -40,7 +40,6 @@ import com.amazonaws.services.s3.model.Bucket; import com.amazonaws.services.s3.model.ObjectMetadata; import io.findify.s3mock.S3Mock; -import org.apache.commons.codec.binary.Base64; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.dspace.AbstractIntegrationTestWithDatabase; From 1ccde8468debf2e0db662a72efc5300b444a3b55 Mon Sep 17 00:00:00 2001 From: Chandaluri Vamsi Krishna Date: Sat, 15 Apr 2023 11:42:02 +0530 Subject: [PATCH 083/510] Removed useProxies usage Signed-off-by: Chandaluri Vamsi Krishna --- .../java/org/dspace/authenticate/IPAuthentication.java | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/authenticate/IPAuthentication.java b/dspace-api/src/main/java/org/dspace/authenticate/IPAuthentication.java index 9c37fcee4755..3b2366034489 100644 --- a/dspace-api/src/main/java/org/dspace/authenticate/IPAuthentication.java +++ b/dspace-api/src/main/java/org/dspace/authenticate/IPAuthentication.java @@ -52,11 +52,6 @@ public class IPAuthentication implements AuthenticationMethod { */ private static Logger log = org.apache.logging.log4j.LogManager.getLogger(IPAuthentication.class); - /** - * Whether to look for x-forwarded headers for logging IP addresses - */ - protected static Boolean useProxies; - /** * All the IP matchers */ @@ -250,7 +245,7 @@ public List getSpecialGroups(Context context, HttpServletRequest request) log.debug(LogHelper.getHeader(context, "authenticated", "special_groups=" + gsb.toString() - + " (by IP=" + addr + ", useProxies=" + useProxies.toString() + ")" + + " (by IP=" + addr + ")" )); } From d5178a71290c33d6eb518c63540b63cd5c1702fe Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Mon, 17 Apr 2023 09:26:49 -0400 Subject: [PATCH 084/510] Document the need to avoid commit() during event dispatch. --- .../main/java/org/dspace/event/Consumer.java | 31 ++++++++++--------- .../java/org/dspace/event/package-info.java | 20 ++++++++++++ 2 files changed, 37 insertions(+), 14 deletions(-) create mode 100644 dspace-api/src/main/java/org/dspace/event/package-info.java diff --git a/dspace-api/src/main/java/org/dspace/event/Consumer.java b/dspace-api/src/main/java/org/dspace/event/Consumer.java index 1a8b16e98a0b..f56efcc7bacb 100644 --- a/dspace-api/src/main/java/org/dspace/event/Consumer.java +++ b/dspace-api/src/main/java/org/dspace/event/Consumer.java @@ -10,18 +10,16 @@ import org.dspace.core.Context; /** - * Interface for content event consumers. Note that the consumer cannot tell if - * it is invoked synchronously or asynchronously; the consumer interface and - * sequence of calls is the same for both. Asynchronous consumers may see more - * consume() calls between the start and end of the event stream, if they are - * invoked asynchronously, once in a long time period, rather than synchronously - * after every Context.commit(). - * - * @version $Revision$ + * Interface for content event consumers. Note that the consumer cannot tell + * if it is invoked synchronously or asynchronously; the consumer interface + * and sequence of calls is the same for both. Asynchronous consumers may see + * more consume() calls between the start and end of the event stream, if they + * are invoked asynchronously, once in a long time period, rather than + * synchronously after every Context.commit(). */ public interface Consumer { /** - * Initialize - allocate any resources required to operate. This may include + * Allocate any resources required to operate. This may include * initializing any pooled JMS resources. Called ONCE when created by the * dispatcher pool. This should be used to set up expensive resources that * will remain for the lifetime of the consumer. @@ -31,12 +29,17 @@ public interface Consumer { public void initialize() throws Exception; /** - * Consume an event; events may get filtered at the dispatcher level, hiding - * it from the consumer. This behavior is based on the dispatcher/consumer - * configuration. Should include logic to initialize any resources required - * for a batch of events. + * Consume an event. Events may be filtered by a dispatcher, hiding them + * from the consumer. This behavior is based on the dispatcher/consumer + * configuration. Should include logic to initialize any resources + * required for a batch of events. + * + *

This method must not commit the context. Committing causes + * re-dispatch of the event queue, which can result in infinite recursion + * leading to memory exhaustion as seen in + * {@link https://github.com/DSpace/DSpace/pull/8756}. * - * @param ctx the execution context object + * @param ctx the current DSpace session * @param event the content event * @throws Exception if error */ diff --git a/dspace-api/src/main/java/org/dspace/event/package-info.java b/dspace-api/src/main/java/org/dspace/event/package-info.java new file mode 100644 index 000000000000..544dfb271a1d --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/event/package-info.java @@ -0,0 +1,20 @@ +/** + * 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/ + */ +/** + * Actions which alter DSpace model objects can queue {@link Event}s, which + * are presented to {@link Consumer}s by a {@link Dispatcher}. A pool of + * {@code Dispatcher}s is managed by an {@link service.EventService}, guided + * by configuration properties {@code event.dispatcher.*}. + * + *

One must be careful not to commit the current DSpace {@code Context} + * during event dispatch. {@code commit()} triggers event dispatching, and + * doing this during event dispatch can lead to infinite recursion and + * memory exhaustion. + */ + +package org.dspace.event; From b48ea37de5d2b87d550099c41526cdbbf2969831 Mon Sep 17 00:00:00 2001 From: Tuan Nguyen Date: Mon, 17 Apr 2023 15:16:17 -0400 Subject: [PATCH 085/510] properly escape ':' in query --- .../java/org/dspace/content/authority/SolrAuthority.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/authority/SolrAuthority.java b/dspace-api/src/main/java/org/dspace/content/authority/SolrAuthority.java index 497fa08f2faf..123626cd0965 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/SolrAuthority.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/SolrAuthority.java @@ -200,8 +200,8 @@ protected void addExternalResults(String text, ArrayList choices, List Date: Wed, 5 Apr 2023 17:48:55 +0200 Subject: [PATCH 086/510] 100414: Missing search_result statistics fix --- .../rest/converter/SearchEventConverter.java | 10 +++ .../app/rest/model/SearchEventRest.java | 9 +++ .../app/rest/utils/DSpaceObjectResolver.java | 61 +++++++++++++++++++ .../app/rest/SearchEventRestRepositoryIT.java | 55 +++++++++++++++++ 4 files changed, 135 insertions(+) create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/DSpaceObjectResolver.java diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SearchEventConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SearchEventConverter.java index 470a3ac3425b..a8203e272f29 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SearchEventConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SearchEventConverter.java @@ -14,6 +14,7 @@ import org.dspace.app.rest.model.PageRest; import org.dspace.app.rest.model.SearchEventRest; import org.dspace.app.rest.model.SearchResultsRest; +import org.dspace.app.rest.utils.DSpaceObjectResolver; import org.dspace.app.rest.utils.ScopeResolver; import org.dspace.content.DSpaceObject; import org.dspace.core.Context; @@ -29,11 +30,20 @@ public class SearchEventConverter { @Autowired private ScopeResolver scopeResolver; + @Autowired + private DSpaceObjectResolver dSpaceObjectResolver; + public UsageSearchEvent convert(Context context, HttpServletRequest request, SearchEventRest searchEventRest) { UsageSearchEvent usageSearchEvent = new UsageSearchEvent(UsageEvent.Action.SEARCH, request, context, null); usageSearchEvent.setQuery(searchEventRest.getQuery()); usageSearchEvent.setDsoType(searchEventRest.getDsoType()); + if (searchEventRest.getObject() != null) { + IndexableObject object = dSpaceObjectResolver.resolveObject(context, searchEventRest.getObject()); + if (object != null && object.getIndexedObject() instanceof DSpaceObject) { + usageSearchEvent.setObject((DSpaceObject) object.getIndexedObject()); + } + } if (searchEventRest.getScope() != null) { IndexableObject scopeObject = scopeResolver.resolveScope(context, String.valueOf(searchEventRest.getScope())); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SearchEventRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SearchEventRest.java index e029dbaf9919..637acb9bfd68 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SearchEventRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SearchEventRest.java @@ -25,6 +25,7 @@ public class SearchEventRest extends BaseObjectRest { private UUID scope; private String configuration; private String dsoType; + private UUID object; private List appliedFilters; private SearchResultsRest.Sorting sort; private PageRest page; @@ -97,4 +98,12 @@ public String getDsoType() { public void setDsoType(String dsoType) { this.dsoType = dsoType; } + + public UUID getObject() { + return object; + } + + public void setObject(UUID object) { + this.object = object; + } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/DSpaceObjectResolver.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/DSpaceObjectResolver.java new file mode 100644 index 000000000000..7ded06bdf52a --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/DSpaceObjectResolver.java @@ -0,0 +1,61 @@ +/** + * 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.utils; + +import java.sql.SQLException; +import java.util.UUID; + +import org.apache.log4j.Logger; +import org.dspace.content.service.CollectionService; +import org.dspace.content.service.CommunityService; +import org.dspace.content.service.ItemService; +import org.dspace.core.Context; +import org.dspace.discovery.IndexableObject; +import org.dspace.discovery.indexobject.IndexableCollection; +import org.dspace.discovery.indexobject.IndexableCommunity; +import org.dspace.discovery.indexobject.IndexableItem; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +public class DSpaceObjectResolver { + /* Log4j logger */ + private static final Logger log = Logger.getLogger(DSpaceObjectResolver.class); + + @Autowired + ItemService itemService; + + @Autowired + CollectionService collectionService; + + @Autowired + CommunityService communityService; + + public IndexableObject resolveObject(Context context, UUID uuid) { + IndexableObject object = null; + if (uuid != null) { + try { + object = new IndexableCommunity(communityService.find(context, uuid)); + if (object.getIndexedObject() == null) { + object = new IndexableCollection(collectionService.find(context, uuid)); + } + if (object.getIndexedObject() == null) { + object = new IndexableItem(itemService.find(context, uuid)); + } + if (object.getIndexedObject() == null) { + throw new IllegalArgumentException("UUID " + uuid + " is expected to resolve to a Community, " + + "Collection or Item, but didn't resolve to any"); + } + } catch (SQLException e) { + log.warn("Unable to retrieve DSpace Object with ID " + uuid + " from the database", e); + } + } + return object; + } + +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SearchEventRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SearchEventRestRepositoryIT.java index bd40cfdc9dd8..63ca4b735dae 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SearchEventRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SearchEventRestRepositoryIT.java @@ -411,4 +411,59 @@ public void postTestSuccesEmptyQuery() throws Exception { .andExpect(status().isCreated()); } + + @Test + public void postTestWithObjectSuccess() 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(); + + context.restoreAuthSystemState(); + + SearchEventRest searchEventRest = new SearchEventRest(); + + searchEventRest.setQuery("test"); + searchEventRest.setScope(publicItem1.getID()); + searchEventRest.setConfiguration("default"); + searchEventRest.setDsoType("item"); + searchEventRest.setObject(publicItem1.getID()); + + SearchResultsRest.Sorting sort = new SearchResultsRest.Sorting("title", "desc"); + searchEventRest.setSort(sort); + + PageRest pageRest = new PageRest(5, 20, 4, 1); + searchEventRest.setPage(pageRest); + + SearchResultsRest.AppliedFilter appliedFilter = + new SearchResultsRest.AppliedFilter("author", "contains", "test","test"); + List appliedFilterList = new LinkedList<>(); + appliedFilterList.add(appliedFilter); + searchEventRest.setAppliedFilters(appliedFilterList); + + ObjectMapper mapper = new ObjectMapper(); + + getClient().perform(post("/api/statistics/searchevents") + .content(mapper.writeValueAsBytes(searchEventRest)) + .contentType(contentType)) + .andExpect(status().isCreated()); + + } } From e3fb8bf601177e6cfff43b44f59cf14e630d2bb1 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 19 Apr 2023 11:21:13 -0500 Subject: [PATCH 087/510] Update to latest Jetty v9 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 3d9e6851d1d0..3b6f7edb6549 100644 --- a/pom.xml +++ b/pom.xml @@ -37,7 +37,7 @@ 2.3.1 1.1.0 - 9.4.48.v20220622 + 9.4.51.v20230217 2.17.1 2.0.27 1.18.0 From aa7acb5be782d1eafdc5ac4fa6bb2c6de7796b38 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 19 Apr 2023 14:25:18 -0500 Subject: [PATCH 088/510] Update Postgres Docker scripts to build/tag images automatically --- .github/workflows/docker.yml | 57 +++++++++++++++++++ docker-compose.yml | 10 +++- .../src/main/docker-compose/db.entities.yml | 2 +- .../dspace-postgres-pgcrypto-curl/Dockerfile | 16 ++++-- .../dspace-postgres-pgcrypto/Dockerfile | 14 +++-- 5 files changed, 85 insertions(+), 14 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index faa50ac8dcc2..971954a5e1ee 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -196,3 +196,60 @@ jobs: # Use tags / labels provided by 'docker/metadata-action' above tags: ${{ steps.meta_build_solr.outputs.tags }} labels: ${{ steps.meta_build_solr.outputs.labels }} + + ########################################################### + # Build/Push the 'dspace/dspace-postgres-pgcrypto' image + ########################################################### + # Get Metadata for docker_build_postgres step below + - name: Sync metadata (tags, labels) from GitHub to Docker for 'dspace-postgres-pgcrypto' image + id: meta_build_postgres + uses: docker/metadata-action@v4 + with: + images: dspace/dspace-postgres-pgcrypto + tags: ${{ env.IMAGE_TAGS }} + flavor: ${{ env.TAGS_FLAVOR }} + + - name: Build and push 'dspace-postgres-pgcrypto' image + id: docker_build_postgres + uses: docker/build-push-action@v3 + with: + # Must build out of subdirectory to have access to install script for pgcrypto + context: ./dspace/src/main/docker/dspace-postgres-pgcrypto/ + dockerfile: Dockerfile + platforms: ${{ env.PLATFORMS }} + # For pull requests, we run the Docker build (to ensure no PR changes break the build), + # but we ONLY do an image push to DockerHub if it's NOT a PR + push: ${{ github.event_name != 'pull_request' }} + # Use tags / labels provided by 'docker/metadata-action' above + tags: ${{ steps.meta_build_postgres.outputs.tags }} + labels: ${{ steps.meta_build_postgres.outputs.labels }} + + ########################################################### + # Build/Push the 'dspace/dspace-postgres-pgcrypto' image ('-loadsql' tag) + ########################################################### + # Get Metadata for docker_build_postgres_loadsql step below + - name: Sync metadata (tags, labels) from GitHub to Docker for 'dspace-postgres-pgcrypto-loadsql' image + id: meta_build_postgres_loadsql + uses: docker/metadata-action@v4 + with: + images: dspace/dspace-postgres-pgcrypto + tags: ${{ env.IMAGE_TAGS }} + # Suffix all tags with "-loadsql". Otherwise, it uses the same + # tagging logic as the primary 'dspace/dspace-postgres-pgcrypto' image above. + flavor: ${{ env.TAGS_FLAVOR }} + suffix=-loadsql + + - name: Build and push 'dspace-postgres-pgcrypto-loadsql' image + id: docker_build_postgres_loadsql + uses: docker/build-push-action@v3 + with: + # Must build out of subdirectory to have access to install script for pgcrypto + context: ./dspace/src/main/docker/dspace-postgres-pgcrypto-curl/ + dockerfile: Dockerfile + platforms: ${{ env.PLATFORMS }} + # For pull requests, we run the Docker build (to ensure no PR changes break the build), + # but we ONLY do an image push to DockerHub if it's NOT a PR + push: ${{ github.event_name != 'pull_request' }} + # Use tags / labels provided by 'docker/metadata-action' above + tags: ${{ steps.meta_build_postgres_loadsql.outputs.tags }} + labels: ${{ steps.meta_build_postgres_loadsql.outputs.labels }} \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 40b4ce064eab..dd26d6cf150b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -62,13 +62,16 @@ services: while (! /dev/null 2>&1; do sleep 1; done; /dspace/bin/dspace database migrate catalina.sh run - # DSpace database container + # DSpace PostgreSQL database container dspacedb: container_name: dspacedb + # Uses a custom Postgres image with pgcrypto installed + image: "${DOCKER_OWNER:-dspace}/dspace-postgres-pgcrypto:${DSPACE_VER:-dspace-7_x}" + build: + # Must build out of subdirectory to have access to install script for pgcrypto + context: ./dspace/src/main/docker/dspace-postgres-pgcrypto/ environment: PGDATA: /pgdata - # Uses a custom Postgres image with pgcrypto installed - image: dspace/dspace-postgres-pgcrypto networks: dspacenet: ports: @@ -77,6 +80,7 @@ services: stdin_open: true tty: true volumes: + # Keep Postgres data directory between reboots - pgdata:/pgdata # DSpace Solr container dspacesolr: diff --git a/dspace/src/main/docker-compose/db.entities.yml b/dspace/src/main/docker-compose/db.entities.yml index 8d86f7bb8359..32c54a5d0bd1 100644 --- a/dspace/src/main/docker-compose/db.entities.yml +++ b/dspace/src/main/docker-compose/db.entities.yml @@ -10,7 +10,7 @@ version: "3.7" services: dspacedb: - image: dspace/dspace-postgres-pgcrypto:loadsql + image: dspace/dspace-postgres-pgcrypto:dspace-7_x-loadsql environment: # This SQL is available from https://github.com/DSpace-Labs/AIP-Files/releases/tag/demo-entities-data - LOADSQL=https://github.com/DSpace-Labs/AIP-Files/releases/download/demo-entities-data/dspace7-entities-data.sql diff --git a/dspace/src/main/docker/dspace-postgres-pgcrypto-curl/Dockerfile b/dspace/src/main/docker/dspace-postgres-pgcrypto-curl/Dockerfile index 0e85dd33ce59..1f51d299f20b 100644 --- a/dspace/src/main/docker/dspace-postgres-pgcrypto-curl/Dockerfile +++ b/dspace/src/main/docker/dspace-postgres-pgcrypto-curl/Dockerfile @@ -6,14 +6,20 @@ # http://www.dspace.org/license/ # -# This will be deployed as dspace/dspace-postgres-pgcrpyto:loadsql -FROM postgres:11 +# To build for example use: +# docker build --build-arg POSTGRES_VERSION=11 --build-arg POSTGRES_PASSWORD=mypass -f ./dspace/src/main/docker/dspace-postgres-pgcrypto-curl/Dockerfile . +# This will be published as dspace/dspace-postgres-pgcrypto:$DSPACE_VERSION-loadsql + +ARG POSTGRES_VERSION=11 +ARG POSTGRES_PASSWORD=dspace + +FROM postgres:${POSTGRES_VERSION}-alpine ENV POSTGRES_DB dspace ENV POSTGRES_USER dspace -ENV POSTGRES_PASSWORD dspace -# Load a SQL dump. Set LOADSQL to a URL for the sql dump file. -RUN apt-get update && apt-get install -y curl +# Install curl which is necessary to load SQL file +RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/* +# Load a SQL dump. Set LOADSQL to a URL for the sql dump file. COPY install-pgcrypto.sh /docker-entrypoint-initdb.d/ diff --git a/dspace/src/main/docker/dspace-postgres-pgcrypto/Dockerfile b/dspace/src/main/docker/dspace-postgres-pgcrypto/Dockerfile index 84b7569a2b2c..0f0f4f0efba7 100644 --- a/dspace/src/main/docker/dspace-postgres-pgcrypto/Dockerfile +++ b/dspace/src/main/docker/dspace-postgres-pgcrypto/Dockerfile @@ -6,13 +6,17 @@ # http://www.dspace.org/license/ # -# This will be deployed as dspace/dspace-postgres-pgcrpyto:latest -FROM postgres:11 +# To build for example use: +# docker build --build-arg POSTGRES_VERSION=11 --build-arg POSTGRES_PASSWORD=mypass -f ./dspace/src/main/docker/dspace-postgres-pgcrypto/Dockerfile . +# This will be published as dspace/dspace-postgres-pgcrypto:$DSPACE_VERSION + +ARG POSTGRES_VERSION=11 +ARG POSTGRES_PASSWORD=dspace + +FROM postgres:${POSTGRES_VERSION}-alpine ENV POSTGRES_DB dspace ENV POSTGRES_USER dspace -ENV POSTGRES_PASSWORD dspace - -RUN apt-get update +# Copy over script which will initialize database and install pgcrypto extension COPY install-pgcrypto.sh /docker-entrypoint-initdb.d/ From 5e013a8a36cc8a2b844fdfb8eacae8ff6a8e1390 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 19 Apr 2023 14:25:52 -0500 Subject: [PATCH 089/510] Update to latest version of Ant --- Dockerfile | 2 +- Dockerfile.cli | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 444a1bcf0b55..f1ff6adf5ac5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -31,7 +31,7 @@ ARG TARGET_DIR=dspace-installer COPY --from=build /install /dspace-src WORKDIR /dspace-src # Create the initial install deployment using ANT -ENV ANT_VERSION 1.10.12 +ENV ANT_VERSION 1.10.13 ENV ANT_HOME /tmp/ant-$ANT_VERSION ENV PATH $ANT_HOME/bin:$PATH # Need wget to install ant diff --git a/Dockerfile.cli b/Dockerfile.cli index 76e559fc83c3..6878d8f8dbfb 100644 --- a/Dockerfile.cli +++ b/Dockerfile.cli @@ -30,7 +30,7 @@ ARG TARGET_DIR=dspace-installer COPY --from=build /install /dspace-src WORKDIR /dspace-src # Create the initial install deployment using ANT -ENV ANT_VERSION 1.10.12 +ENV ANT_VERSION 1.10.13 ENV ANT_HOME /tmp/ant-$ANT_VERSION ENV PATH $ANT_HOME/bin:$PATH # Need wget to install ant From 3df54e549ff85c50063f445ca3762da09014b810 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 19 Apr 2023 15:14:40 -0500 Subject: [PATCH 090/510] Revert change to alpine linux. Scripts all assume debian. --- dspace/src/main/docker/dspace-postgres-pgcrypto-curl/Dockerfile | 2 +- dspace/src/main/docker/dspace-postgres-pgcrypto/Dockerfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dspace/src/main/docker/dspace-postgres-pgcrypto-curl/Dockerfile b/dspace/src/main/docker/dspace-postgres-pgcrypto-curl/Dockerfile index 1f51d299f20b..3e38cdae27a3 100644 --- a/dspace/src/main/docker/dspace-postgres-pgcrypto-curl/Dockerfile +++ b/dspace/src/main/docker/dspace-postgres-pgcrypto-curl/Dockerfile @@ -13,7 +13,7 @@ ARG POSTGRES_VERSION=11 ARG POSTGRES_PASSWORD=dspace -FROM postgres:${POSTGRES_VERSION}-alpine +FROM postgres:${POSTGRES_VERSION} ENV POSTGRES_DB dspace ENV POSTGRES_USER dspace diff --git a/dspace/src/main/docker/dspace-postgres-pgcrypto/Dockerfile b/dspace/src/main/docker/dspace-postgres-pgcrypto/Dockerfile index 0f0f4f0efba7..bf8344103c44 100644 --- a/dspace/src/main/docker/dspace-postgres-pgcrypto/Dockerfile +++ b/dspace/src/main/docker/dspace-postgres-pgcrypto/Dockerfile @@ -13,7 +13,7 @@ ARG POSTGRES_VERSION=11 ARG POSTGRES_PASSWORD=dspace -FROM postgres:${POSTGRES_VERSION}-alpine +FROM postgres:${POSTGRES_VERSION} ENV POSTGRES_DB dspace ENV POSTGRES_USER dspace From d9f7e04368f60b38a0cea43d7a8771121feaf346 Mon Sep 17 00:00:00 2001 From: aroman-arvo Date: Thu, 20 Apr 2023 11:13:28 +0200 Subject: [PATCH 091/510] #8636 Request a Copy's "helpdesk" strategy requires authentication to respond --- .../org/dspace/app/requestitem/RequestItemEmailNotifier.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemEmailNotifier.java b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemEmailNotifier.java index 384f33decaf2..61b42fd185e2 100644 --- a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemEmailNotifier.java +++ b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemEmailNotifier.java @@ -175,9 +175,12 @@ static public void sendResponse(Context context, RequestItem ri, String subject, } } else { Bitstream bitstream = ri.getBitstream(); + // #8636 Anyone receiving the email can respond to the request without authenticating into DSpace + context.turnOffAuthorisationSystem(); email.addAttachment(bitstreamService.retrieve(context, bitstream), bitstream.getName(), bitstream.getFormat(context).getMIMEType()); + context.restoreAuthSystemState(); } email.send(); } else { From 98b843668e07ae2112b0ce7dbbd741af7afd3189 Mon Sep 17 00:00:00 2001 From: Sean Kalynuk Date: Thu, 20 Apr 2023 14:08:30 -0500 Subject: [PATCH 092/510] Fix #8490 preferred label lookup When the authority key still needs to be generated, skip the lookup for the preferred label since it does not exist in Solr yet. --- .../dspace/discovery/indexobject/ItemIndexFactoryImpl.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/discovery/indexobject/ItemIndexFactoryImpl.java b/dspace-api/src/main/java/org/dspace/discovery/indexobject/ItemIndexFactoryImpl.java index 61827747b72d..fc024cc524b0 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/indexobject/ItemIndexFactoryImpl.java +++ b/dspace-api/src/main/java/org/dspace/discovery/indexobject/ItemIndexFactoryImpl.java @@ -31,6 +31,7 @@ import org.apache.logging.log4j.Logger; import org.apache.solr.client.solrj.SolrServerException; import org.apache.solr.common.SolrInputDocument; +import org.dspace.authority.service.AuthorityValueService; import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.Item; @@ -93,6 +94,8 @@ public class ItemIndexFactoryImpl extends DSpaceObjectIndexFactoryImpl Date: Fri, 21 Apr 2023 07:55:24 +0200 Subject: [PATCH 093/510] Fix #8792, change column type of Process.parameters to TEXT --- dspace-api/src/main/java/org/dspace/scripts/Process.java | 4 ++++ .../V7.6_2023.04.19__process_parameters_to_text_type.sql | 9 +++++++++ 2 files changed, 13 insertions(+) create mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2023.04.19__process_parameters_to_text_type.sql diff --git a/dspace-api/src/main/java/org/dspace/scripts/Process.java b/dspace-api/src/main/java/org/dspace/scripts/Process.java index ce41f46bdf7b..190d214a3c8d 100644 --- a/dspace-api/src/main/java/org/dspace/scripts/Process.java +++ b/dspace-api/src/main/java/org/dspace/scripts/Process.java @@ -21,6 +21,7 @@ import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.JoinTable; +import javax.persistence.Lob; import javax.persistence.ManyToMany; import javax.persistence.ManyToOne; import javax.persistence.SequenceGenerator; @@ -35,6 +36,7 @@ import org.dspace.core.ReloadableEntity; import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; +import org.hibernate.annotations.Type; /** * This class is the DB Entity representation of the Process object to be stored in the Database @@ -68,6 +70,8 @@ public class Process implements ReloadableEntity { @Enumerated(EnumType.STRING) private ProcessStatus processStatus; + @Lob + @Type(type = "org.dspace.storage.rdbms.hibernate.DatabaseAwareLobType") @Column(name = "parameters") private String parameters; diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2023.04.19__process_parameters_to_text_type.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2023.04.19__process_parameters_to_text_type.sql new file mode 100644 index 000000000000..f7e0e51d0bf7 --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2023.04.19__process_parameters_to_text_type.sql @@ -0,0 +1,9 @@ +-- +-- The contents of this file are subject to the license and copyright +-- detailed in the LICENSE and NOTICE files at the root of the source +-- tree and available online at +-- +-- http://www.dspace.org/license/ +-- + +ALTER TABLE process ALTER COLUMN parameters TYPE TEXT; From d7c4d35d99582d0589a53d61d33216a68eb4272d Mon Sep 17 00:00:00 2001 From: "max.nuding" Date: Fri, 21 Apr 2023 11:52:24 +0200 Subject: [PATCH 094/510] Add H2 and Oracle DB migrations --- .../V7.6_2023.04.19__process_parameters_to_text_type.sql | 9 +++++++++ .../V7.6_2023.04.19__process_parameters_to_text_type.sql | 9 +++++++++ 2 files changed, 18 insertions(+) create mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.6_2023.04.19__process_parameters_to_text_type.sql create mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.6_2023.04.19__process_parameters_to_text_type.sql diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.6_2023.04.19__process_parameters_to_text_type.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.6_2023.04.19__process_parameters_to_text_type.sql new file mode 100644 index 000000000000..1028ba370c47 --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.6_2023.04.19__process_parameters_to_text_type.sql @@ -0,0 +1,9 @@ +-- +-- The contents of this file are subject to the license and copyright +-- detailed in the LICENSE and NOTICE files at the root of the source +-- tree and available online at +-- +-- http://www.dspace.org/license/ +-- + +ALTER TABLE process ALTER COLUMN parameters SET DATA TYPE CLOB; diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.6_2023.04.19__process_parameters_to_text_type.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.6_2023.04.19__process_parameters_to_text_type.sql new file mode 100644 index 000000000000..6b2dd705ea68 --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.6_2023.04.19__process_parameters_to_text_type.sql @@ -0,0 +1,9 @@ +-- +-- The contents of this file are subject to the license and copyright +-- detailed in the LICENSE and NOTICE files at the root of the source +-- tree and available online at +-- +-- http://www.dspace.org/license/ +-- + +ALTER TABLE process MODIFY (parameters CLOB); From f1f4fe4cdf02444f59fa46674e923addb604a07c Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 21 Apr 2023 16:54:54 -0500 Subject: [PATCH 095/510] Update to Postgres v13 (as v11 images are not being maintained). Add db.restore.yml upgrade script & enhance READMEs --- docker-compose.yml | 1 + dspace/src/main/docker-compose/README.md | 161 +++++++++++++++--- dspace/src/main/docker-compose/db.restore.yml | 26 +++ dspace/src/main/docker/README.md | 76 ++++++--- .../dspace-postgres-pgcrypto-curl/Dockerfile | 5 +- .../install-pgcrypto.sh | 22 ++- .../dspace-postgres-pgcrypto/Dockerfile | 5 +- 7 files changed, 236 insertions(+), 60 deletions(-) create mode 100644 dspace/src/main/docker-compose/db.restore.yml diff --git a/docker-compose.yml b/docker-compose.yml index dd26d6cf150b..36ba6af2c981 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -72,6 +72,7 @@ services: context: ./dspace/src/main/docker/dspace-postgres-pgcrypto/ environment: PGDATA: /pgdata + POSTGRES_PASSWORD: dspace networks: dspacenet: ports: diff --git a/dspace/src/main/docker-compose/README.md b/dspace/src/main/docker-compose/README.md index a83a466bdbba..35a6e6055433 100644 --- a/dspace/src/main/docker-compose/README.md +++ b/dspace/src/main/docker-compose/README.md @@ -1,4 +1,4 @@ -# Docker Compose Resources +# Docker Compose files for DSpace Backend *** :warning: **THESE IMAGES ARE NOT PRODUCTION READY** The below Docker Compose images/resources were built for development/testing only. Therefore, they may not be fully secured or up-to-date, and should not be used in production. @@ -6,27 +6,51 @@ If you wish to run DSpace on Docker in production, we recommend building your own Docker images. You are welcome to borrow ideas/concepts from the below images in doing so. But, the below images should not be used "as is" in any production scenario. *** -## root directory Resources + +## Overview +The scripts in this directory can be used to start the DSpace REST API (backend) in Docker. +Optionally, the DSpace User Interface (frontend) may also be started in Docker. + +For additional options/settings in starting the User Interface (frontend) in Docker, see the Docker Compose +documentation for the frontend: https://github.com/DSpace/dspace-angular/blob/main/docker/README.md + +## Primary Docker Compose Scripts (in root directory) +The root directory of this project contains the primary Dockerfiles & Docker Compose scripts +which are used to start the backend. + - docker-compose.yml - - Docker compose file to orchestrate DSpace 7 REST components -- docker-compose-cli - - Docker compose file to run DSpace CLI tasks within a running DSpace instance in Docker + - Docker compose file to orchestrate DSpace REST API (backend) components. + - Uses the `Dockerfile` in the same directory. +- docker-compose-cli.yml + - Docker compose file to run DSpace CLI (Command Line Interface) tasks within a running DSpace instance in Docker. See instructions below. + - Uses the `Dockerfile.cli` in the same directory. -## dspace/src/main/docker-compose resources +Documentation for all Dockerfiles used by these compose scripts can be found in the ["docker" folder README](../docker/README.md) + +## Additional Docker Compose tools (in ./dspace/src/main/docker-compose) - cli.assetstore.yml - Docker compose file that will download and install a default assetstore. + - The default assetstore is the configurable entities test dataset. Useful for [testing/demos of Entities](#Ingest Option 2 Ingest Entities Test Data). - cli.ingest.yml - - Docker compose file that will run an AIP ingest into DSpace 7. + - Docker compose file that will run an AIP ingest into DSpace 7. Useful for testing/demos with basic Items. - db.entities.yml - - Docker compose file that pre-populate a database instance using a SQL dump. The default dataset is the configurable entities test dataset. -- local.cfg - - Sets the environment used across containers run with docker-compose + - Docker compose file that pre-populate a database instance using a downloaded SQL dump. + - The default dataset is the configurable entities test dataset. Useful for [testing/demos of Entities](#Ingest Option 2 Ingest Entities Test Data). +- db.restore.yml + - Docker compose file that pre-populate a database instance using a *local* SQL dump (hardcoded to `./pgdump.sql`) + - Useful for restoring data from a local backup, or [Upgrading PostgreSQL in Docker](#Upgrading PostgreSQL in Docker) - docker-compose-angular.yml - - Docker compose file that will start a published DSpace angular container that interacts with the branch. + - Docker compose file that will start a published DSpace User Interface container that interacts with the branch. - docker-compose-shibboleth.yml - Docker compose file that will start a *test/demo* Shibboleth SP container (in Apache) that proxies requests to the DSpace container - ONLY useful for testing/development. NOT production ready. +- docker-compose-iiif.yml + - Docker compose file that will start a *test/demo* Cantaloupe image server container required for enabling IIIF support. + - ONLY useful for testing/development. NOT production ready. + +Documentation for all Dockerfiles used by these compose scripts can be found in the ["docker" folder README](../docker/README.md) + ## To refresh / pull DSpace images from Dockerhub ``` @@ -55,6 +79,12 @@ docker-compose -p d7 up -d docker-compose -p d7 -f docker-compose.yml -f dspace/src/main/docker-compose/docker-compose-angular.yml up -d ``` +## Run DSpace REST and DSpace Angular from local branches + +*Allows you to run the backend from the "DSpace/DSpace" codebase while also running the frontend from the "DSpace/dspace-angular" codebase.* + +See documentation in [DSpace User Interface Docker instructions](https://github.com/DSpace/dspace-angular/blob/main/docker/README.md#run-dspace-rest-and-dspace-angular-from-local-branches). + ## Run DSpace 7 REST with a IIIF Image Server from your branch *Only useful for testing IIIF support in a development environment* @@ -67,7 +97,6 @@ docker-compose -p d7 -f docker-compose.yml -f dspace/src/main/docker-compose/doc ``` ## Run DSpace 7 REST and Shibboleth SP (in Apache) from your branch - *Only useful for testing Shibboleth in a development environment* This Shibboleth container uses https://samltest.id/ as an IdP (see `../docker/dspace-shibboleth/`). @@ -143,21 +172,11 @@ The remainder of these instructions assume you are using ngrok (though other pro DSPACE_HOSTNAME=[subdomain].ngrok.io docker-compose -p d7 -f docker-compose.yml -f dspace/src/main/docker-compose/docker-compose-angular.yml -f dspace/src/main/docker-compose/docker-compose-shibboleth.yml up -d ``` -## Run DSpace 7 REST and Angular from local branches +## Sample Test Data -_The system will be started in 2 steps. Each step shares the same docker network._ +### Ingesting test content from AIP files -From DSpace/DSpace -``` -docker-compose -p d7 up -d -``` - -From DSpace/DSpace-angular (build as needed) -``` -docker-compose -p d7 -f docker/docker-compose.yml up -d -``` - -## Ingest Option 1: Ingesting test content from AIP files into a running DSpace 7 instance +*Allows you to ingest a set of AIPs into your DSpace instance for testing/demo purposes.* These AIPs represent basic Communities, Collections and Items. Prerequisites - Start DSpace 7 using one of the options listed above @@ -173,8 +192,14 @@ Download a Zip file of AIP content and ingest test data docker-compose -p d7 -f docker-compose-cli.yml -f dspace/src/main/docker-compose/cli.ingest.yml run --rm dspace-cli ``` -## Ingest Option 2: Ingest Entities Test Data -_Remove your d7 volumes if you already ingested content into your docker volumes_ +### Ingest Entities Test Data + +*Allows you to load Configurable Entities test data for testing/demo purposes.* + +Prerequisites +- Start DSpace 7 using one of the options listed above +- Build the DSpace CLI image if needed. See the instructions above. +- _Remove your d7 volumes if you already ingested content into your docker volumes_ Start DSpace REST with a postgres database dump downloaded from the internet. ``` @@ -212,3 +237,85 @@ Similarly, you can see the value of any DSpace configuration (in local.cfg or ds # Output the value of `dspace.ui.url` from running Docker instance docker-compose -p d7 -f docker-compose-cli.yml run --rm dspace-cli dsprop -p dspace.ui.url ``` + +NOTE: It is also possible to run CLI scripts directly on the "dspace" container (where the backend runs) +This can be useful if you want to pass environment variables which override DSpace configs. +``` +# Run the "./dspace database clean" command from the "dspace" container +# Before doing so, it sets "db.cleanDisabled=false". +# WARNING: This will delete all your data. It's just an example of how to do so. +docker-compose -p d7 exec -e "db__P__cleanDisabled=false" dspace /dspace/bin/dspace database clean +``` + +## Upgrading PostgreSQL in Docker + +Occasionally, we update our `dspace-postgres-*` images to use a new version of PostgreSQL. +Simply using the new image will likely throw errors as the pgdata (postgres data) directory is incompatible +with the new version of PostgreSQL. These errors look like: +``` +FATAL: database files are incompatible with server +DETAIL: The data directory was initialized by PostgreSQL version 11, which is not compatible with this version 13.10 +``` + +Here's how to fix those issues by migrating your old Postgres data to the new version of Postgres + +1. First, you must start up the older PostgreSQL image (to dump your existing data to a `*.sql` file) + ``` + # This command assumes you are using the process described above to start all your containers + docker-compose -p d7 up -d + ``` + * If you've already accidentally updated to the new PostgreSQL image, you have a few options: + * Pull down an older version of the image from Dockerhub (using a tag) + * Or, temporarily rebuild your local image with the old version of Postgres. For example: + ``` + # This command will rebuild using PostgreSQL v11 & tag it locally as "dspace-7_x" + docker build --build-arg POSTGRES_VERSION=11 -t dspace/dspace-postgres-pgcrypto:dspace-7_x ./dspace/src/main/docker/dspace-postgres-pgcrypto/ + # Then restart container with that image + docker-compose -p d7 up -d + ``` +2. Dump your entire "dspace" database out of the old "dspacedb" container to a local file named `pgdump.sql` + ``` + # NOTE: WE HIGHLY RECOMMEND LOGGING INTO THE CONTAINER and doing the pg_dump within the container. + # If you attempt to run pg_dump from your local machine via docker "exec" (or similar), sometimes + # UTF-8 characters can be corrupted in the export file. This may result in data loss. + + # First login to the "dspacedb" container + docker exec -it dspacedb /bin/bash + + # Dump the "dspace" database to a file named "/tmp/pgdump.sql" within the container + pg_dump -U dspace dspace > /tmp/pgdump.sql + + # Exit the container + exit + + # Download (copy) that /tmp/pgdump.sql backup file from container to your local machine + docker cp dspacedb:/tmp/pgdump.sql . + ``` +3. Now, stop all existing containers. This shuts down the old version of PostgreSQL + ``` + # This command assumes you are using the process described above to start/stop all your containers + docker-compose -p d7 down + ``` +4. Delete the `pgdata` volume. WARNING: This deletes all your old PostgreSQL data. Make sure you have that `pgdump.sql` file FIRST! + ``` + # Assumes you are using `-p d7` which prefixes all volumes with `d7_` + docker volume rm d7_pgdata + ``` +5. Now, pull down the latest PostgreSQL image with the NEW version of PostgreSQL. + ``` + docker-compose -f docker-compose.yml -f docker-compose-cli.yml pull + ``` +6. Start everything up using our `db.restore.yml` script. This script will recreate the database +using the local `./pgdump.sql` file. IMPORTANT: If you renamed that "pgdump.sql" file or stored it elsewhere, +then you MUST change the name/directory in the `db.restore.yml` script. + ``` + # Restore database from "./pgdump.sql" (this path is hardcoded in db.restore.yml) + docker-compose -p d7 -f docker-compose.yml -f dspace/src/main/docker-compose/db.restore.yml up -d + ``` +7. Finally, reindex all database contents into Solr (just to be sure Solr indexes are current). + ``` + # Run "./dspace index-discovery -b" using our CLI image + docker-compose -p d7 -f docker-compose-cli.yml run --rm dspace-cli index-discovery -b + ``` +At this point in time, all your old database data should be migrated to the new Postgres +and running at http://localhost:8080/server/ \ No newline at end of file diff --git a/dspace/src/main/docker-compose/db.restore.yml b/dspace/src/main/docker-compose/db.restore.yml new file mode 100644 index 000000000000..fc2f30b9d8e0 --- /dev/null +++ b/dspace/src/main/docker-compose/db.restore.yml @@ -0,0 +1,26 @@ +# +# 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/ +# + +version: "3.7" + +# +# Overrides the default "dspacedb" container behavior to load a local SQL file into PostgreSQL. +# +# This can be used to restore a "dspacedb" container from a pg_dump, or during upgrade to a new version of PostgreSQL. +services: + dspacedb: + image: dspace/dspace-postgres-pgcrypto:dspace-7_x-loadsql + environment: + # Location where the dump SQL file will be available on the running container + - LOCALSQL=/tmp/pgdump.sql + volumes: + # Volume which shares a local SQL file at "./pgdump.sql" to the running container + # IF YOUR LOCAL FILE HAS A DIFFERENT NAME (or is in a different location), then change the "./pgdump.sql" + # portion of this line. + - ./pgdump.sql:/tmp/pgdump.sql + diff --git a/dspace/src/main/docker/README.md b/dspace/src/main/docker/README.md index f2a944f60851..ac1b4cb9236b 100644 --- a/dspace/src/main/docker/README.md +++ b/dspace/src/main/docker/README.md @@ -1,4 +1,4 @@ -# Docker images supporting DSpace +# Docker images supporting DSpace Backend *** :warning: **THESE IMAGES ARE NOT PRODUCTION READY** The below Docker Compose images/resources were built for development/testing only. Therefore, they may not be fully secured or up-to-date, and should not be used in production. @@ -6,9 +6,15 @@ If you wish to run DSpace on Docker in production, we recommend building your own Docker images. You are welcome to borrow ideas/concepts from the below images in doing so. But, the below images should not be used "as is" in any production scenario. *** -## Dockerfile.dependencies +## Overview +The Dockerfiles in this directory (and subdirectories) are used by our [Docker Compose scripts](../docker-compose/README.md). + +## Dockerfile.dependencies (in root folder) This Dockerfile is used to pre-cache Maven dependency downloads that will be used in subsequent DSpace docker builds. +Caching these Maven dependencies provides a speed increase to all later builds by ensuring the dependencies +are only downloaded once. + ``` docker build -t dspace/dspace-dependencies:dspace-7_x -f Dockerfile.dependencies . ``` @@ -22,12 +28,13 @@ Admins to our DockerHub repo can manually publish with the following command. docker push dspace/dspace-dependencies:dspace-7_x ``` -## Dockerfile.test +## Dockerfile.test (in root folder) -This Dockerfile builds a DSpace 7 Tomcat image (for testing/development). -This image deploys two DSpace webapps: +This Dockerfile builds a DSpace 7 backend image (for testing/development). +This image deploys two DSpace webapps to Tomcat running in Docker: 1. The DSpace 7 REST API (at `http://localhost:8080/server`) -2. The legacy (v6) REST API (at `http://localhost:8080//rest`), deployed without requiring HTTPS access. +2. The legacy (v6) REST API (at `http://localhost:8080/rest`), deployed without requiring HTTPS access. +This image also sets up debugging in Tomcat for development. ``` docker build -t dspace/dspace:dspace-7_x-test -f Dockerfile.test . @@ -42,12 +49,12 @@ Admins to our DockerHub repo can manually publish with the following command. docker push dspace/dspace:dspace-7_x-test ``` -## Dockerfile +## Dockerfile (in root folder) -This Dockerfile builds a DSpace 7 tomcat image. -This image deploys two DSpace webapps: +This Dockerfile builds a DSpace 7 backend image. +This image deploys one DSpace webapp to Tomcat running in Docker: 1. The DSpace 7 REST API (at `http://localhost:8080/server`) -2. The legacy (v6) REST API (at `http://localhost:8080//rest`), deployed *requiring* HTTPS access. + ``` docker build -t dspace/dspace:dspace-7_x -f Dockerfile . ``` @@ -61,9 +68,9 @@ Admins to our DockerHub repo can publish with the following command. docker push dspace/dspace:dspace-7_x ``` -## Dockefile.cli +## Dockerfile.cli (in root folder) -This Dockerfile builds a DSpace 7 CLI image, which can be used to run commandline tools via Docker. +This Dockerfile builds a DSpace 7 CLI (command line interface) image, which can be used to run DSpace's commandline tools via Docker. ``` docker build -t dspace/dspace-cli:dspace-7_x -f Dockerfile.cli . ``` @@ -77,46 +84,60 @@ Admins to our DockerHub repo can publish with the following command. docker push dspace/dspace-cli:dspace-7_x ``` -## dspace/src/main/docker/dspace-postgres-pgcrypto/Dockerfile +## ./dspace-postgres-pgcrypto/Dockerfile This is a PostgreSQL Docker image containing the `pgcrypto` extension required by DSpace 6+. +This image is built *automatically* after each commit is made to the `main` branch. + +How to build manually: ``` cd dspace/src/main/docker/dspace-postgres-pgcrypto -docker build -t dspace/dspace-postgres-pgcrypto . +docker build -t dspace/dspace-postgres-pgcrypto:dspace-7_x . ``` -**This image is built manually.** It should be rebuilt as needed. +It is also possible to change the version of PostgreSQL or the PostgreSQL user's password during the build: +``` +cd dspace/src/main/docker/dspace-postgres-pgcrypto +docker build -t dspace/dspace-postgres-pgcrypto:dspace-7_x --build-arg POSTGRES_VERSION=11 --build-arg POSTGRES_PASSWORD=mypass . +``` A copy of this file exists in the DSpace 6 branch. A specialized version of this file exists for DSpace 4 in DSpace-Docker-Images. -Admins to our DockerHub repo can publish with the following command. +Admins to our DockerHub repo can (manually) publish with the following command. ``` -docker push dspace/dspace-postgres-pgcrypto +docker push dspace/dspace-postgres-pgcrypto:dspace-7_x ``` -## dspace/src/main/docker/dspace-postgres-pgcrypto-curl/Dockerfile +## ./dspace-postgres-pgcrypto-curl/Dockerfile This is a PostgreSQL Docker image containing the `pgcrypto` extension required by DSpace 6+. This image also contains `curl`. The image is pre-configured to load a Postgres database dump on initialization. + +This image is built *automatically* after each commit is made to the `main` branch. + +How to build manually: ``` cd dspace/src/main/docker/dspace-postgres-pgcrypto-curl -docker build -t dspace/dspace-postgres-pgcrypto:loadsql . +docker build -t dspace/dspace-postgres-pgcrypto:dspace-7_x-loadsql . ``` -**This image is built manually.** It should be rebuilt as needed. +Similar to `dspace-postgres-pgcrypto` above, you can also modify the version of PostgreSQL or the PostgreSQL user's password. +See examples above. A copy of this file exists in the DSpace 6 branch. -Admins to our DockerHub repo can publish with the following command. +Admins to our DockerHub repo can (manually) publish with the following command. ``` -docker push dspace/dspace-postgres-pgcrypto:loadsql +docker push dspace/dspace-postgres-pgcrypto:dspace-7_x-loadsql ``` -## dspace/src/main/docker/dspace-shibboleth/Dockerfile +## ./dspace-shibboleth/Dockerfile This is a test / demo image which provides an Apache HTTPD proxy (in front of Tomcat) -with mod_shib & Shibboleth installed. It is primarily for usage for -testing DSpace's Shibboleth integration. It uses https://samltest.id/ as the Shibboleth IDP +with `mod_shib` & Shibboleth installed based on the +[DSpace Shibboleth configuration instructions](https://wiki.lyrasis.org/display/DSDOC7x/Authentication+Plugins#AuthenticationPlugins-ShibbolethAuthentication). +It is primarily for usage for testing DSpace's Shibboleth integration. +It uses https://samltest.id/ as the Shibboleth IDP **This image is built manually.** It should be rebuilt as needed. @@ -130,7 +151,7 @@ docker run -i -t -d -p 80:80 -p 443:443 dspace/dspace-shibboleth This image can also be rebuilt using the `../docker-compose/docker-compose-shibboleth.yml` script. -## dspace/src/main/docker/dspace-solr/Dockerfile +## ./dspace-solr/Dockerfile This Dockerfile builds a Solr image with DSpace Solr configsets included. It can be pulled / built following the [docker compose resources](../docker-compose/README.md) @@ -148,9 +169,10 @@ restart the `dspacesolr` container for the changes to be deployed. From DSpace r docker-compose -p d7 up --detach --build dspacesolr ``` -## test/ folder +## ./test/ folder These resources are bundled into the `dspace/dspace:dspace-*-test` image at build time. +See the `Dockerfile.test` section above for more information about the test image. ## Debugging Docker builds diff --git a/dspace/src/main/docker/dspace-postgres-pgcrypto-curl/Dockerfile b/dspace/src/main/docker/dspace-postgres-pgcrypto-curl/Dockerfile index 3e38cdae27a3..b2131a740262 100644 --- a/dspace/src/main/docker/dspace-postgres-pgcrypto-curl/Dockerfile +++ b/dspace/src/main/docker/dspace-postgres-pgcrypto-curl/Dockerfile @@ -7,16 +7,17 @@ # # To build for example use: -# docker build --build-arg POSTGRES_VERSION=11 --build-arg POSTGRES_PASSWORD=mypass -f ./dspace/src/main/docker/dspace-postgres-pgcrypto-curl/Dockerfile . +# docker build --build-arg POSTGRES_VERSION=13 --build-arg POSTGRES_PASSWORD=mypass ./dspace/src/main/docker/dspace-postgres-pgcrypto-curl/ # This will be published as dspace/dspace-postgres-pgcrypto:$DSPACE_VERSION-loadsql -ARG POSTGRES_VERSION=11 +ARG POSTGRES_VERSION=13 ARG POSTGRES_PASSWORD=dspace FROM postgres:${POSTGRES_VERSION} ENV POSTGRES_DB dspace ENV POSTGRES_USER dspace +ENV POSTGRES_PASSWORD ${POSTGRES_PASSWORD} # Install curl which is necessary to load SQL file RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/* diff --git a/dspace/src/main/docker/dspace-postgres-pgcrypto-curl/install-pgcrypto.sh b/dspace/src/main/docker/dspace-postgres-pgcrypto-curl/install-pgcrypto.sh index 054d3dede5dc..3f8e95e1044f 100644 --- a/dspace/src/main/docker/dspace-postgres-pgcrypto-curl/install-pgcrypto.sh +++ b/dspace/src/main/docker/dspace-postgres-pgcrypto-curl/install-pgcrypto.sh @@ -11,15 +11,33 @@ set -e CHECKFILE=/pgdata/ingest.hasrun.flag +# If $LOADSQL environment variable set, use 'curl' to download that SQL and run it in PostgreSQL +# This can be used to initialize a database based on test data available on the web. if [ ! -f $CHECKFILE -a ! -z ${LOADSQL} ] then - curl ${LOADSQL} -L -s --output /tmp/dspace.sql - psql -U $POSTGRES_USER < /tmp/dspace.sql + # Download SQL file to /tmp/dspace-db-init.sql + curl ${LOADSQL} -L -s --output /tmp/dspace-db-init.sql + # Load into PostgreSQL + psql -U $POSTGRES_USER < /tmp/dspace-db-init.sql + # Remove downloaded file + rm /tmp/dspace-db-init.sql touch $CHECKFILE exit fi +# If $LOCALSQL environment variable set, then simply run it in PostgreSQL +# This can be used to restore data from a pg_dump or similar. +if [ ! -f $CHECKFILE -a ! -z ${LOCALSQL} ] +then + # Load into PostgreSQL + psql -U $POSTGRES_USER < ${LOCALSQL} + + touch $CHECKFILE + exit +fi + +# Then, setup pgcrypto on this database psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" <<-EOSQL -- Create a new schema in this database named "extensions" (or whatever you want to name it) CREATE SCHEMA extensions; diff --git a/dspace/src/main/docker/dspace-postgres-pgcrypto/Dockerfile b/dspace/src/main/docker/dspace-postgres-pgcrypto/Dockerfile index bf8344103c44..7dde1a6bfd1c 100644 --- a/dspace/src/main/docker/dspace-postgres-pgcrypto/Dockerfile +++ b/dspace/src/main/docker/dspace-postgres-pgcrypto/Dockerfile @@ -7,16 +7,17 @@ # # To build for example use: -# docker build --build-arg POSTGRES_VERSION=11 --build-arg POSTGRES_PASSWORD=mypass -f ./dspace/src/main/docker/dspace-postgres-pgcrypto/Dockerfile . +# docker build --build-arg POSTGRES_VERSION=13 --build-arg POSTGRES_PASSWORD=mypass ./dspace/src/main/docker/dspace-postgres-pgcrypto/ # This will be published as dspace/dspace-postgres-pgcrypto:$DSPACE_VERSION -ARG POSTGRES_VERSION=11 +ARG POSTGRES_VERSION=13 ARG POSTGRES_PASSWORD=dspace FROM postgres:${POSTGRES_VERSION} ENV POSTGRES_DB dspace ENV POSTGRES_USER dspace +ENV POSTGRES_PASSWORD ${POSTGRES_PASSWORD} # Copy over script which will initialize database and install pgcrypto extension COPY install-pgcrypto.sh /docker-entrypoint-initdb.d/ From 61845da1aab8ee7a80aab524b54144b05d0f90f0 Mon Sep 17 00:00:00 2001 From: Hardy Pottinger Date: Tue, 25 Apr 2023 11:17:24 -0500 Subject: [PATCH 096/510] Install unzip in Dockerfile.cli so cli.ingest.yml can work correctly (requires unzip) --- Dockerfile.cli | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile.cli b/Dockerfile.cli index 6878d8f8dbfb..62e83b79ef02 100644 --- a/Dockerfile.cli +++ b/Dockerfile.cli @@ -33,9 +33,9 @@ WORKDIR /dspace-src ENV ANT_VERSION 1.10.13 ENV ANT_HOME /tmp/ant-$ANT_VERSION ENV PATH $ANT_HOME/bin:$PATH -# Need wget to install ant +# Need wget to install ant, and unzip for managing AIPs RUN apt-get update \ - && apt-get install -y --no-install-recommends wget \ + && apt-get install -y --no-install-recommends wget unzip \ && apt-get purge -y --auto-remove \ && rm -rf /var/lib/apt/lists/* # Download and install 'ant' From a15a41401b7725996a5e251319ed09444e949eb0 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 6 Mar 2023 09:34:29 -0600 Subject: [PATCH 097/510] Update Spring & Spring Boot versions --- pom.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index 3b6f7edb6549..062544c91c41 100644 --- a/pom.xml +++ b/pom.xml @@ -19,9 +19,9 @@ 11 - 5.3.20 - 2.6.8 - 5.6.5 + 5.3.25 + 2.7.9 + 5.7.7 5.6.5.Final 6.0.23.Final 42.4.3 From 42f548b17e282caeef614e6e87012ad20c4775f9 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 10 Mar 2023 14:40:07 -0600 Subject: [PATCH 098/510] Remove check for double-encoded param. It is no longer double-encoded after upgrading Spring Boot. --- .../java/org/dspace/app/rest/DiscoveryRestControllerIT.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryRestControllerIT.java index 8b01e7b37790..a115c8aa2f15 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryRestControllerIT.java @@ -6473,7 +6473,7 @@ public void discoverFacetsSupervisedByWithPrefixTest() throws Exception { .andExpect(jsonPath("$.facetType", equalTo("authority"))) //There always needs to be a self link available .andExpect(jsonPath("$._links.self.href", - containsString("api/discover/facets/supervisedBy?prefix=group%2520B&configuration=supervision"))) + containsString("api/discover/facets/supervisedBy?prefix=group%20B&configuration=supervision"))) //This is how the page object must look like because it's the default with size 20 .andExpect(jsonPath("$.page", is(PageMatcher.pageEntry(0, 20)))) From 66d7c1bde6295798cea551f1f463fccb9011ea6d Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 24 Mar 2023 09:16:52 -0500 Subject: [PATCH 099/510] Update to latest spring-boot and spring --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 062544c91c41..1b4d34ccbf98 100644 --- a/pom.xml +++ b/pom.xml @@ -19,8 +19,8 @@ 11 - 5.3.25 - 2.7.9 + 5.3.26 + 2.7.10 5.7.7 5.6.5.Final 6.0.23.Final From 8cd82e5d2e13b4fdca1fbc64634bbf825ced0708 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 25 Apr 2023 17:13:26 -0500 Subject: [PATCH 100/510] Update to Spring Boot v2.7.11 --- pom.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index 1b4d34ccbf98..aec4b57fc1ed 100644 --- a/pom.xml +++ b/pom.xml @@ -19,9 +19,9 @@ 11 - 5.3.26 - 2.7.10 - 5.7.7 + 5.3.27 + 2.7.11 + 5.7.8 5.6.5.Final 6.0.23.Final 42.4.3 From 535edef1b3498cf32b3214fe91af55a2a7ec5ae8 Mon Sep 17 00:00:00 2001 From: Abel Date: Fri, 28 Apr 2023 03:53:43 +0200 Subject: [PATCH 101/510] Add ImageMagickVideoThumbnailFilter --- .../ImageMagickVideoThumbnailFilter.java | 74 +++++++++++++++++++ dspace/config/dspace.cfg | 5 ++ 2 files changed, 79 insertions(+) create mode 100644 dspace-api/src/main/java/org/dspace/app/mediafilter/ImageMagickVideoThumbnailFilter.java diff --git a/dspace-api/src/main/java/org/dspace/app/mediafilter/ImageMagickVideoThumbnailFilter.java b/dspace-api/src/main/java/org/dspace/app/mediafilter/ImageMagickVideoThumbnailFilter.java new file mode 100644 index 000000000000..2f5ff9da0f65 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/mediafilter/ImageMagickVideoThumbnailFilter.java @@ -0,0 +1,74 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.mediafilter; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.InputStream; +import java.io.IOException; +import java.nio.file.Files; + +import org.dspace.content.Item; +import org.im4java.core.ConvertCmd; +import org.im4java.core.IM4JavaException; +import org.im4java.core.IMOperation; + + +/** + * Filter video bitstreams, scaling the image to be within the bounds of + * thumbnail.maxwidth, thumbnail.maxheight, the size we want our thumbnail to be + * no bigger than. Creates only JPEGs. + */ +public class ImageMagickVideoThumbnailFilter extends ImageMagickThumbnailFilter { + private static final int DEFAULT_WIDTH = 340; + private static final int DEFAULT_HEIGHT = 280; + + /** + * @param currentItem item + * @param source source input stream + * @param verbose verbose mode + * @return InputStream the resulting input stream + * @throws Exception if error + */ + @Override + public InputStream getDestinationStream(Item currentItem, InputStream source, boolean verbose) + throws Exception { + File f = inputStreamToTempFile(source, "imthumb", ".tmp"); + File f2 = null; + try { + f2 = getThumbnailFile(f, verbose); + byte[] bytes = Files.readAllBytes(f2.toPath()); + return new ByteArrayInputStream(bytes); + } finally { + //noinspection ResultOfMethodCallIgnored + f.delete(); + if (f2 != null) { + //noinspection ResultOfMethodCallIgnored + f2.delete(); + } + } + } + + public File getThumbnailFile(File f, boolean verbose) + throws IOException, InterruptedException, IM4JavaException { + File f2 = new File(f.getParentFile(), f.getName() + ".jpg"); + f2.deleteOnExit(); + ConvertCmd cmd = new ConvertCmd(); + IMOperation op = new IMOperation(); + op.autoOrient(); + op.addImage("VIDEO:" + f.getAbsolutePath() + "[100]"); + op.thumbnail(configurationService.getIntProperty("thumbnail.maxwidth", DEFAULT_WIDTH), + configurationService.getIntProperty("thumbnail.maxheight", DEFAULT_HEIGHT)); + op.addImage(f2.getAbsolutePath()); + if (verbose) { + System.out.println("IM Thumbnail Param: " + op); + } + cmd.run(op); + return f2; + } +} diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 2a35e894593a..138a690af45a 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -465,6 +465,9 @@ filter.plugins = PDFBox JPEG Thumbnail # remove "JPEG Thumbnail" from the plugin list # uncomment and insert the following line into the plugin list # ImageMagick Image Thumbnail, ImageMagick PDF Thumbnail, \ +# [To enable ImageMagick Video Thumbnails (requires both ImageMagick and ffmpeg installed)]: +# uncomment and insert the following line into the plugin list +# ImageMagick Video Thumbnail, \ #Assign 'human-understandable' names to each filter plugin.named.org.dspace.app.mediafilter.FormatFilter = org.dspace.app.mediafilter.TikaTextExtractionFilter = Text Extractor @@ -473,6 +476,7 @@ plugin.named.org.dspace.app.mediafilter.FormatFilter = org.dspace.app.mediafilte plugin.named.org.dspace.app.mediafilter.FormatFilter = org.dspace.app.mediafilter.PDFBoxThumbnail = PDFBox JPEG Thumbnail plugin.named.org.dspace.app.mediafilter.FormatFilter = org.dspace.app.mediafilter.ImageMagickImageThumbnailFilter = ImageMagick Image Thumbnail plugin.named.org.dspace.app.mediafilter.FormatFilter = org.dspace.app.mediafilter.ImageMagickPdfThumbnailFilter = ImageMagick PDF Thumbnail +plugin.named.org.dspace.app.mediafilter.FormatFilter = org.dspace.app.mediafilter.ImageMagickVideoThumbnailFilter = ImageMagick Video Thumbnail #Configure each filter's input format(s) # NOTE: The TikaTextExtractionFilter can support any file formats that are supported by Apache Tika. So, you can easily @@ -496,6 +500,7 @@ filter.org.dspace.app.mediafilter.JPEGFilter.inputFormats = BMP, GIF, JPEG, imag filter.org.dspace.app.mediafilter.BrandedPreviewJPEGFilter.inputFormats = BMP, GIF, JPEG, image/png filter.org.dspace.app.mediafilter.ImageMagickImageThumbnailFilter.inputFormats = BMP, GIF, image/png, JPG, TIFF, JPEG, JPEG 2000 filter.org.dspace.app.mediafilter.ImageMagickPdfThumbnailFilter.inputFormats = Adobe PDF +filter.org.dspace.app.mediafilter.ImageMagickVideoThumbnailFilter.inputFormats = Video MP4 filter.org.dspace.app.mediafilter.PDFBoxThumbnail.inputFormats = Adobe PDF #Publicly accessible thumbnails of restricted content. From 43661de74ea7e1f4dde4ea2dae26ed5a9e3f34f6 Mon Sep 17 00:00:00 2001 From: Abel Date: Fri, 28 Apr 2023 03:59:00 +0200 Subject: [PATCH 102/510] Add Override annotation --- .../dspace/app/mediafilter/ImageMagickVideoThumbnailFilter.java | 1 + 1 file changed, 1 insertion(+) diff --git a/dspace-api/src/main/java/org/dspace/app/mediafilter/ImageMagickVideoThumbnailFilter.java b/dspace-api/src/main/java/org/dspace/app/mediafilter/ImageMagickVideoThumbnailFilter.java index 2f5ff9da0f65..f6f657eaa0a3 100644 --- a/dspace-api/src/main/java/org/dspace/app/mediafilter/ImageMagickVideoThumbnailFilter.java +++ b/dspace-api/src/main/java/org/dspace/app/mediafilter/ImageMagickVideoThumbnailFilter.java @@ -54,6 +54,7 @@ public InputStream getDestinationStream(Item currentItem, InputStream source, bo } } + @Override public File getThumbnailFile(File f, boolean verbose) throws IOException, InterruptedException, IM4JavaException { File f2 = new File(f.getParentFile(), f.getName() + ".jpg"); From d256065fc2795f43c6197b09f5ee26e04c7827a5 Mon Sep 17 00:00:00 2001 From: Abel Date: Fri, 28 Apr 2023 04:09:26 +0200 Subject: [PATCH 103/510] Set FRAME_NUMBER using a constant --- .../app/mediafilter/ImageMagickVideoThumbnailFilter.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/app/mediafilter/ImageMagickVideoThumbnailFilter.java b/dspace-api/src/main/java/org/dspace/app/mediafilter/ImageMagickVideoThumbnailFilter.java index f6f657eaa0a3..c5ee4639d761 100644 --- a/dspace-api/src/main/java/org/dspace/app/mediafilter/ImageMagickVideoThumbnailFilter.java +++ b/dspace-api/src/main/java/org/dspace/app/mediafilter/ImageMagickVideoThumbnailFilter.java @@ -27,6 +27,7 @@ public class ImageMagickVideoThumbnailFilter extends ImageMagickThumbnailFilter { private static final int DEFAULT_WIDTH = 340; private static final int DEFAULT_HEIGHT = 280; + private static final int FRAME_NUMBER = 100; /** * @param currentItem item @@ -62,7 +63,7 @@ public File getThumbnailFile(File f, boolean verbose) ConvertCmd cmd = new ConvertCmd(); IMOperation op = new IMOperation(); op.autoOrient(); - op.addImage("VIDEO:" + f.getAbsolutePath() + "[100]"); + op.addImage("VIDEO:" + f.getAbsolutePath() + "[" + FRAME_NUMBER + "]"); op.thumbnail(configurationService.getIntProperty("thumbnail.maxwidth", DEFAULT_WIDTH), configurationService.getIntProperty("thumbnail.maxheight", DEFAULT_HEIGHT)); op.addImage(f2.getAbsolutePath()); From 413c6f443f597a8addfd4db85974a2700c0fd6a5 Mon Sep 17 00:00:00 2001 From: Abel Date: Fri, 28 Apr 2023 04:28:38 +0200 Subject: [PATCH 104/510] Use same defaults than ImageMagickThumbnailFilter --- .../app/mediafilter/ImageMagickVideoThumbnailFilter.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/mediafilter/ImageMagickVideoThumbnailFilter.java b/dspace-api/src/main/java/org/dspace/app/mediafilter/ImageMagickVideoThumbnailFilter.java index c5ee4639d761..6f8706d774df 100644 --- a/dspace-api/src/main/java/org/dspace/app/mediafilter/ImageMagickVideoThumbnailFilter.java +++ b/dspace-api/src/main/java/org/dspace/app/mediafilter/ImageMagickVideoThumbnailFilter.java @@ -25,8 +25,8 @@ * no bigger than. Creates only JPEGs. */ public class ImageMagickVideoThumbnailFilter extends ImageMagickThumbnailFilter { - private static final int DEFAULT_WIDTH = 340; - private static final int DEFAULT_HEIGHT = 280; + private static final int DEFAULT_WIDTH = 180; + private static final int DEFAULT_HEIGHT = 120; private static final int FRAME_NUMBER = 100; /** From f7414f1d58c5a80dc1ac88017b1facc6451235f5 Mon Sep 17 00:00:00 2001 From: Abel Date: Fri, 28 Apr 2023 04:40:23 +0200 Subject: [PATCH 105/510] Add a comment about ImageMagick policies --- dspace/config/dspace.cfg | 3 +++ 1 file changed, 3 insertions(+) diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 138a690af45a..62d429a7e205 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -468,6 +468,9 @@ filter.plugins = PDFBox JPEG Thumbnail # [To enable ImageMagick Video Thumbnails (requires both ImageMagick and ffmpeg installed)]: # uncomment and insert the following line into the plugin list # ImageMagick Video Thumbnail, \ +# NOTE: pay attention to the ImageMagick policies and reource limits in its policy.xml +# configuration file. The limits may have to be increased if a "cache resources +# exhausted" error is thrown. #Assign 'human-understandable' names to each filter plugin.named.org.dspace.app.mediafilter.FormatFilter = org.dspace.app.mediafilter.TikaTextExtractionFilter = Text Extractor From 1138667f3a3fc4c4ee76667b79e882bfcd086daa Mon Sep 17 00:00:00 2001 From: Francesco Pio Scognamiglio Date: Fri, 28 Apr 2023 09:50:12 +0200 Subject: [PATCH 106/510] [CST-6402] fix saf import via remote url: the url param is a string --- .../org/dspace/app/itemimport/ItemImportScriptConfiguration.java | 1 - 1 file changed, 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportScriptConfiguration.java b/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportScriptConfiguration.java index cfe97ad89bd6..2d304d8a7dc8 100644 --- a/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportScriptConfiguration.java +++ b/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportScriptConfiguration.java @@ -67,7 +67,6 @@ public Options getOptions() { .hasArg().build()); options.addOption(Option.builder("u").longOpt("url") .desc("url of zip file") - .type(InputStream.class) .hasArg().build()); options.addOption(Option.builder("c").longOpt("collection") .desc("destination collection(s) Handle or database ID") From dd1a5188b60514e7890b7dde8457f9f1e4df1338 Mon Sep 17 00:00:00 2001 From: Francesco Pio Scognamiglio Date: Fri, 28 Apr 2023 09:51:50 +0200 Subject: [PATCH 107/510] [CST-6402] manage saf import via remote url in CLI mode --- .../org/dspace/app/itemimport/ItemImport.java | 6 ++-- .../dspace/app/itemimport/ItemImportCLI.java | 33 ++++++++++++++++--- .../ItemImportCLIScriptConfiguration.java | 3 ++ 3 files changed, 36 insertions(+), 6 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImport.java b/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImport.java index 2b1089e3e01f..ac9db7605103 100644 --- a/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImport.java +++ b/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImport.java @@ -79,7 +79,7 @@ public class ItemImport extends DSpaceRunnable { protected String zipfilename = null; protected boolean help = false; protected File workDir = null; - private File workFile = null; + protected File workFile = null; protected static final CollectionService collectionService = ContentServiceFactory.getInstance().getCollectionService(); @@ -239,6 +239,9 @@ public void internalRun() throws Exception { if (zip) { FileUtils.deleteDirectory(new File(sourcedir)); FileUtils.deleteDirectory(workDir); + if (remoteUrl && workFile != null && workFile.exists()) { + workFile.delete(); + } } Date endTime = new Date(); @@ -308,7 +311,6 @@ protected void process(Context context, ItemImportService itemImportService, handler.writeFilestream(context, MAPFILE_FILENAME, mapfileInputStream, MAPFILE_BITSTREAM_TYPE); } finally { mapFile.delete(); - workFile.delete(); } } diff --git a/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportCLI.java b/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportCLI.java index 35de7b443a97..08ecdd097c16 100644 --- a/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportCLI.java +++ b/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportCLI.java @@ -8,10 +8,14 @@ package org.dspace.app.itemimport; import java.io.File; +import java.io.InputStream; +import java.net.URL; import java.sql.SQLException; import java.util.List; +import java.util.Optional; import java.util.UUID; +import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.StringUtils; import org.dspace.app.itemimport.service.ItemImportService; import org.dspace.content.Collection; @@ -96,10 +100,25 @@ protected void process(Context context, ItemImportService itemImportService, protected void readZip(Context context, ItemImportService itemImportService) throws Exception { // If this is a zip archive, unzip it first if (zip) { - workDir = new File(itemImportService.getTempWorkDir() + File.separator + TEMP_DIR - + File.separator + context.getCurrentUser().getID()); - sourcedir = itemImportService.unzip( - new File(sourcedir + File.separator + zipfilename), workDir.getAbsolutePath()); + if (!remoteUrl) { + workDir = new File(itemImportService.getTempWorkDir() + File.separator + TEMP_DIR + + File.separator + context.getCurrentUser().getID()); + sourcedir = itemImportService.unzip( + new File(sourcedir + File.separator + zipfilename), workDir.getAbsolutePath()); + } else { + // manage zip via remote url + Optional optionalFileStream = Optional.ofNullable(new URL(zipfilename).openStream()); + if (optionalFileStream.isPresent()) { + workFile = new File(itemImportService.getTempWorkDir() + File.separator + + zipfilename + "-" + context.getCurrentUser().getID()); + FileUtils.copyInputStreamToFile(optionalFileStream.get(), workFile); + } else { + throw new IllegalArgumentException( + "Error reading file, the file couldn't be found for filename: " + zipfilename); + } + workDir = new File(itemImportService.getTempWorkDir() + File.separator + TEMP_DIR); + sourcedir = itemImportService.unzip(workFile, workDir.getAbsolutePath()); + } } } @@ -120,6 +139,12 @@ protected void setZip() { zip = true; zipfilename = commandLine.getOptionValue('z'); } + + if (commandLine.hasOption('u')) { // remote url + zip = true; + remoteUrl = true; + zipfilename = commandLine.getOptionValue('u'); + } } @Override diff --git a/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportCLIScriptConfiguration.java b/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportCLIScriptConfiguration.java index d265cbf4a1d6..89abd7155b39 100644 --- a/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportCLIScriptConfiguration.java +++ b/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportCLIScriptConfiguration.java @@ -37,6 +37,9 @@ public Options getOptions() { options.addOption(Option.builder("z").longOpt("zip") .desc("name of zip file") .hasArg().required(false).build()); + options.addOption(Option.builder("u").longOpt("url") + .desc("url of zip file") + .hasArg().build()); options.addOption(Option.builder("c").longOpt("collection") .desc("destination collection(s) Handle or database ID") .hasArg().required(false).build()); From 77f6daf0a32e0266fe92aa7c59ec91927ddcbbe9 Mon Sep 17 00:00:00 2001 From: Abel Date: Fri, 28 Apr 2023 13:36:20 +0200 Subject: [PATCH 108/510] Change import order --- .../dspace/app/mediafilter/ImageMagickVideoThumbnailFilter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/app/mediafilter/ImageMagickVideoThumbnailFilter.java b/dspace-api/src/main/java/org/dspace/app/mediafilter/ImageMagickVideoThumbnailFilter.java index 6f8706d774df..4221a514d7d5 100644 --- a/dspace-api/src/main/java/org/dspace/app/mediafilter/ImageMagickVideoThumbnailFilter.java +++ b/dspace-api/src/main/java/org/dspace/app/mediafilter/ImageMagickVideoThumbnailFilter.java @@ -9,8 +9,8 @@ import java.io.ByteArrayInputStream; import java.io.File; -import java.io.InputStream; import java.io.IOException; +import java.io.InputStream; import java.nio.file.Files; import org.dspace.content.Item; From c919df81fb15868169a0a14d5be42079fe0c3908 Mon Sep 17 00:00:00 2001 From: Michael Plate Date: Fri, 28 Apr 2023 17:09:55 +0200 Subject: [PATCH 109/510] LDAP Auth extended for many groups --- .../authenticate/LDAPAuthentication.java | 90 ++++++++++++------- 1 file changed, 59 insertions(+), 31 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/authenticate/LDAPAuthentication.java b/dspace-api/src/main/java/org/dspace/authenticate/LDAPAuthentication.java index f3c6022e02c2..afd82db863ba 100644 --- a/dspace-api/src/main/java/org/dspace/authenticate/LDAPAuthentication.java +++ b/dspace-api/src/main/java/org/dspace/authenticate/LDAPAuthentication.java @@ -11,9 +11,11 @@ import java.io.IOException; import java.sql.SQLException; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Hashtable; +import java.util.Iterator; import java.util.List; import javax.naming.NamingEnumeration; import javax.naming.NamingException; @@ -64,6 +66,7 @@ * @author Reuben Pasquini * @author Samuel Ottenhoff * @author Ivan Masár + * @author Michael Plate */ public class LDAPAuthentication implements AuthenticationMethod { @@ -391,7 +394,7 @@ private static class SpeakerToLDAP { protected String ldapGivenName = null; protected String ldapSurname = null; protected String ldapPhone = null; - protected String ldapGroup = null; + protected ArrayList ldapGroup = null; /** * LDAP settings @@ -406,9 +409,9 @@ private static class SpeakerToLDAP { final String ldap_surname_field; final String ldap_phone_field; final String ldap_group_field; - final boolean useTLS; + SpeakerToLDAP(Logger thelog) { ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); @@ -547,7 +550,11 @@ protected String getDNOfUser(String adminUser, String adminPassword, Context con if (attlist[4] != null) { att = atts.get(attlist[4]); if (att != null) { - ldapGroup = (String) att.get(); + // loop through all groups returned by LDAP + ldapGroup = new ArrayList(); + for (NamingEnumeration val = att.getAll(); val.hasMoreElements(); ) { + ldapGroup.add((String) val.next()); + } } } @@ -693,48 +700,69 @@ public String getName() { /* * Add authenticated users to the group defined in dspace.cfg by * the authentication-ldap.login.groupmap.* key. + * + * @param dn + * The string containing distinguished name of the user + * + * @param group + * List of strings with LDAP dn of groups + * + * @param context + * DSpace context */ - private void assignGroups(String dn, String group, Context context) { + private void assignGroups(String dn, ArrayList group, Context context) { if (StringUtils.isNotBlank(dn)) { System.out.println("dn:" + dn); int i = 1; String groupMap = configurationService.getProperty("authentication-ldap.login.groupmap." + i); - boolean cmp; + + // groupmap contains the mapping of LDAP groups to DSpace groups + // outer loop with the DSpace groups while (groupMap != null) { String t[] = groupMap.split(":"); String ldapSearchString = t[0]; String dspaceGroupName = t[1]; - if (group == null) { - cmp = StringUtils.containsIgnoreCase(dn, ldapSearchString + ","); - } else { - cmp = StringUtils.equalsIgnoreCase(group, ldapSearchString); - } + // list of strings with dn from LDAP groups + // inner loop + Iterator groupIterator = group.iterator(); + while (groupIterator.hasNext()) { - if (cmp) { - // assign user to this group - try { - Group ldapGroup = groupService.findByName(context, dspaceGroupName); - if (ldapGroup != null) { - groupService.addMember(context, ldapGroup, context.getCurrentUser()); - groupService.update(context, ldapGroup); - } else { - // The group does not exist - log.warn(LogHelper.getHeader(context, - "ldap_assignGroupsBasedOnLdapDn", - "Group defined in authentication-ldap.login.groupmap." + i - + " does not exist :: " + dspaceGroupName)); + // save the current entry from iterator for further use + String currentGroup = groupIterator.next(); + + // very much the old code from DSpace <= 7.5 + if (currentGroup == null) { + cmp = StringUtils.containsIgnoreCase(dn, ldapSearchString + ","); + } else { + cmp = StringUtils.equalsIgnoreCase(currentGroup, ldapSearchString); + } + + if (cmp) { + // assign user to this group + try { + Group ldapGroup = groupService.findByName(context, dspaceGroupName); + if (ldapGroup != null) { + groupService.addMember(context, ldapGroup, context.getCurrentUser()); + groupService.update(context, ldapGroup); + } else { + // The group does not exist + log.warn(LogHelper.getHeader(context, + "ldap_assignGroupsBasedOnLdapDn", + "Group defined in authentication-ldap.login.groupmap." + i + + " does not exist :: " + dspaceGroupName)); + } + } catch (AuthorizeException ae) { + log.debug(LogHelper.getHeader(context, + "assignGroupsBasedOnLdapDn could not authorize addition to " + + "group", + dspaceGroupName)); + } catch (SQLException e) { + log.debug(LogHelper.getHeader(context, "assignGroupsBasedOnLdapDn could not find group", + dspaceGroupName)); } - } catch (AuthorizeException ae) { - log.debug(LogHelper.getHeader(context, - "assignGroupsBasedOnLdapDn could not authorize addition to " + - "group", - dspaceGroupName)); - } catch (SQLException e) { - log.debug(LogHelper.getHeader(context, "assignGroupsBasedOnLdapDn could not find group", - dspaceGroupName)); } } From fdf4a4d5712e9b1be3a9966dc080af90db002243 Mon Sep 17 00:00:00 2001 From: Chandaluri Vamsi Krishna Date: Sun, 30 Apr 2023 22:32:42 +0530 Subject: [PATCH 110/510] Updated to fetch dynamic default description Signed-off-by: Chandaluri Vamsi Krishna --- .../src/main/java/org/dspace/app/util/SyndicationFeed.java | 3 ++- dspace-api/src/main/resources/Messages.properties | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/app/util/SyndicationFeed.java b/dspace-api/src/main/java/org/dspace/app/util/SyndicationFeed.java index 5b0520a3e1d1..c1402499c444 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/SyndicationFeed.java +++ b/dspace-api/src/main/java/org/dspace/app/util/SyndicationFeed.java @@ -51,6 +51,7 @@ import org.dspace.content.service.CommunityService; import org.dspace.content.service.ItemService; import org.dspace.core.Context; +import org.dspace.core.I18nUtil; import org.dspace.discovery.IndexableObject; import org.dspace.discovery.indexobject.IndexableCollection; import org.dspace.discovery.indexobject.IndexableCommunity; @@ -233,7 +234,7 @@ public void populate(HttpServletRequest request, Context context, IndexableObjec localize(labels, MSG_FEED_TITLE) : defaultTitle); if (defaultDescriptionField == null || defaultDescriptionField == "") { - defaultDescriptionField = "No Description"; + defaultDescriptionField = I18nUtil.getMessage("org.dspace.app.util.SyndicationFeed.no-description"); } feed.setDescription(defaultDescriptionField); diff --git a/dspace-api/src/main/resources/Messages.properties b/dspace-api/src/main/resources/Messages.properties index c478e4e69b2e..9be443f5eaec 100644 --- a/dspace-api/src/main/resources/Messages.properties +++ b/dspace-api/src/main/resources/Messages.properties @@ -51,6 +51,7 @@ metadata.bitstream.iiif-virtual.bytes = File size metadata.bitstream.iiif-virtual.checksum = Checksum org.dspace.app.itemexport.no-result = The DSpaceObject that you specified has no items. +org.dspace.app.util.SyndicationFeed.no-description = No Description org.dspace.checker.ResultsLogger.bitstream-format = Bitstream format org.dspace.checker.ResultsLogger.bitstream-found = Bitstream found org.dspace.checker.ResultsLogger.bitstream-id = Bitstream ID From 80af0665c12b3707351d45db962975657ebfe7c9 Mon Sep 17 00:00:00 2001 From: nwoodward Date: Mon, 1 May 2023 15:15:28 -0500 Subject: [PATCH 111/510] send "forgot password" email if user tries to register with email address already in eperson table --- .../RegistrationRestRepository.java | 36 ++++++++++++------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RegistrationRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RegistrationRestRepository.java index ac44ccb4c274..c3c94a4d0bf0 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RegistrationRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RegistrationRestRepository.java @@ -138,21 +138,31 @@ public RegistrationRest createAndReturn(Context context) { + registrationRest.getEmail(), e); } } else if (accountType.equalsIgnoreCase(TYPE_REGISTER)) { - try { - String email = registrationRest.getEmail(); - if (!AuthorizeUtil.authorizeNewAccountRegistration(context, request)) { - throw new AccessDeniedException( - "Registration is disabled, you are not authorized to create a new Authorization"); + if (eperson == null) { + try { + String email = registrationRest.getEmail(); + if (!AuthorizeUtil.authorizeNewAccountRegistration(context, request)) { + throw new AccessDeniedException( + "Registration is disabled, you are not authorized to create a new Authorization"); + } + if (!authenticationService.canSelfRegister(context, request, email)) { + throw new UnprocessableEntityException( + String.format("Registration is not allowed with email address" + + " %s", email)); + } + accountService.sendRegistrationInfo(context, email); + } catch (SQLException | IOException | MessagingException | AuthorizeException e) { + log.error("Something went wrong with sending registration info email: " + + registrationRest.getEmail(), e); } - if (!authenticationService.canSelfRegister(context, request, email)) { - throw new UnprocessableEntityException( - String.format("Registration is not allowed with email address" + - " %s", email)); + } else { + // if an eperson with this email already exists then send "forgot password" email instead + try { + accountService.sendForgotPasswordInfo(context, registrationRest.getEmail()); + } catch (SQLException | IOException | MessagingException | AuthorizeException e) { + log.error("Something went wrong with sending forgot password info email: " + + registrationRest.getEmail(), e); } - accountService.sendRegistrationInfo(context, email); - } catch (SQLException | IOException | MessagingException | AuthorizeException e) { - log.error("Something went wrong with sending registration info email: " - + registrationRest.getEmail(), e); } } return null; From ef138cf76d4ef42df57fb90454667030f181842f Mon Sep 17 00:00:00 2001 From: Francesco Pio Scognamiglio Date: Tue, 2 May 2023 09:19:32 +0200 Subject: [PATCH 112/510] [CST-6402] fix saf import via remote url: the source param is not used --- .../src/main/java/org/dspace/app/itemimport/ItemImportCLI.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportCLI.java b/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportCLI.java index 08ecdd097c16..1a71a8c4c09e 100644 --- a/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportCLI.java +++ b/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportCLI.java @@ -66,7 +66,7 @@ protected void validate(Context context) { handler.logError("Must run with either add, replace, or remove (run with -h flag for details)"); throw new UnsupportedOperationException("Must run with either add, replace, or remove"); } else if ("add".equals(command) || "replace".equals(command)) { - if (sourcedir == null) { + if (!remoteUrl && sourcedir == null) { handler.logError("A source directory containing items must be set (run with -h flag for details)"); throw new UnsupportedOperationException("A source directory containing items must be set"); } From 603cd9a19b7ad23a4b1d4923370c8508bbea75fc Mon Sep 17 00:00:00 2001 From: Ma-Tador <86478723+Ma-Tador@users.noreply.github.com> Date: Tue, 2 May 2023 12:11:44 +0200 Subject: [PATCH 113/510] Remove white spaces to comply with DSpace checkstyle --- .../app/rest/repository/RegistrationRestRepository.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RegistrationRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RegistrationRestRepository.java index 2cc14c1ff705..98f129b4bf9d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RegistrationRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RegistrationRestRepository.java @@ -87,7 +87,7 @@ public RegistrationRest findOne(Context context, Integer integer) { public Page findAll(Context context, Pageable pageable) { throw new RepositoryMethodNotImplementedException("No implementation found; Method not allowed!", ""); } - + @Override public RegistrationRest createAndReturn(Context context) { HttpServletRequest request = requestService.getCurrentRequest().getHttpServletRequest(); @@ -136,7 +136,7 @@ public RegistrationRest createAndReturn(Context context) { log.error("Something went wrong with sending forgot password info email: " + registrationRest.getEmail(), e); } - } else if (accountType.equalsIgnoreCase(TYPE_REGISTER)) { + } else if (accountType.equalsIgnoreCase(TYPE_REGISTER)) { try { String email = registrationRest.getEmail(); if (!AuthorizeUtil.authorizeNewAccountRegistration(context, request)) { @@ -157,7 +157,6 @@ public RegistrationRest createAndReturn(Context context) { return null; } - @Override public Class getDomainClass() { return RegistrationRest.class; From 518cdb7ff0b6d519f56f45cfa50fd49b61ce9c42 Mon Sep 17 00:00:00 2001 From: Mohamed Saber Eskander Date: Tue, 2 May 2023 14:42:33 +0300 Subject: [PATCH 114/510] [DURACOM-126] solved Submission with a validation error on a hidden step can't be submitted --- .../dspace/app/util/SubmissionStepConfig.java | 29 +++++ .../dspaceFolder/config/item-submission.xml | 37 ++++++- .../dspaceFolder/config/submission-forms.xml | 29 +++++ .../converter/AInprogressItemConverter.java | 4 + .../SubmissionDefinitionsControllerIT.java | 100 +++++++++++------- .../app/rest/SubmissionFormsControllerIT.java | 66 ++++++++---- .../rest/WorkflowItemRestRepositoryIT.java | 31 ++++++ .../rest/WorkspaceItemRestRepositoryIT.java | 29 +++++ dspace/config/item-submission.xml | 1 - 9 files changed, 266 insertions(+), 60 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/util/SubmissionStepConfig.java b/dspace-api/src/main/java/org/dspace/app/util/SubmissionStepConfig.java index 5506b3c23f1e..1a8f2744b818 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/SubmissionStepConfig.java +++ b/dspace-api/src/main/java/org/dspace/app/util/SubmissionStepConfig.java @@ -11,6 +11,9 @@ import java.util.Map; import org.apache.commons.lang3.BooleanUtils; +import org.dspace.content.InProgressSubmission; +import org.dspace.content.WorkspaceItem; +import org.hibernate.proxy.HibernateProxyHelper; /** * Class representing configuration for a single step within an Item Submission @@ -173,6 +176,32 @@ public String getVisibilityOutside() { return visibilityOutside; } + public boolean isHiddenForInProgressSubmission(InProgressSubmission obj) { + + String scopeToCheck = getScope(obj); + + if (scope == null || scopeToCheck == null) { + return false; + } + + String visibility = getVisibility(); + String visibilityOutside = getVisibilityOutside(); + + if (scope.equalsIgnoreCase(scopeToCheck)) { + return "hidden".equalsIgnoreCase(visibility); + } else { + return visibilityOutside == null || "hidden".equalsIgnoreCase(visibilityOutside); + } + + } + + private String getScope(InProgressSubmission obj) { + if (HibernateProxyHelper.getClassWithoutInitializingProxy(obj).equals(WorkspaceItem.class)) { + return "submission"; + } + return "workflow"; + } + /** * Get the number of this step in the current Submission process config. * Step numbers start with #0 (although step #0 is ALWAYS the special 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 6d8ae0c2f0d9..452460501a54 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/item-submission.xml +++ b/dspace-api/src/test/data/dspaceFolder/config/item-submission.xml @@ -23,6 +23,7 @@ + @@ -54,7 +55,6 @@ org.dspace.app.rest.submit.step.CollectionStep collection - submission submit.progressbar.describe.stepone @@ -149,6 +149,34 @@ org.dspace.app.rest.submit.step.ShowIdentifiersStep identifiers + + + submit.progressbar.describe.stepone + org.dspace.app.rest.submit.step.DescribeStep + submission-form + workflow + + + + submit.progressbar.describe.stepone + org.dspace.app.rest.submit.step.DescribeStep + submission-form + submission + + + + + org.dspace.app.rest.submit.step.CollectionStep + collection + + + + + org.dspace.app.rest.submit.step.CollectionStep + collection + submission + + @@ -222,6 +250,13 @@ + + + + + + + diff --git a/dspace-api/src/test/data/dspaceFolder/config/submission-forms.xml b/dspace-api/src/test/data/dspaceFolder/config/submission-forms.xml index 7438fda85212..6b7349616e2d 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/submission-forms.xml +++ b/dspace-api/src/test/data/dspaceFolder/config/submission-forms.xml @@ -436,6 +436,35 @@ it, please enter the types and the actual numbers or codes. + +

+ + + dc + title + + false + + onebox + Field required + + + + +
+ + + dc + type + + false + + onebox + Field required + + +
+ diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/AInprogressItemConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/AInprogressItemConverter.java index ce7ca349180d..fa1d145011f7 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/AInprogressItemConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/AInprogressItemConverter.java @@ -86,6 +86,10 @@ protected void fillFromModel(T obj, R witem, Projection projection) { for (SubmissionSectionRest sections : def.getPanels()) { SubmissionStepConfig stepConfig = submissionSectionConverter.toModel(sections); + if (stepConfig.isHiddenForInProgressSubmission(obj)) { + continue; + } + /* * First, load the step processing class (using the current * class loader) 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 e7d43ec4d620..babb1fac2326 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 @@ -257,10 +257,10 @@ public void findAllPaginationTest() throws Exception { Matchers.containsString("page=1"), Matchers.containsString("size=1")))) .andExpect(jsonPath("$._links.last.href", Matchers.allOf( Matchers.containsString("/api/config/submissiondefinitions?"), - Matchers.containsString("page=5"), Matchers.containsString("size=1")))) + Matchers.containsString("page=6"), Matchers.containsString("size=1")))) .andExpect(jsonPath("$.page.size", is(1))) - .andExpect(jsonPath("$.page.totalElements", is(6))) - .andExpect(jsonPath("$.page.totalPages", is(6))) + .andExpect(jsonPath("$.page.totalElements", is(7))) + .andExpect(jsonPath("$.page.totalPages", is(7))) .andExpect(jsonPath("$.page.number", is(0))); getClient(tokenAdmin).perform(get("/api/config/submissiondefinitions") @@ -268,7 +268,7 @@ public void findAllPaginationTest() throws Exception { .param("page", "1")) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) - .andExpect(jsonPath("$._embedded.submissiondefinitions[0].id", is("accessConditionNotDiscoverable"))) + .andExpect(jsonPath("$._embedded.submissiondefinitions[0].id", is("test-hidden"))) .andExpect(jsonPath("$._links.first.href", Matchers.allOf( Matchers.containsString("/api/config/submissiondefinitions?"), Matchers.containsString("page=0"), Matchers.containsString("size=1")))) @@ -285,8 +285,8 @@ public void findAllPaginationTest() throws Exception { Matchers.containsString("/api/config/submissiondefinitions?"), Matchers.containsString("page="), Matchers.containsString("size=1")))) .andExpect(jsonPath("$.page.size", is(1))) - .andExpect(jsonPath("$.page.totalElements", is(6))) - .andExpect(jsonPath("$.page.totalPages", is(6))) + .andExpect(jsonPath("$.page.totalElements", is(7))) + .andExpect(jsonPath("$.page.totalPages", is(7))) .andExpect(jsonPath("$.page.number", is(1))); getClient(tokenAdmin).perform(get("/api/config/submissiondefinitions") @@ -294,30 +294,56 @@ public void findAllPaginationTest() throws Exception { .param("page", "2")) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) - .andExpect(jsonPath("$._embedded.submissiondefinitions[0].id", is("languagetestprocess"))) + .andExpect(jsonPath("$._embedded.submissiondefinitions[0].id", is("accessConditionNotDiscoverable"))) .andExpect(jsonPath("$._links.first.href", Matchers.allOf( - Matchers.containsString("/api/config/submissiondefinitions?"), - Matchers.containsString("page=0"), Matchers.containsString("size=1")))) + Matchers.containsString("/api/config/submissiondefinitions?"), + Matchers.containsString("page=0"), Matchers.containsString("size=1")))) .andExpect(jsonPath("$._links.prev.href", Matchers.allOf( - Matchers.containsString("/api/config/submissiondefinitions?"), - Matchers.containsString("page=1"), Matchers.containsString("size=1")))) + Matchers.containsString("/api/config/submissiondefinitions?"), + Matchers.containsString("page=1"), Matchers.containsString("size=1")))) .andExpect(jsonPath("$._links.next.href", Matchers.allOf( - Matchers.containsString("/api/config/submissiondefinitions?"), - Matchers.containsString("page=3"), Matchers.containsString("size=1")))) + Matchers.containsString("/api/config/submissiondefinitions?"), + Matchers.containsString("page=3"), Matchers.containsString("size=1")))) .andExpect(jsonPath("$._links.self.href", Matchers.allOf( - Matchers.containsString("/api/config/submissiondefinitions?"), - Matchers.containsString("page=2"), Matchers.containsString("size=1")))) + Matchers.containsString("/api/config/submissiondefinitions?"), + Matchers.containsString("page=2"), Matchers.containsString("size=1")))) .andExpect(jsonPath("$._links.last.href", Matchers.allOf( - Matchers.containsString("/api/config/submissiondefinitions?"), - Matchers.containsString("page=5"), Matchers.containsString("size=1")))) + Matchers.containsString("/api/config/submissiondefinitions?"), + Matchers.containsString("page=6"), Matchers.containsString("size=1")))) .andExpect(jsonPath("$.page.size", is(1))) - .andExpect(jsonPath("$.page.totalElements", is(6))) - .andExpect(jsonPath("$.page.totalPages", is(6))) + .andExpect(jsonPath("$.page.totalElements", is(7))) + .andExpect(jsonPath("$.page.totalPages", is(7))) .andExpect(jsonPath("$.page.number", is(2))); + getClient(tokenAdmin).perform(get("/api/config/submissiondefinitions") + .param("size", "1") + .param("page", "3")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.submissiondefinitions[0].id", is("languagetestprocess"))) + .andExpect(jsonPath("$._links.first.href", Matchers.allOf( + Matchers.containsString("/api/config/submissiondefinitions?"), + Matchers.containsString("page=0"), Matchers.containsString("size=1")))) + .andExpect(jsonPath("$._links.prev.href", Matchers.allOf( + Matchers.containsString("/api/config/submissiondefinitions?"), + Matchers.containsString("page=2"), Matchers.containsString("size=1")))) + .andExpect(jsonPath("$._links.next.href", Matchers.allOf( + Matchers.containsString("/api/config/submissiondefinitions?"), + Matchers.containsString("page=4"), Matchers.containsString("size=1")))) + .andExpect(jsonPath("$._links.self.href", Matchers.allOf( + Matchers.containsString("/api/config/submissiondefinitions?"), + Matchers.containsString("page=3"), Matchers.containsString("size=1")))) + .andExpect(jsonPath("$._links.last.href", Matchers.allOf( + Matchers.containsString("/api/config/submissiondefinitions?"), + Matchers.containsString("page=6"), Matchers.containsString("size=1")))) + .andExpect(jsonPath("$.page.size", is(1))) + .andExpect(jsonPath("$.page.totalElements", is(7))) + .andExpect(jsonPath("$.page.totalPages", is(7))) + .andExpect(jsonPath("$.page.number", is(3))); + getClient(tokenAdmin).perform(get("/api/config/submissiondefinitions") .param("size", "1") - .param("page", "3")) + .param("page", "4")) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$._embedded.submissiondefinitions[0].id", is("qualdroptest"))) @@ -326,24 +352,24 @@ public void findAllPaginationTest() throws Exception { Matchers.containsString("page=0"), Matchers.containsString("size=1")))) .andExpect(jsonPath("$._links.prev.href", Matchers.allOf( Matchers.containsString("/api/config/submissiondefinitions?"), - Matchers.containsString("page=2"), Matchers.containsString("size=1")))) + Matchers.containsString("page=3"), Matchers.containsString("size=1")))) .andExpect(jsonPath("$._links.next.href", Matchers.allOf( Matchers.containsString("/api/config/submissiondefinitions?"), - Matchers.containsString("page=4"), Matchers.containsString("size=1")))) + Matchers.containsString("page=5"), Matchers.containsString("size=1")))) .andExpect(jsonPath("$._links.self.href", Matchers.allOf( Matchers.containsString("/api/config/submissiondefinitions?"), - Matchers.containsString("page=3"), Matchers.containsString("size=1")))) + Matchers.containsString("page=4"), Matchers.containsString("size=1")))) .andExpect(jsonPath("$._links.last.href", Matchers.allOf( Matchers.containsString("/api/config/submissiondefinitions?"), - Matchers.containsString("page=5"), Matchers.containsString("size=1")))) + Matchers.containsString("page=6"), Matchers.containsString("size=1")))) .andExpect(jsonPath("$.page.size", is(1))) - .andExpect(jsonPath("$.page.totalElements", is(6))) - .andExpect(jsonPath("$.page.totalPages", is(6))) - .andExpect(jsonPath("$.page.number", is(3))); + .andExpect(jsonPath("$.page.totalElements", is(7))) + .andExpect(jsonPath("$.page.totalPages", is(7))) + .andExpect(jsonPath("$.page.number", is(4))); getClient(tokenAdmin).perform(get("/api/config/submissiondefinitions") .param("size", "1") - .param("page", "4")) + .param("page", "5")) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$._embedded.submissiondefinitions[0].id", is("extractiontestprocess"))) @@ -352,20 +378,20 @@ public void findAllPaginationTest() throws Exception { Matchers.containsString("page=0"), Matchers.containsString("size=1")))) .andExpect(jsonPath("$._links.prev.href", Matchers.allOf( Matchers.containsString("/api/config/submissiondefinitions?"), - Matchers.containsString("page=3"), Matchers.containsString("size=1")))) - .andExpect(jsonPath("$._links.next.href", Matchers.allOf( - Matchers.containsString("/api/config/submissiondefinitions?"), - Matchers.containsString("page=5"), Matchers.containsString("size=1")))) + Matchers.containsString("page=4"), Matchers.containsString("size=1")))) + .andExpect(jsonPath("$._links.next.href", Matchers.allOf( + Matchers.containsString("/api/config/submissiondefinitions?"), + Matchers.containsString("page=6"), Matchers.containsString("size=1")))) .andExpect(jsonPath("$._links.self.href", Matchers.allOf( Matchers.containsString("/api/config/submissiondefinitions?"), - Matchers.containsString("page=4"), Matchers.containsString("size=1")))) + Matchers.containsString("page=5"), Matchers.containsString("size=1")))) .andExpect(jsonPath("$._links.last.href", Matchers.allOf( Matchers.containsString("/api/config/submissiondefinitions?"), - Matchers.containsString("page=5"), Matchers.containsString("size=1")))) + Matchers.containsString("page=6"), Matchers.containsString("size=1")))) .andExpect(jsonPath("$.page.size", is(1))) - .andExpect(jsonPath("$.page.totalElements", is(6))) - .andExpect(jsonPath("$.page.totalPages", is(6))) - .andExpect(jsonPath("$.page.number", is(4))); + .andExpect(jsonPath("$.page.totalElements", is(7))) + .andExpect(jsonPath("$.page.totalPages", is(7))) + .andExpect(jsonPath("$.page.number", is(5))); } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionFormsControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionFormsControllerIT.java index 241bdefe2108..cf1e0c7c76a9 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionFormsControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionFormsControllerIT.java @@ -67,13 +67,13 @@ public void findAll() throws Exception { .andExpect(content().contentType(contentType)) //The configuration file for the test env includes 6 forms .andExpect(jsonPath("$.page.size", is(20))) - .andExpect(jsonPath("$.page.totalElements", equalTo(8))) + .andExpect(jsonPath("$.page.totalElements", equalTo(10))) .andExpect(jsonPath("$.page.totalPages", equalTo(1))) .andExpect(jsonPath("$.page.number", is(0))) .andExpect( jsonPath("$._links.self.href", Matchers.startsWith(REST_SERVER_URL + "config/submissionforms"))) //The array of submissionforms should have a size of 8 - .andExpect(jsonPath("$._embedded.submissionforms", hasSize(equalTo(8)))) + .andExpect(jsonPath("$._embedded.submissionforms", hasSize(equalTo(10)))) ; } @@ -84,12 +84,12 @@ public void findAllWithNewlyCreatedAccountTest() throws Exception { .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$.page.size", is(20))) - .andExpect(jsonPath("$.page.totalElements", equalTo(8))) + .andExpect(jsonPath("$.page.totalElements", equalTo(10))) .andExpect(jsonPath("$.page.totalPages", equalTo(1))) .andExpect(jsonPath("$.page.number", is(0))) .andExpect(jsonPath("$._links.self.href", Matchers.startsWith(REST_SERVER_URL + "config/submissionforms"))) - .andExpect(jsonPath("$._embedded.submissionforms", hasSize(equalTo(8)))); + .andExpect(jsonPath("$._embedded.submissionforms", hasSize(equalTo(10)))); } @Test @@ -696,10 +696,10 @@ public void findAllPaginationTest() throws Exception { Matchers.containsString("page=1"), Matchers.containsString("size=2")))) .andExpect(jsonPath("$._links.last.href", Matchers.allOf( Matchers.containsString("/api/config/submissionforms?"), - Matchers.containsString("page=3"), Matchers.containsString("size=2")))) + Matchers.containsString("page=4"), Matchers.containsString("size=2")))) .andExpect(jsonPath("$.page.size", is(2))) - .andExpect(jsonPath("$.page.totalElements", equalTo(8))) - .andExpect(jsonPath("$.page.totalPages", equalTo(4))) + .andExpect(jsonPath("$.page.totalElements", equalTo(10))) + .andExpect(jsonPath("$.page.totalPages", equalTo(5))) .andExpect(jsonPath("$.page.number", is(0))); getClient(tokenAdmin).perform(get("/api/config/submissionforms") @@ -707,8 +707,8 @@ public void findAllPaginationTest() throws Exception { .param("page", "1")) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) - .andExpect(jsonPath("$._embedded.submissionforms[0].id", is("languagetest"))) - .andExpect(jsonPath("$._embedded.submissionforms[1].id", is("qualdroptest"))) + .andExpect(jsonPath("$._embedded.submissionforms[0].id", is("test-outside-workflow-hidden"))) + .andExpect(jsonPath("$._embedded.submissionforms[1].id", is("languagetest"))) .andExpect(jsonPath("$._links.first.href", Matchers.allOf( Matchers.containsString("/api/config/submissionforms?"), Matchers.containsString("page=0"), Matchers.containsString("size=2")))) @@ -723,10 +723,10 @@ public void findAllPaginationTest() throws Exception { Matchers.containsString("page=2"), Matchers.containsString("size=2")))) .andExpect(jsonPath("$._links.last.href", Matchers.allOf( Matchers.containsString("/api/config/submissionforms?"), - Matchers.containsString("page=3"), Matchers.containsString("size=2")))) + Matchers.containsString("page=4"), Matchers.containsString("size=2")))) .andExpect(jsonPath("$.page.size", is(2))) - .andExpect(jsonPath("$.page.totalElements", equalTo(8))) - .andExpect(jsonPath("$.page.totalPages", equalTo(4))) + .andExpect(jsonPath("$.page.totalElements", equalTo(10))) + .andExpect(jsonPath("$.page.totalPages", equalTo(5))) .andExpect(jsonPath("$.page.number", is(1))); getClient(tokenAdmin).perform(get("/api/config/submissionforms") @@ -734,8 +734,8 @@ public void findAllPaginationTest() throws Exception { .param("page", "2")) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) - .andExpect(jsonPath("$._embedded.submissionforms[0].id", is("traditionalpagetwo"))) - .andExpect(jsonPath("$._embedded.submissionforms[1].id", is("sampleauthority"))) + .andExpect(jsonPath("$._embedded.submissionforms[0].id", is("test-outside-submission-hidden"))) + .andExpect(jsonPath("$._embedded.submissionforms[1].id", is("qualdroptest"))) .andExpect(jsonPath("$._links.first.href", Matchers.allOf( Matchers.containsString("/api/config/submissionforms?"), Matchers.containsString("page=0"), Matchers.containsString("size=2")))) @@ -747,10 +747,10 @@ public void findAllPaginationTest() throws Exception { Matchers.containsString("page=2"), Matchers.containsString("size=2")))) .andExpect(jsonPath("$._links.last.href", Matchers.allOf( Matchers.containsString("/api/config/submissionforms?"), - Matchers.containsString("page=3"), Matchers.containsString("size=2")))) + Matchers.containsString("page=4"), Matchers.containsString("size=2")))) .andExpect(jsonPath("$.page.size", is(2))) - .andExpect(jsonPath("$.page.totalElements", equalTo(8))) - .andExpect(jsonPath("$.page.totalPages", equalTo(4))) + .andExpect(jsonPath("$.page.totalElements", equalTo(10))) + .andExpect(jsonPath("$.page.totalPages", equalTo(5))) .andExpect(jsonPath("$.page.number", is(2))); getClient(tokenAdmin).perform(get("/api/config/submissionforms") @@ -758,7 +758,8 @@ public void findAllPaginationTest() throws Exception { .param("page", "3")) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) - .andExpect(jsonPath("$._embedded.submissionforms[0].id", is("traditionalpageone"))) + .andExpect(jsonPath("$._embedded.submissionforms[0].id", is("traditionalpagetwo"))) + .andExpect(jsonPath("$._embedded.submissionforms[1].id", is("sampleauthority"))) .andExpect(jsonPath("$._links.first.href", Matchers.allOf( Matchers.containsString("/api/config/submissionforms?"), Matchers.containsString("page=0"), Matchers.containsString("size=2")))) @@ -770,10 +771,33 @@ public void findAllPaginationTest() throws Exception { Matchers.containsString("page=3"), Matchers.containsString("size=2")))) .andExpect(jsonPath("$._links.last.href", Matchers.allOf( Matchers.containsString("/api/config/submissionforms?"), - Matchers.containsString("page=3"), Matchers.containsString("size=2")))) + Matchers.containsString("page=4"), Matchers.containsString("size=2")))) .andExpect(jsonPath("$.page.size", is(2))) - .andExpect(jsonPath("$.page.totalElements", equalTo(8))) - .andExpect(jsonPath("$.page.totalPages", equalTo(4))) + .andExpect(jsonPath("$.page.totalElements", equalTo(10))) + .andExpect(jsonPath("$.page.totalPages", equalTo(5))) .andExpect(jsonPath("$.page.number", is(3))); + + getClient(tokenAdmin).perform(get("/api/config/submissionforms") + .param("size", "2") + .param("page", "4")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.submissionforms[0].id", is("traditionalpageone"))) + .andExpect(jsonPath("$._links.first.href", Matchers.allOf( + Matchers.containsString("/api/config/submissionforms?"), + Matchers.containsString("page=0"), Matchers.containsString("size=2")))) + .andExpect(jsonPath("$._links.prev.href", Matchers.allOf( + Matchers.containsString("/api/config/submissionforms?"), + Matchers.containsString("page=3"), Matchers.containsString("size=2")))) + .andExpect(jsonPath("$._links.self.href", Matchers.allOf( + Matchers.containsString("/api/config/submissionforms?"), + Matchers.containsString("page=4"), Matchers.containsString("size=2")))) + .andExpect(jsonPath("$._links.last.href", Matchers.allOf( + Matchers.containsString("/api/config/submissionforms?"), + Matchers.containsString("page=4"), Matchers.containsString("size=2")))) + .andExpect(jsonPath("$.page.size", is(2))) + .andExpect(jsonPath("$.page.totalElements", equalTo(10))) + .andExpect(jsonPath("$.page.totalPages", equalTo(5))) + .andExpect(jsonPath("$.page.number", is(4))); } } 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 c43821d4a013..72612fc5eb53 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 @@ -2122,4 +2122,35 @@ public void whenWorkspaceitemBecomeWorkflowitemWithAccessConditionsTheItemMustBe WorkflowItemBuilder.deleteWorkflowItem(idRef.get()); } } + + @Test + public void testWorkflowWithHiddenSections() throws Exception { + + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection collection = CollectionBuilder.createCollection(context, parentCommunity, "123456789/test-hidden") + .withName("Collection 1") + .withWorkflowGroup(1, eperson) + .build(); + + XmlWorkflowItem workflowItem = WorkflowItemBuilder.createWorkflowItem(context, collection) + .withTitle("Workflow Item") + .build(); + + context.restoreAuthSystemState(); + + getClient(getAuthToken(admin.getEmail(), password)) + .perform(get("/api/workflow/workflowitems/" + workflowItem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.test-outside-workflow-hidden").exists()) + .andExpect(jsonPath("$.sections.test-outside-submission-hidden").doesNotExist()) + .andExpect(jsonPath("$.sections.test-never-hidden").exists()) + .andExpect(jsonPath("$.sections.test-always-hidden").doesNotExist()); + + } + } 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 6c975264257a..ad9b8046f25a 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 @@ -8566,4 +8566,33 @@ public void patchBySupervisorTest() throws Exception { ))); } + @Test + public void testSubmissionWithHiddenSections() throws Exception { + + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection collection = CollectionBuilder.createCollection(context, parentCommunity, "123456789/test-hidden") + .withName("Collection 1") + .build(); + + WorkspaceItem workspaceItem = WorkspaceItemBuilder.createWorkspaceItem(context, collection) + .withTitle("Workspace Item") + .withIssueDate("2023-01-01") + .build(); + + context.restoreAuthSystemState(); + + getClient(getAuthToken(admin.getEmail(), password)) + .perform(get("/api/submission/workspaceitems/" + workspaceItem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.test-outside-workflow-hidden").doesNotExist()) + .andExpect(jsonPath("$.sections.test-outside-submission-hidden").exists()) + .andExpect(jsonPath("$.sections.test-never-hidden").exists()) + .andExpect(jsonPath("$.sections.test-always-hidden").doesNotExist()); + + } } diff --git a/dspace/config/item-submission.xml b/dspace/config/item-submission.xml index 2ab26dcf57df..9fbcb776d15b 100644 --- a/dspace/config/item-submission.xml +++ b/dspace/config/item-submission.xml @@ -79,7 +79,6 @@ org.dspace.app.rest.submit.step.CollectionStep collection - submission diff --git a/dspace-swordv2/pom.xml b/dspace-swordv2/pom.xml index 35206d6ee291..9badeb2fe80a 100644 --- a/dspace-swordv2/pom.xml +++ b/dspace-swordv2/pom.xml @@ -22,38 +22,6 @@ ${basedir}/.. - - - oracle-support - - - db.name - oracle - - - - - com.oracle - ojdbc6 - - - - - postgres-support - - - !db.name - - - - - org.postgresql - postgresql - - - - - javax.servlet diff --git a/dspace/modules/additions/pom.xml b/dspace/modules/additions/pom.xml index 8f5a6f84f492..dd98bf0cbdd1 100644 --- a/dspace/modules/additions/pom.xml +++ b/dspace/modules/additions/pom.xml @@ -61,22 +61,6 @@ - - oracle-support - - - db.name - oracle - - - - - com.oracle - ojdbc6 - - - - unit-test-environment diff --git a/dspace/modules/rest/pom.xml b/dspace/modules/rest/pom.xml index a7c9b5922c62..115393b7db20 100644 --- a/dspace/modules/rest/pom.xml +++ b/dspace/modules/rest/pom.xml @@ -90,24 +90,6 @@ - - - oracle-support - - - db.name - oracle - - - - - com.oracle - ojdbc6 - - - - - diff --git a/dspace/modules/server/pom.xml b/dspace/modules/server/pom.xml index 9b696fa0cbda..41ddb94be5a9 100644 --- a/dspace/modules/server/pom.xml +++ b/dspace/modules/server/pom.xml @@ -244,22 +244,6 @@ just adding new jar in the classloader - - - oracle-support - - - db.name - oracle - - - - - com.oracle - ojdbc6 - - - diff --git a/pom.xml b/pom.xml index 3b6f7edb6549..fba97314a5ae 100644 --- a/pom.xml +++ b/pom.xml @@ -1617,11 +1617,6 @@ icu4j 62.1 - - com.oracle - ojdbc6 - 11.2.0.4.0 - org.dspace From 6fbf97a6b39502f440ac1b6be2094ee40871d7d7 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 2 May 2023 11:57:47 -0500 Subject: [PATCH 119/510] Remove oracle mentions from configs / READMEs --- .../storage/rdbms/sqlmigration/h2/README.md | 28 +++++++------------ .../rdbms/sqlmigration/postgres/README.md | 7 +++-- dspace/config/dspace.cfg | 18 +++--------- dspace/config/local.cfg.EXAMPLE | 18 +++--------- 4 files changed, 22 insertions(+), 49 deletions(-) diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/README.md b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/README.md index 8088c6ccca62..87e114ca53a5 100644 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/README.md +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/README.md @@ -4,33 +4,25 @@ in Production. Instead, DSpace uses the H2 Database to perform Unit Testing during development. -By default, the DSpace Unit Testing environment configures H2 to run in -"Oracle Mode" and initializes the H2 database using the scripts in this directory. -These database migrations are automatically called by [Flyway](http://flywaydb.org/) -when the `DatabaseManager` initializes itself (see `initializeDatabase()` method). - -The H2 migrations in this directory are *based on* the Oracle Migrations, but -with some modifications in order to be valid in H2. - -## Oracle vs H2 script differences +By default, the DSpace Unit Testing environment configures H2 to run in memory +and initializes the H2 database using the scripts in this directory. See +`[src]/dspace-api/src/test/data/dspaceFolder/config/local.cfg`. -One of the primary differences between the Oracle scripts and these H2 ones -is in the syntax of the `ALTER TABLE` command. Unfortunately, H2's syntax for -that command differs greatly from Oracle (and PostgreSQL as well). +These database migrations are automatically called by [Flyway](http://flywaydb.org/) +in `DatabaseUtils`. -Most of the remainder of the scripts contain the exact Oracle syntax (which is -usually valid in H2). But, to you can always `diff` scripts of the same name -for further syntax differences. +The H2 migrations in this directory all use H2's grammar/syntax. +For additional info see the [H2 SQL Grammar](https://www.h2database.com/html/grammar.html). -For additional info see the [H2 SQL Grammar](http://www.h2database.com/html/grammar.html). ## More Information on Flyway The SQL scripts in this directory are H2-specific database migrations. They are used to automatically upgrade your DSpace database using [Flyway](http://flywaydb.org/). As such, these scripts are automatically called by Flyway when the DSpace -`DatabaseManager` initializes itself (see `initializeDatabase()` method). During -that process, Flyway determines which version of DSpace your database is using +`DatabaseUtils` initializes. + +During that process, Flyway determines which version of DSpace your database is using and then executes the appropriate upgrade script(s) to bring it up to the latest version. diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/README.md b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/README.md index 72eb279912b5..e16e4c6d4c91 100644 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/README.md +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/README.md @@ -3,8 +3,9 @@ The SQL scripts in this directory are PostgreSQL-specific database migrations. They are used to automatically upgrade your DSpace database using [Flyway](http://flywaydb.org/). As such, these scripts are automatically called by Flyway when the DSpace -`DatabaseManager` initializes itself (see `initializeDatabase()` method). During -that process, Flyway determines which version of DSpace your database is using +`DatabaseUtils` initializes. + +During that process, Flyway determines which version of DSpace your database is using and then executes the appropriate upgrade script(s) to bring it up to the latest version. @@ -22,7 +23,7 @@ Please see the Flyway Documentation for more information: http://flywaydb.org/ The `update-sequences.sql` script in this directory may still be used to update your internal database counts if you feel they have gotten out of "sync". This may sometimes occur after large restores of content (e.g. when using the DSpace -[AIP Backup and Restore](https://wiki.duraspace.org/display/DSDOC5x/AIP+Backup+and+Restore) +[AIP Backup and Restore](https://wiki.lyrasis.org/display/DSDOC7x/AIP+Backup+and+Restore) feature). This `update-sequences.sql` script can be executed by running diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 65b1f951fad3..aee08c8e6009 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -74,23 +74,15 @@ solr.multicorePrefix = # solr.client.timeToLive = 600 ##### Database settings ##### -# DSpace only supports two database types: PostgreSQL or Oracle -# PostgreSQL is highly recommended. -# Oracle support is DEPRECATED. See https://github.com/DSpace/DSpace/issues/8214 +# DSpace ONLY supports PostgreSQL at this time. # URL for connecting to database -# * Postgres template: jdbc:postgresql://localhost:5432/dspace -# * Oracle template (DEPRECATED): jdbc:oracle:thin:@//localhost:1521/xe db.url = jdbc:postgresql://localhost:5432/dspace -# JDBC Driver -# * For Postgres: org.postgresql.Driver -# * For Oracle (DEPRECATED): oracle.jdbc.OracleDriver +# JDBC Driver for PostgreSQL db.driver = org.postgresql.Driver -# Database Dialect (for Hibernate) -# * For Postgres: org.hibernate.dialect.PostgreSQL94Dialect -# * For Oracle (DEPRECATED): org.hibernate.dialect.Oracle10gDialect +# PostgreSQL Database Dialect (for Hibernate) db.dialect = org.hibernate.dialect.PostgreSQL94Dialect # Database username and password @@ -98,9 +90,7 @@ db.username = dspace db.password = dspace # Database Schema name -# * For Postgres, this is often "public" (default schema) -# * For Oracle (DEPRECATED), schema is equivalent to the username of your database account, -# so this may be set to ${db.username} in most scenarios. +# For PostgreSQL, this is often "public" (default schema) db.schema = public ## Database Connection pool parameters diff --git a/dspace/config/local.cfg.EXAMPLE b/dspace/config/local.cfg.EXAMPLE index cf13a47d762c..7176ed275a51 100644 --- a/dspace/config/local.cfg.EXAMPLE +++ b/dspace/config/local.cfg.EXAMPLE @@ -71,23 +71,15 @@ dspace.name = DSpace at My University ########################## # DATABASE CONFIGURATION # ########################## -# DSpace only supports two database types: PostgreSQL or Oracle -# PostgreSQL is highly recommended. -# Oracle support is DEPRECATED. See https://github.com/DSpace/DSpace/issues/8214 +# DSpace ONLY supports PostgreSQL at this time. # URL for connecting to database -# * Postgres template: jdbc:postgresql://localhost:5432/dspace -# * Oracle template (DEPRECATED): jdbc:oracle:thin:@//localhost:1521/xe db.url = jdbc:postgresql://localhost:5432/dspace -# JDBC Driver -# * For Postgres: org.postgresql.Driver -# * For Oracle (DEPRECATED): oracle.jdbc.OracleDriver +# JDBC Driver for PostgreSQL db.driver = org.postgresql.Driver -# Database Dialect (for Hibernate) -# * For Postgres: org.hibernate.dialect.PostgreSQL94Dialect -# * For Oracle (DEPRECATED): org.hibernate.dialect.Oracle10gDialect +# PostgreSQL Database Dialect (for Hibernate) db.dialect = org.hibernate.dialect.PostgreSQL94Dialect # Database username and password @@ -95,9 +87,7 @@ db.username = dspace db.password = dspace # Database Schema name -# * For Postgres, this is often "public" (default schema) -# * For Oracle (DEPRECATED), schema is equivalent to the username of your database account, -# so this may be set to ${db.username} in most scenarios. +# For PostgreSQL, this is often "public" (default schema) db.schema = public ## Connection pool parameters From 140cdc6de07a2263857372337257c56b8c4296fe Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 2 May 2023 12:09:30 -0500 Subject: [PATCH 120/510] Remove all oracle-specific migration scripts --- .../oracle/upgradeToFlyway4x.sql | 29 - ...1__CollectionCommunity_Metadata_Handle.sql | 90 --- .../rdbms/sqlmigration/oracle/README.md | 84 --- ...tial_DSpace_1.2_Oracle_database_schema.sql | 550 ------------------ .../V1.3__Upgrade_to_DSpace_1.3_schema.sql | 57 -- ...V1.4.2__Upgrade_to_DSpace_1.4.2_schema.sql | 133 ----- .../V1.4__Upgrade_to_DSpace_1.4_schema.sql | 371 ------------ .../V1.5__Upgrade_to_DSpace_1.5_schema.sql | 142 ----- .../V1.6__Upgrade_to_DSpace_1.6_schema.sql | 93 --- .../V1.7__Upgrade_to_DSpace_1.7_schema.sql | 20 - .../V1.8__Upgrade_to_DSpace_1.8_schema.sql | 23 - .../V3.0__Upgrade_to_DSpace_3.x_schema.sql | 52 -- .../V4.0__Upgrade_to_DSpace_4.x_schema.sql | 88 --- ....9_2015.10.26__DS-2818_registry_update.sql | 64 -- ...08.08__DS-1945_Helpdesk_Request_a_Copy.sql | 20 - ...9.26__DS-1582_Metadata_For_All_Objects.sql | 333 ----------- .../oracle/V5.6_2016.08.23__DS-3097.sql | 24 - ..._metadatavalue_resource_type_id_column.sql | 23 - ...015.03.07__DS-2701_Hibernate_migration.sql | 469 --------------- ..._03_06_01__DS_3378_lost_oracle_indexes.sql | 18 - .../oracle/V6.0_2016.01.03__DS-3024.sql | 25 - ...02.25__DS-3004-slow-searching-as-admin.sql | 30 - ...04.01__DS-1955_Increase_embargo_reason.sql | 25 - ...016.04.04__DS-3086-OAI-Performance-fix.sql | 46 -- ...125-fix-bundle-bitstream-delete-rights.sql | 33 -- ...DS-3168-fix-requestitem_item_id_column.sql | 24 - .../oracle/V6.0_2016.07.21__DS-2775.sql | 30 - ...6.07.26__DS-3277_fix_handle_assignment.sql | 44 -- .../oracle/V6.0_2016.08.23__DS-3097.sql | 24 - .../V6.0_2016.11.29__DS-3410-lost-indexes.sql | 17 - .../oracle/V6.0_2016.11.30__DS-3409.sql | 16 - ...2017.10.12__DS-3542-stateless-sessions.sql | 20 - .../V7.0_2018.04.16__dspace-entities.sql | 65 --- .../V7.0_2018.06.07__DS-3851-permission.sql | 24 - ....05.02__DS-4239-workflow-xml-migration.sql | 17 - ..._2019.07.31__Retrieval_of_name_variant.sql | 18 - ....13__relationship_type_copy_left_right.sql | 14 - .../V7.0_2019_06_14__scripts-and-process.sql | 40 -- ..._2020.01.08__DS-626-statistics-tracker.sql | 29 - ...V7.0_2021.01.22__Remove_basic_workflow.sql | 17 - .../oracle/V7.0_2021.02.08__tilted_rels.sql | 13 - ....18__Move_entity_type_to_dspace_schema.sql | 56 -- ..._type_from_item_template_to_collection.sql | 28 - ...ction_table_drop_workflow_stem_columns.sql | 15 - ...DV_place_after_migrating_from_DSpace_5.sql | 24 - ....3_2022.04.29__orcid_queue_and_history.sql | 54 -- .../V7.3_2022.05.16__Orcid_token_table.sql | 24 - .../V7.3_2022.06.16__process_to_group.sql | 18 - ...on_status_column_to_relationship_table.sql | 10 - ...mter_change_columns_subscription_table.sql | 45 -- ...5_2022.12.09__Supervision_Orders_table.sql | 78 --- .../V7.5_2022.12.15__system_wide_alerts.sql | 22 - ...pdate_PNG_in_bitstream_format_registry.sql | 17 - ..._and_history_descriptions_to_text_type.sql | 10 - .../sqlmigration/oracle/update-sequences.sql | 77 --- .../V5.7_2017.05.05__DS-3431.sql | 503 ---------------- ...8.11__DS-2701_Basic_Workflow_Migration.sql | 37 -- .../V6.1_2017.01.03__DS-3431.sql | 503 ---------------- ....08.11__DS-2701_Xml_Workflow_Migration.sql | 141 ----- ....0_2018.04.03__upgrade_workflow_policy.sql | 27 - .../oracle/data_workflow_migration.sql | 377 ------------ .../v6.0__DS-2701_data_workflow_migration.sql | 377 ------------ .../v6.0__DS-2701_xml_workflow_migration.sql | 124 ---- .../oracle/xml_workflow_migration.sql | 124 ---- 64 files changed, 5945 deletions(-) delete mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/flywayupgrade/oracle/upgradeToFlyway4x.sql delete mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/metadata/oracle/V7.0_2020.10.31__CollectionCommunity_Metadata_Handle.sql delete mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/README.md delete mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V1.2__Initial_DSpace_1.2_Oracle_database_schema.sql delete mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V1.3__Upgrade_to_DSpace_1.3_schema.sql delete mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V1.4.2__Upgrade_to_DSpace_1.4.2_schema.sql delete mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V1.4__Upgrade_to_DSpace_1.4_schema.sql delete mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V1.5__Upgrade_to_DSpace_1.5_schema.sql delete mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V1.6__Upgrade_to_DSpace_1.6_schema.sql delete mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V1.7__Upgrade_to_DSpace_1.7_schema.sql delete mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V1.8__Upgrade_to_DSpace_1.8_schema.sql delete mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V3.0__Upgrade_to_DSpace_3.x_schema.sql delete mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V4.0__Upgrade_to_DSpace_4.x_schema.sql delete mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V4.9_2015.10.26__DS-2818_registry_update.sql delete mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V5.0_2014.08.08__DS-1945_Helpdesk_Request_a_Copy.sql delete mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V5.0_2014.09.26__DS-1582_Metadata_For_All_Objects.sql delete mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V5.6_2016.08.23__DS-3097.sql delete mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V5.7_2017.04.11__DS-3563_Index_metadatavalue_resource_type_id_column.sql delete mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2015.03.07__DS-2701_Hibernate_migration.sql delete mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2015_03_06_01__DS_3378_lost_oracle_indexes.sql delete mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2016.01.03__DS-3024.sql delete mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2016.02.25__DS-3004-slow-searching-as-admin.sql delete mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2016.04.01__DS-1955_Increase_embargo_reason.sql delete mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2016.04.04__DS-3086-OAI-Performance-fix.sql delete mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2016.04.14__DS-3125-fix-bundle-bitstream-delete-rights.sql delete mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2016.05.10__DS-3168-fix-requestitem_item_id_column.sql delete mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2016.07.21__DS-2775.sql delete mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2016.07.26__DS-3277_fix_handle_assignment.sql delete mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2016.08.23__DS-3097.sql delete mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2016.11.29__DS-3410-lost-indexes.sql delete mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2016.11.30__DS-3409.sql delete mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2017.10.12__DS-3542-stateless-sessions.sql delete mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2018.04.16__dspace-entities.sql delete mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2018.06.07__DS-3851-permission.sql delete mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2019.05.02__DS-4239-workflow-xml-migration.sql delete mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2019.07.31__Retrieval_of_name_variant.sql delete mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2019.11.13__relationship_type_copy_left_right.sql delete mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2019_06_14__scripts-and-process.sql delete mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2020.01.08__DS-626-statistics-tracker.sql delete mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2021.01.22__Remove_basic_workflow.sql delete mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2021.02.08__tilted_rels.sql delete mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2021.03.18__Move_entity_type_to_dspace_schema.sql delete mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2021.09.24__Move_entity_type_from_item_template_to_collection.sql delete mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2021.10.04__alter_collection_table_drop_workflow_stem_columns.sql delete mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.1_2021.10.18__Fix_MDV_place_after_migrating_from_DSpace_5.sql delete mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.3_2022.04.29__orcid_queue_and_history.sql delete mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.3_2022.05.16__Orcid_token_table.sql delete mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.3_2022.06.16__process_to_group.sql delete mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.3_2022.06.20__add_last_version_status_column_to_relationship_table.sql delete mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.5_2022.12.01__add_table_subscriptionparamter_change_columns_subscription_table.sql delete mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.5_2022.12.09__Supervision_Orders_table.sql delete mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.5_2022.12.15__system_wide_alerts.sql delete mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.6_2023.03.24__Update_PNG_in_bitstream_format_registry.sql delete mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.6_2023.03.29__orcid_queue_and_history_descriptions_to_text_type.sql delete mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/update-sequences.sql delete mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/workflow/oracle/basicWorkflow/V5.7_2017.05.05__DS-3431.sql delete mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/workflow/oracle/basicWorkflow/V6.0_2015.08.11__DS-2701_Basic_Workflow_Migration.sql delete mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/workflow/oracle/basicWorkflow/V6.1_2017.01.03__DS-3431.sql delete mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/workflow/oracle/xmlworkflow/V6.0_2015.08.11__DS-2701_Xml_Workflow_Migration.sql delete mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/workflow/oracle/xmlworkflow/V7.0_2018.04.03__upgrade_workflow_policy.sql delete mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/xmlworkflow/oracle/data_workflow_migration.sql delete mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/xmlworkflow/oracle/v6.0__DS-2701_data_workflow_migration.sql delete mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/xmlworkflow/oracle/v6.0__DS-2701_xml_workflow_migration.sql delete mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/xmlworkflow/oracle/xml_workflow_migration.sql diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/flywayupgrade/oracle/upgradeToFlyway4x.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/flywayupgrade/oracle/upgradeToFlyway4x.sql deleted file mode 100644 index 7907fccc00ae..000000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/flywayupgrade/oracle/upgradeToFlyway4x.sql +++ /dev/null @@ -1,29 +0,0 @@ --- --- Copyright 2010-2017 Boxfuse GmbH --- --- Licensed under the Apache License, Version 2.0 (the "License"); --- you may not use this file except in compliance with the License. --- You may obtain a copy of the License at --- --- http://www.apache.org/licenses/LICENSE-2.0 --- --- Unless required by applicable law or agreed to in writing, software --- distributed under the License is distributed on an "AS IS" BASIS, --- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. --- See the License for the specific language governing permissions and --- limitations under the License. --- ------------------ --- This is the Oracle upgrade script from Flyway v4.2.0, copied/borrowed from: --- https://github.com/flyway/flyway/blob/flyway-4.2.0/flyway-core/src/main/resources/org/flywaydb/core/internal/dbsupport/oracle/upgradeMetaDataTable.sql --- --- The variables in this script are replaced in FlywayUpgradeUtils.upgradeFlywayTable() ------------------- - -DROP INDEX "${schema}"."${table}_vr_idx"; -DROP INDEX "${schema}"."${table}_ir_idx"; -ALTER TABLE "${schema}"."${table}" DROP COLUMN "version_rank"; -ALTER TABLE "${schema}"."${table}" DROP PRIMARY KEY DROP INDEX; -ALTER TABLE "${schema}"."${table}" MODIFY "version" NULL; -ALTER TABLE "${schema}"."${table}" ADD CONSTRAINT "${table}_pk" PRIMARY KEY ("installed_rank"); -UPDATE "${schema}"."${table}" SET "type"='BASELINE' WHERE "type"='INIT'; diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/metadata/oracle/V7.0_2020.10.31__CollectionCommunity_Metadata_Handle.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/metadata/oracle/V7.0_2020.10.31__CollectionCommunity_Metadata_Handle.sql deleted file mode 100644 index fff1fe154f57..000000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/metadata/oracle/V7.0_2020.10.31__CollectionCommunity_Metadata_Handle.sql +++ /dev/null @@ -1,90 +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/ --- - --- =============================================================== --- WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING --- --- DO NOT MANUALLY RUN THIS DATABASE MIGRATION. IT WILL BE EXECUTED --- AUTOMATICALLY (IF NEEDED) BY "FLYWAY" WHEN YOU STARTUP DSPACE. --- http://flywaydb.org/ --- =============================================================== - -------------------------------------------------------------- --- This will create COMMUNITY handle metadata -------------------------------------------------------------- - -insert into metadatavalue (metadata_field_id, text_value, text_lang, place, authority, confidence, dspace_object_id) - select distinct - T1.metadata_field_id as metadata_field_id, - concat('${handle.canonical.prefix}', h.handle) as text_value, - null as text_lang, 0 as place, - null as authority, - -1 as confidence, - c.uuid as dspace_object_id - - from community c - left outer join handle h on h.resource_id = c.uuid - left outer join metadatavalue mv on mv.dspace_object_id = c.uuid - left outer join metadatafieldregistry mfr on mv.metadata_field_id = mfr.metadata_field_id - left outer join metadataschemaregistry msr on mfr.metadata_schema_id = msr.metadata_schema_id - - cross join (select mfr.metadata_field_id as metadata_field_id from metadatafieldregistry mfr - left outer join metadataschemaregistry msr on mfr.metadata_schema_id = msr.metadata_schema_id - where msr.short_id = 'dc' - and mfr.element = 'identifier' - and mfr.qualifier = 'uri') T1 - - where uuid not in ( - select c.uuid as uuid from community c - left outer join handle h on h.resource_id = c.uuid - left outer join metadatavalue mv on mv.dspace_object_id = c.uuid - left outer join metadatafieldregistry mfr on mv.metadata_field_id = mfr.metadata_field_id - left outer join metadataschemaregistry msr on mfr.metadata_schema_id = msr.metadata_schema_id - where msr.short_id = 'dc' - and mfr.element = 'identifier' - and mfr.qualifier = 'uri' - ) -; - -------------------------------------------------------------- --- This will create COLLECTION handle metadata -------------------------------------------------------------- - -insert into metadatavalue (metadata_field_id, text_value, text_lang, place, authority, confidence, dspace_object_id) - select distinct - T1.metadata_field_id as metadata_field_id, - concat('${handle.canonical.prefix}', h.handle) as text_value, - null as text_lang, 0 as place, - null as authority, - -1 as confidence, - c.uuid as dspace_object_id - - from collection c - left outer join handle h on h.resource_id = c.uuid - left outer join metadatavalue mv on mv.dspace_object_id = c.uuid - left outer join metadatafieldregistry mfr on mv.metadata_field_id = mfr.metadata_field_id - left outer join metadataschemaregistry msr on mfr.metadata_schema_id = msr.metadata_schema_id - - cross join (select mfr.metadata_field_id as metadata_field_id from metadatafieldregistry mfr - left outer join metadataschemaregistry msr on mfr.metadata_schema_id = msr.metadata_schema_id - where msr.short_id = 'dc' - and mfr.element = 'identifier' - and mfr.qualifier = 'uri') T1 - - where uuid not in ( - select c.uuid as uuid from collection c - left outer join handle h on h.resource_id = c.uuid - left outer join metadatavalue mv on mv.dspace_object_id = c.uuid - left outer join metadatafieldregistry mfr on mv.metadata_field_id = mfr.metadata_field_id - left outer join metadataschemaregistry msr on mfr.metadata_schema_id = msr.metadata_schema_id - where msr.short_id = 'dc' - and mfr.element = 'identifier' - and mfr.qualifier = 'uri' - ) -; - diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/README.md b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/README.md deleted file mode 100644 index 6cef123859ca..000000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/README.md +++ /dev/null @@ -1,84 +0,0 @@ -# Oracle Flyway Database Migrations (i.e. Upgrades) - ---- -WARNING: Oracle Support is deprecated. -See https://github.com/DSpace/DSpace/issues/8214 ---- - -The SQL scripts in this directory are Oracle-specific database migrations. They are -used to automatically upgrade your DSpace database using [Flyway](http://flywaydb.org/). -As such, these scripts are automatically called by Flyway when the DSpace -`DatabaseManager` initializes itself (see `initializeDatabase()` method). During -that process, Flyway determines which version of DSpace your database is using -and then executes the appropriate upgrade script(s) to bring it up to the latest -version. - -If any failures occur, Flyway will "rollback" the upgrade script which resulted -in an error and log the issue in the DSpace log file at `[dspace]/log/dspace.log.[date]` - -**WARNING:** IT IS NOT RECOMMENDED TO RUN THESE SCRIPTS MANUALLY. If you do so, -Flyway will may throw failures the next time you startup DSpace, as Flyway will -not realize you manually ran one or more scripts. - -Please see the Flyway Documentation for more information: http://flywaydb.org/ - -## Oracle Porting Notes for the Curious - -Oracle is missing quite a number of cool features found in Postgres, so -workarounds had to be found, most of which are hidden behind tests in -DatabaseManager. If Oracle is your DBMS, the workarounds are activated: - -Oracle doesn't like ';' characters in JDBC SQL - they have all been removed -from the DSpace source, including code in the .sql file reader to strip ;'s. - -browse code - LIMIT and OFFSET is used to limit browse results, and an -Oracle-hack is used to limit the result set to a given size - -Oracle has no boolean data type, so a new schema file was created that -uses NUMBER(1) (AKA 'integers') and code is inserted everywhere to use 0 for -false and 1 for true if DSpace is using Oracle. - -Oracle doesn't have a TEXT data type either, so TEXT columns are defined -as VARCHAR2 in the Oracle-specific schema. - -Oracle doesn't allow dynamic naming for objects, so our cute trick to -derive the name of the sequence by appending _seq to the table name -in a function doesn't work in Oracle - workaround is to insert Oracle -code to generate the name of the sequence and then place that into -our SQL calls to generate a new ID. - -Oracle doesn't let you directly set the value of sequences, so -update-sequences.sql is forced to use a special script sequpdate.sql -to update the sequences. - -Bitstream had a column 'size' which is a reserved word in Oracle, -so this had to be changed to 'size_bytes' with corresponding code changes. - -VARCHAR2 has a limit of 4000 characters, so DSpace text data is limited to 4k. -Going to the CLOB data type can get around that, but seemed like too much effort -for now. Note that with UTF-8 encoding that 4k could translate to 1300 -characters worst-case (every character taking up 3 bytes is the worst case -scenario.) - -### UPDATE 5 April 2007 - -CLOBs are now used as follows: -MetadataValue:text_value -Community:introductory_text -Community:copyright_text -Collection:introductory_text -Collection:license -Collection:copyright_text - -DatabaseManager had to have some of the type checking changed, because Oracle's -JDBC driver is reporting INTEGERS as type DECIMAL. - -Oracle doesn't like it when you reference table names in lower case when -getting JDBC metadata for the tables, so they are converted in TableRow -to upper case. - -### UPDATE 27 November 2012 - -Oracle complains with ORA-01408 if you attempt to create an index on a column which -has already had the UNIQUE contraint added (such an index is implicit in maintaining the uniqueness -of the column). See [DS-1370](https://jira.duraspace.org/browse/DS-1370) for details. diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V1.2__Initial_DSpace_1.2_Oracle_database_schema.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V1.2__Initial_DSpace_1.2_Oracle_database_schema.sql deleted file mode 100644 index 157274e05d66..000000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V1.2__Initial_DSpace_1.2_Oracle_database_schema.sql +++ /dev/null @@ -1,550 +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/ --- - --- =============================================================== --- WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING --- --- DO NOT MANUALLY RUN THIS DATABASE MIGRATION. IT WILL BE EXECUTED --- AUTOMATICALLY (IF NEEDED) BY "FLYWAY" WHEN YOU STARTUP DSPACE. --- http://flywaydb.org/ --- =============================================================== - -CREATE SEQUENCE bitstreamformatregistry_seq; -CREATE SEQUENCE fileextension_seq; -CREATE SEQUENCE bitstream_seq; -CREATE SEQUENCE eperson_seq; --- start group sequence at 0, since Anonymous group = 0 -CREATE SEQUENCE epersongroup_seq MINVALUE 0 START WITH 0; -CREATE SEQUENCE item_seq; -CREATE SEQUENCE bundle_seq; -CREATE SEQUENCE item2bundle_seq; -CREATE SEQUENCE bundle2bitstream_seq; -CREATE SEQUENCE dctyperegistry_seq; -CREATE SEQUENCE dcvalue_seq; -CREATE SEQUENCE community_seq; -CREATE SEQUENCE collection_seq; -CREATE SEQUENCE community2community_seq; -CREATE SEQUENCE community2collection_seq; -CREATE SEQUENCE collection2item_seq; -CREATE SEQUENCE resourcepolicy_seq; -CREATE SEQUENCE epersongroup2eperson_seq; -CREATE SEQUENCE handle_seq; -CREATE SEQUENCE workspaceitem_seq; -CREATE SEQUENCE workflowitem_seq; -CREATE SEQUENCE tasklistitem_seq; -CREATE SEQUENCE registrationdata_seq; -CREATE SEQUENCE subscription_seq; -CREATE SEQUENCE history_seq; -CREATE SEQUENCE historystate_seq; -CREATE SEQUENCE communities2item_seq; -CREATE SEQUENCE itemsbyauthor_seq; -CREATE SEQUENCE itemsbytitle_seq; -CREATE SEQUENCE itemsbydate_seq; -CREATE SEQUENCE itemsbydateaccessioned_seq; - - -------------------------------------------------------- --- BitstreamFormatRegistry table -------------------------------------------------------- -CREATE TABLE BitstreamFormatRegistry -( - bitstream_format_id INTEGER PRIMARY KEY, - mimetype VARCHAR2(48), - short_description VARCHAR2(128) UNIQUE, - description VARCHAR2(2000), - support_level INTEGER, - -- Identifies internal types - internal NUMBER(1) -); - -------------------------------------------------------- --- FileExtension table -------------------------------------------------------- -CREATE TABLE FileExtension -( - file_extension_id INTEGER PRIMARY KEY, - bitstream_format_id INTEGER REFERENCES BitstreamFormatRegistry(bitstream_format_id), - extension VARCHAR2(16) -); - -------------------------------------------------------- --- Bitstream table -------------------------------------------------------- -CREATE TABLE Bitstream -( - bitstream_id INTEGER PRIMARY KEY, - bitstream_format_id INTEGER REFERENCES BitstreamFormatRegistry(bitstream_format_id), - name VARCHAR2(256), - size_bytes INTEGER, - checksum VARCHAR2(64), - checksum_algorithm VARCHAR2(32), - description VARCHAR2(2000), - user_format_description VARCHAR2(2000), - source VARCHAR2(256), - internal_id VARCHAR2(256), - deleted NUMBER(1), - store_number INTEGER, - sequence_id INTEGER -); - -------------------------------------------------------- --- EPerson table -------------------------------------------------------- -CREATE TABLE EPerson -( - eperson_id INTEGER PRIMARY KEY, - email VARCHAR2(64) UNIQUE, - password VARCHAR2(64), - firstname VARCHAR2(64), - lastname VARCHAR2(64), - can_log_in NUMBER(1), - require_certificate NUMBER(1), - self_registered NUMBER(1), - last_active TIMESTAMP, - sub_frequency INTEGER, - phone VARCHAR2(32) -); - -------------------------------------------------------- --- EPersonGroup table -------------------------------------------------------- -CREATE TABLE EPersonGroup -( - eperson_group_id INTEGER PRIMARY KEY, - name VARCHAR2(256) UNIQUE -); - -------------------------------------------------------- --- Item table -------------------------------------------------------- -CREATE TABLE Item -( - item_id INTEGER PRIMARY KEY, - submitter_id INTEGER REFERENCES EPerson(eperson_id), - in_archive NUMBER(1), - withdrawn NUMBER(1), - last_modified TIMESTAMP, - owning_collection INTEGER -); - -------------------------------------------------------- --- Bundle table -------------------------------------------------------- -CREATE TABLE Bundle -( - bundle_id INTEGER PRIMARY KEY, - mets_bitstream_id INTEGER REFERENCES Bitstream(bitstream_id), - name VARCHAR2(16), -- ORIGINAL | THUMBNAIL | TEXT - primary_bitstream_id INTEGER REFERENCES Bitstream(bitstream_id) -); - -------------------------------------------------------- --- Item2Bundle table -------------------------------------------------------- -CREATE TABLE Item2Bundle -( - id INTEGER PRIMARY KEY, - item_id INTEGER REFERENCES Item(item_id), - bundle_id INTEGER REFERENCES Bundle(bundle_id) -); - --- index by item_id -CREATE INDEX item2bundle_item_idx on Item2Bundle(item_id); - -------------------------------------------------------- --- Bundle2Bitstream table -------------------------------------------------------- -CREATE TABLE Bundle2Bitstream -( - id INTEGER PRIMARY KEY, - bundle_id INTEGER REFERENCES Bundle(bundle_id), - bitstream_id INTEGER REFERENCES Bitstream(bitstream_id) -); - --- index by bundle_id -CREATE INDEX bundle2bitstream_bundle_idx ON Bundle2Bitstream(bundle_id); - -------------------------------------------------------- --- DCTypeRegistry table -------------------------------------------------------- -CREATE TABLE DCTypeRegistry -( - dc_type_id INTEGER PRIMARY KEY, - element VARCHAR2(64), - qualifier VARCHAR2(64), - scope_note VARCHAR2(2000), - UNIQUE(element, qualifier) -); - -------------------------------------------------------- --- DCValue table -------------------------------------------------------- -CREATE TABLE DCValue -( - dc_value_id INTEGER PRIMARY KEY, - item_id INTEGER REFERENCES Item(item_id), - dc_type_id INTEGER REFERENCES DCTypeRegistry(dc_type_id), - text_value VARCHAR2(2000), - text_lang VARCHAR2(24), - place INTEGER, - source_id INTEGER -); - --- An index for item_id - almost all access is based on --- instantiating the item object, which grabs all dcvalues --- related to that item -CREATE INDEX dcvalue_item_idx on DCValue(item_id); - -------------------------------------------------------- --- Community table -------------------------------------------------------- -CREATE TABLE Community -( - community_id INTEGER PRIMARY KEY, - name VARCHAR2(128) UNIQUE, - short_description VARCHAR2(512), - introductory_text VARCHAR2(2000), - logo_bitstream_id INTEGER REFERENCES Bitstream(bitstream_id), - copyright_text VARCHAR2(2000), - side_bar_text VARCHAR2(2000) -); - -------------------------------------------------------- --- Collection table -------------------------------------------------------- -CREATE TABLE Collection -( - collection_id INTEGER PRIMARY KEY, - name VARCHAR2(128), - short_description VARCHAR2(512), - introductory_text VARCHAR2(2000), - logo_bitstream_id INTEGER REFERENCES Bitstream(bitstream_id), - template_item_id INTEGER REFERENCES Item(item_id), - provenance_description VARCHAR2(2000), - license VARCHAR2(2000), - copyright_text VARCHAR2(2000), - side_bar_text VARCHAR2(2000), - workflow_step_1 INTEGER REFERENCES EPersonGroup( eperson_group_id ), - workflow_step_2 INTEGER REFERENCES EPersonGroup( eperson_group_id ), - workflow_step_3 INTEGER REFERENCES EPersonGroup( eperson_group_id ) -); - -------------------------------------------------------- --- Community2Community table -------------------------------------------------------- -CREATE TABLE Community2Community -( - id INTEGER PRIMARY KEY, - parent_comm_id INTEGER REFERENCES Community(community_id), - child_comm_id INTEGER REFERENCES Community(community_id) -); - -------------------------------------------------------- --- Community2Collection table -------------------------------------------------------- -CREATE TABLE Community2Collection -( - id INTEGER PRIMARY KEY, - community_id INTEGER REFERENCES Community(community_id), - collection_id INTEGER REFERENCES Collection(collection_id) -); - -------------------------------------------------------- --- Collection2Item table -------------------------------------------------------- -CREATE TABLE Collection2Item -( - id INTEGER PRIMARY KEY, - collection_id INTEGER REFERENCES Collection(collection_id), - item_id INTEGER REFERENCES Item(item_id) -); - --- index by collection_id -CREATE INDEX collection2item_collection_idx ON Collection2Item(collection_id); - -------------------------------------------------------- --- ResourcePolicy table -------------------------------------------------------- -CREATE TABLE ResourcePolicy -( - policy_id INTEGER PRIMARY KEY, - resource_type_id INTEGER, - resource_id INTEGER, - action_id INTEGER, - eperson_id INTEGER REFERENCES EPerson(eperson_id), - epersongroup_id INTEGER REFERENCES EPersonGroup(eperson_group_id), - start_date DATE, - end_date DATE -); - --- index by resource_type,resource_id - all queries by --- authorization manager are select type=x, id=y, action=z -CREATE INDEX resourcepolicy_type_id_idx ON ResourcePolicy(resource_type_id,resource_id); - -------------------------------------------------------- --- EPersonGroup2EPerson table -------------------------------------------------------- -CREATE TABLE EPersonGroup2EPerson -( - id INTEGER PRIMARY KEY, - eperson_group_id INTEGER REFERENCES EPersonGroup(eperson_group_id), - eperson_id INTEGER REFERENCES EPerson(eperson_id) -); - --- Index by group ID (used heavily by AuthorizeManager) -CREATE INDEX epersongroup2eperson_group_idx on EPersonGroup2EPerson(eperson_group_id); - - -------------------------------------------------------- --- Handle table -------------------------------------------------------- -CREATE TABLE Handle -( - handle_id INTEGER PRIMARY KEY, - handle VARCHAR2(256) UNIQUE, - resource_type_id INTEGER, - resource_id INTEGER -); - -------------------------------------------------------- --- WorkspaceItem table -------------------------------------------------------- -CREATE TABLE WorkspaceItem -( - workspace_item_id INTEGER PRIMARY KEY, - item_id INTEGER REFERENCES Item(item_id), - collection_id INTEGER REFERENCES Collection(collection_id), - -- Answers to questions on first page of submit UI - multiple_titles NUMBER(1), -- boolean - published_before NUMBER(1), - multiple_files NUMBER(1), - -- How for the user has got in the submit process - stage_reached INTEGER -); - -------------------------------------------------------- --- WorkflowItem table -------------------------------------------------------- -CREATE TABLE WorkflowItem -( - workflow_id INTEGER PRIMARY KEY, - item_id INTEGER UNIQUE REFERENCES Item(item_id), - collection_id INTEGER REFERENCES Collection(collection_id), - state INTEGER, - owner INTEGER REFERENCES EPerson(eperson_id), - - -- Answers to questions on first page of submit UI - multiple_titles NUMBER(1), - published_before NUMBER(1), - multiple_files NUMBER(1) - -- Note: stage reached not applicable here - people involved in workflow - -- can always jump around submission UI - -); - -------------------------------------------------------- --- TasklistItem table -------------------------------------------------------- -CREATE TABLE TasklistItem -( - tasklist_id INTEGER PRIMARY KEY, - eperson_id INTEGER REFERENCES EPerson(eperson_id), - workflow_id INTEGER REFERENCES WorkflowItem(workflow_id) -); - - -------------------------------------------------------- --- RegistrationData table -------------------------------------------------------- -CREATE TABLE RegistrationData -( - registrationdata_id INTEGER PRIMARY KEY, - email VARCHAR2(64) UNIQUE, - token VARCHAR2(48), - expires TIMESTAMP -); - - -------------------------------------------------------- --- Subscription table -------------------------------------------------------- -CREATE TABLE Subscription -( - subscription_id INTEGER PRIMARY KEY, - eperson_id INTEGER REFERENCES EPerson(eperson_id), - collection_id INTEGER REFERENCES Collection(collection_id) -); - - -------------------------------------------------------- --- History table -------------------------------------------------------- -CREATE TABLE History -( - history_id INTEGER PRIMARY KEY, - -- When it was stored - creation_date TIMESTAMP, - -- A checksum to keep INTEGERizations from being stored more than once - checksum VARCHAR2(32) UNIQUE -); - -------------------------------------------------------- --- HistoryState table -------------------------------------------------------- -CREATE TABLE HistoryState -( - history_state_id INTEGER PRIMARY KEY, - object_id VARCHAR2(64) -); - ------------------------------------------------------------- --- Browse subsystem tables and views ------------------------------------------------------------- - -------------------------------------------------------- --- Communities2Item table -------------------------------------------------------- -CREATE TABLE Communities2Item -( - id INTEGER PRIMARY KEY, - community_id INTEGER REFERENCES Community(community_id), - item_id INTEGER REFERENCES Item(item_id) -); - -------------------------------------------------------- --- Community2Item view ------------------------------------------------------- -CREATE VIEW Community2Item as -SELECT Community2Collection.community_id, Collection2Item.item_id -FROM Community2Collection, Collection2Item -WHERE Collection2Item.collection_id = Community2Collection.collection_id -; - -------------------------------------------------------- --- ItemsByAuthor table -------------------------------------------------------- -CREATE TABLE ItemsByAuthor -( - items_by_author_id INTEGER PRIMARY KEY, - item_id INTEGER REFERENCES Item(item_id), - author VARCHAR2(2000), - sort_author VARCHAR2(2000) -); - --- index by sort_author, of course! -CREATE INDEX sort_author_idx on ItemsByAuthor(sort_author); - -------------------------------------------------------- --- CollectionItemsByAuthor view -------------------------------------------------------- -CREATE VIEW CollectionItemsByAuthor as -SELECT Collection2Item.collection_id, ItemsByAuthor.* -FROM ItemsByAuthor, Collection2Item -WHERE ItemsByAuthor.item_id = Collection2Item.item_id -; - -------------------------------------------------------- --- CommunityItemsByAuthor view -------------------------------------------------------- -CREATE VIEW CommunityItemsByAuthor as -SELECT Communities2Item.community_id, ItemsByAuthor.* -FROM ItemsByAuthor, Communities2Item -WHERE ItemsByAuthor.item_id = Communities2Item.item_id -; - ----------------------------------------- --- ItemsByTitle table ----------------------------------------- -CREATE TABLE ItemsByTitle -( - items_by_title_id INTEGER PRIMARY KEY, - item_id INTEGER REFERENCES Item(item_id), - title VARCHAR2(2000), - sort_title VARCHAR2(2000) -); - --- index by the sort_title -CREATE INDEX sort_title_idx on ItemsByTitle(sort_title); - - -------------------------------------------------------- --- CollectionItemsByTitle view -------------------------------------------------------- -CREATE VIEW CollectionItemsByTitle as -SELECT Collection2Item.collection_id, ItemsByTitle.* -FROM ItemsByTitle, Collection2Item -WHERE ItemsByTitle.item_id = Collection2Item.item_id -; - -------------------------------------------------------- --- CommunityItemsByTitle view -------------------------------------------------------- -CREATE VIEW CommunityItemsByTitle as -SELECT Communities2Item.community_id, ItemsByTitle.* -FROM ItemsByTitle, Communities2Item -WHERE ItemsByTitle.item_id = Communities2Item.item_id -; - -------------------------------------------------------- --- ItemsByDate table -------------------------------------------------------- -CREATE TABLE ItemsByDate -( - items_by_date_id INTEGER PRIMARY KEY, - item_id INTEGER REFERENCES Item(item_id), - date_issued VARCHAR2(2000) -); - --- sort by date -CREATE INDEX date_issued_idx on ItemsByDate(date_issued); - -------------------------------------------------------- --- CollectionItemsByDate view -------------------------------------------------------- -CREATE VIEW CollectionItemsByDate as -SELECT Collection2Item.collection_id, ItemsByDate.* -FROM ItemsByDate, Collection2Item -WHERE ItemsByDate.item_id = Collection2Item.item_id -; - -------------------------------------------------------- --- CommunityItemsByDate view -------------------------------------------------------- -CREATE VIEW CommunityItemsByDate as -SELECT Communities2Item.community_id, ItemsByDate.* -FROM ItemsByDate, Communities2Item -WHERE ItemsByDate.item_id = Communities2Item.item_id -; - -------------------------------------------------------- --- ItemsByDateAccessioned table -------------------------------------------------------- -CREATE TABLE ItemsByDateAccessioned -( - items_by_date_accessioned_id INTEGER PRIMARY KEY, - item_id INTEGER REFERENCES Item(item_id), - date_accessioned VARCHAR2(2000) -); - -------------------------------------------------------- --- CollectionItemsByDateAccession view -------------------------------------------------------- -CREATE VIEW CollectionItemsByDateAccession as -SELECT Collection2Item.collection_id, ItemsByDateAccessioned.* -FROM ItemsByDateAccessioned, Collection2Item -WHERE ItemsByDateAccessioned.item_id = Collection2Item.item_id -; - -------------------------------------------------------- --- CommunityItemsByDateAccession view -------------------------------------------------------- -CREATE VIEW CommunityItemsByDateAccession as -SELECT Communities2Item.community_id, ItemsByDateAccessioned.* -FROM ItemsByDateAccessioned, Communities2Item -WHERE ItemsByDateAccessioned.item_id = Communities2Item.item_id -; diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V1.3__Upgrade_to_DSpace_1.3_schema.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V1.3__Upgrade_to_DSpace_1.3_schema.sql deleted file mode 100644 index 37d7e115eb53..000000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V1.3__Upgrade_to_DSpace_1.3_schema.sql +++ /dev/null @@ -1,57 +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/ --- - --- =============================================================== --- WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING --- --- DO NOT MANUALLY RUN THIS DATABASE MIGRATION. IT WILL BE EXECUTED --- AUTOMATICALLY (IF NEEDED) BY "FLYWAY" WHEN YOU STARTUP DSPACE. --- http://flywaydb.org/ --- =============================================================== - -CREATE SEQUENCE epersongroup2workspaceitem_seq; - -------------------------------------------------------------------------------- --- create the new EPersonGroup2WorkspaceItem table -------------------------------------------------------------------------------- - -CREATE TABLE EPersonGroup2WorkspaceItem -( - id INTEGER PRIMARY KEY, - eperson_group_id INTEGER REFERENCES EPersonGroup(eperson_group_id), - workspace_item_id INTEGER REFERENCES WorkspaceItem(workspace_item_id) -); - -------------------------------------------------------------------------------- --- modification to collection table to support being able to change the --- submitter and collection admin group names -------------------------------------------------------------------------------- -ALTER TABLE collection ADD submitter INTEGER REFERENCES EPersonGroup(eperson_group_id); - -ALTER TABLE collection ADD admin INTEGER REFERENCES EPersonGroup(eperson_group_id); - -ALTER TABLE eperson ADD netid VARCHAR2(64) UNIQUE; - -------------------------------------------------------------------------------- --- Additional indices for performance -------------------------------------------------------------------------------- - --- index by resource id and resource type id -CREATE INDEX handle_resource_id_type_idx ON handle(resource_id, resource_type_id); - --- Indexing browse tables update/re-index performance -CREATE INDEX Communities2Item_item_id_idx ON Communities2Item( item_id ); -CREATE INDEX ItemsByAuthor_item_id_idx ON ItemsByAuthor(item_id); -CREATE INDEX ItemsByTitle_item_id_idx ON ItemsByTitle(item_id); -CREATE INDEX ItemsByDate_item_id_idx ON ItemsByDate(item_id); -CREATE INDEX ItemsByDateAcc_item_id_idx ON ItemsByDateAccessioned(item_id); - --- Improve mapping tables -CREATE INDEX Com2Coll_community_id_idx ON Community2Collection(community_id); -CREATE INDEX Com2Coll_collection_id_idx ON Community2Collection(collection_id); -CREATE INDEX Coll2Item_item_id_idx ON Collection2Item( item_id ); diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V1.4.2__Upgrade_to_DSpace_1.4.2_schema.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V1.4.2__Upgrade_to_DSpace_1.4.2_schema.sql deleted file mode 100644 index a713ced8bbb2..000000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V1.4.2__Upgrade_to_DSpace_1.4.2_schema.sql +++ /dev/null @@ -1,133 +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/ --- - --- =============================================================== --- WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING --- --- DO NOT MANUALLY RUN THIS DATABASE MIGRATION. IT WILL BE EXECUTED --- AUTOMATICALLY (IF NEEDED) BY "FLYWAY" WHEN YOU STARTUP DSPACE. --- http://flywaydb.org/ --- =============================================================== - ---------------------------------------- --- Update MetadataValue to include CLOB ---------------------------------------- - -CREATE TABLE MetadataValueTemp -( - metadata_value_id INTEGER PRIMARY KEY, - item_id INTEGER REFERENCES Item(item_id), - metadata_field_id INTEGER REFERENCES MetadataFieldRegistry(metadata_field_id), - text_value CLOB, - text_lang VARCHAR(64), - place INTEGER -); - -INSERT INTO MetadataValueTemp -SELECT * FROM MetadataValue; - -DROP VIEW dcvalue; -DROP TABLE MetadataValue; -ALTER TABLE MetadataValueTemp RENAME TO MetadataValue; - -CREATE VIEW dcvalue AS - SELECT MetadataValue.metadata_value_id AS "dc_value_id", MetadataValue.item_id, - MetadataValue.metadata_field_id AS "dc_type_id", MetadataValue.text_value, - MetadataValue.text_lang, MetadataValue.place - FROM MetadataValue, MetadataFieldRegistry - WHERE MetadataValue.metadata_field_id = MetadataFieldRegistry.metadata_field_id - AND MetadataFieldRegistry.metadata_schema_id = 1; - -CREATE INDEX metadatavalue_item_idx ON MetadataValue(item_id); -CREATE INDEX metadatavalue_item_idx2 ON MetadataValue(item_id,metadata_field_id); - ------------------------------------- --- Update Community to include CLOBs ------------------------------------- - -CREATE TABLE CommunityTemp -( - community_id INTEGER PRIMARY KEY, - name VARCHAR2(128), - short_description VARCHAR2(512), - introductory_text CLOB, - logo_bitstream_id INTEGER REFERENCES Bitstream(bitstream_id), - copyright_text CLOB, - side_bar_text VARCHAR2(2000) -); - -INSERT INTO CommunityTemp -SELECT * FROM Community; - -DROP TABLE Community CASCADE CONSTRAINTS; -ALTER TABLE CommunityTemp RENAME TO Community; - -ALTER TABLE Community2Community ADD CONSTRAINT fk_c2c_parent -FOREIGN KEY (parent_comm_id) -REFERENCES Community (community_id); - -ALTER TABLE Community2Community ADD CONSTRAINT fk_c2c_child -FOREIGN KEY (child_comm_id) -REFERENCES Community (community_id); - -ALTER TABLE Community2Collection ADD CONSTRAINT fk_c2c_community -FOREIGN KEY (community_id) -REFERENCES Community (community_id); - -ALTER TABLE Communities2Item ADD CONSTRAINT fk_c2i_community -FOREIGN KEY (community_id) -REFERENCES Community (community_id); - -------------------------------------- --- Update Collection to include CLOBs -------------------------------------- - -CREATE TABLE CollectionTemp -( - collection_id INTEGER PRIMARY KEY, - name VARCHAR2(128), - short_description VARCHAR2(512), - introductory_text CLOB, - logo_bitstream_id INTEGER REFERENCES Bitstream(bitstream_id), - template_item_id INTEGER REFERENCES Item(item_id), - provenance_description VARCHAR2(2000), - license CLOB, - copyright_text CLOB, - side_bar_text VARCHAR2(2000), - workflow_step_1 INTEGER REFERENCES EPersonGroup( eperson_group_id ), - workflow_step_2 INTEGER REFERENCES EPersonGroup( eperson_group_id ), - workflow_step_3 INTEGER REFERENCES EPersonGroup( eperson_group_id ), - submitter INTEGER REFERENCES EPersonGroup( eperson_group_id ), - admin INTEGER REFERENCES EPersonGroup( eperson_group_id ) -); - -INSERT INTO CollectionTemp -SELECT * FROM Collection; - -DROP TABLE Collection CASCADE CONSTRAINTS; -ALTER TABLE CollectionTemp RENAME TO Collection; - -ALTER TABLE Community2Collection ADD CONSTRAINT fk_c2c_collection -FOREIGN KEY (collection_id) -REFERENCES Collection (collection_id); - -ALTER TABLE Collection2Item ADD CONSTRAINT fk_c2i_collection -FOREIGN KEY (collection_id) -REFERENCES Collection (collection_id); - -ALTER TABLE WorkspaceItem ADD CONSTRAINT fk_wsi_collection -FOREIGN KEY (collection_id) -REFERENCES Collection (collection_id); - -ALTER TABLE WorkflowItem ADD CONSTRAINT fk_wfi_collection -FOREIGN KEY (collection_id) -REFERENCES Collection (collection_id); - -ALTER TABLE Subscription ADD CONSTRAINT fk_subs_collection -FOREIGN KEY (collection_id) -REFERENCES Collection (collection_id); diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V1.4__Upgrade_to_DSpace_1.4_schema.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V1.4__Upgrade_to_DSpace_1.4_schema.sql deleted file mode 100644 index 54cf10067b91..000000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V1.4__Upgrade_to_DSpace_1.4_schema.sql +++ /dev/null @@ -1,371 +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/ --- - --- =============================================================== --- WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING --- --- DO NOT MANUALLY RUN THIS DATABASE MIGRATION. IT WILL BE EXECUTED --- AUTOMATICALLY (IF NEEDED) BY "FLYWAY" WHEN YOU STARTUP DSPACE. --- http://flywaydb.org/ --- =============================================================== - -------------------------------------------------------------------------------- --- Sequences for Group within Group feature -------------------------------------------------------------------------------- -CREATE SEQUENCE group2group_seq; -CREATE SEQUENCE group2groupcache_seq; - ------------------------------------------------------- --- Group2Group table, records group membership in other groups ------------------------------------------------------- -CREATE TABLE Group2Group -( - id INTEGER PRIMARY KEY, - parent_id INTEGER REFERENCES EPersonGroup(eperson_group_id), - child_id INTEGER REFERENCES EPersonGroup(eperson_group_id) -); - ------------------------------------------------------- --- Group2GroupCache table, is the 'unwound' hierarchy in --- Group2Group. It explicitly names every parent child --- relationship, even with nested groups. For example, --- If Group2Group lists B is a child of A and C is a child of B, --- this table will have entries for parent(A,B), and parent(B,C) --- AND parent(A,C) so that all of the child groups of A can be --- looked up in a single simple query ------------------------------------------------------- -CREATE TABLE Group2GroupCache -( - id INTEGER PRIMARY KEY, - parent_id INTEGER REFERENCES EPersonGroup(eperson_group_id), - child_id INTEGER REFERENCES EPersonGroup(eperson_group_id) -); - - -------------------------------------------------------- --- New Metadata Tables and Sequences -------------------------------------------------------- -CREATE SEQUENCE metadataschemaregistry_seq; -CREATE SEQUENCE metadatafieldregistry_seq; -CREATE SEQUENCE metadatavalue_seq; - --- MetadataSchemaRegistry table -CREATE TABLE MetadataSchemaRegistry -( - metadata_schema_id INTEGER PRIMARY KEY, - namespace VARCHAR(256) UNIQUE, - short_id VARCHAR(32) -); - --- MetadataFieldRegistry table -CREATE TABLE MetadataFieldRegistry -( - metadata_field_id INTEGER PRIMARY KEY, - metadata_schema_id INTEGER NOT NULL REFERENCES MetadataSchemaRegistry(metadata_schema_id), - element VARCHAR(64), - qualifier VARCHAR(64), - scope_note VARCHAR2(2000) -); - --- MetadataValue table -CREATE TABLE MetadataValue -( - metadata_value_id INTEGER PRIMARY KEY, - item_id INTEGER REFERENCES Item(item_id), - metadata_field_id INTEGER REFERENCES MetadataFieldRegistry(metadata_field_id), - text_value VARCHAR2(2000), - text_lang VARCHAR(24), - place INTEGER -); - --- Create the DC schema -INSERT INTO MetadataSchemaRegistry VALUES (1,'http://dublincore.org/documents/dcmi-terms/','dc'); - --- Migrate the existing DCTypes into the new metadata field registry -INSERT INTO MetadataFieldRegistry - (metadata_schema_id, metadata_field_id, element, qualifier, scope_note) - SELECT '1' AS metadata_schema_id, dc_type_id, element, - qualifier, scope_note FROM dctyperegistry; - --- Copy the DCValues into the new MetadataValue table -INSERT INTO MetadataValue (item_id, metadata_field_id, text_value, text_lang, place) - SELECT item_id, dc_type_id, text_value, text_lang, place FROM dcvalue; - -DROP TABLE dcvalue; -CREATE VIEW dcvalue AS - SELECT MetadataValue.metadata_value_id AS "dc_value_id", MetadataValue.item_id, - MetadataValue.metadata_field_id AS "dc_type_id", MetadataValue.text_value, - MetadataValue.text_lang, MetadataValue.place - FROM MetadataValue, MetadataFieldRegistry - WHERE MetadataValue.metadata_field_id = MetadataFieldRegistry.metadata_field_id - AND MetadataFieldRegistry.metadata_schema_id = 1; - - --- After copying data from dctypregistry to metadataschemaregistry, we need to reset our sequences --- Update metadatafieldregistry_seq to new max value -DECLARE - curr NUMBER := 0; -BEGIN - SELECT max(metadata_field_id) INTO curr FROM metadatafieldregistry; - - curr := curr + 1; - - EXECUTE IMMEDIATE 'DROP SEQUENCE metadatafieldregistry_seq'; - - EXECUTE IMMEDIATE 'CREATE SEQUENCE metadatafieldregistry_seq START WITH ' || NVL(curr,1); -END; -/ --- Update metadatavalue_seq to new max value -DECLARE - curr NUMBER := 0; -BEGIN - SELECT max(metadata_value_id) INTO curr FROM metadatavalue; - - curr := curr + 1; - - EXECUTE IMMEDIATE 'DROP SEQUENCE metadatavalue_seq'; - - EXECUTE IMMEDIATE 'CREATE SEQUENCE metadatavalue_seq START WITH ' || NVL(curr,1); -END; -/ --- Update metadataschemaregistry_seq to new max value -DECLARE - curr NUMBER := 0; -BEGIN - SELECT max(metadata_schema_id) INTO curr FROM metadataschemaregistry; - - curr := curr + 1; - - EXECUTE IMMEDIATE 'DROP SEQUENCE metadataschemaregistry_seq'; - - EXECUTE IMMEDIATE 'CREATE SEQUENCE metadataschemaregistry_seq START WITH ' || NVL(curr,1); -END; -/ - --- Drop the old dctyperegistry -DROP TABLE dctyperegistry; - --- create indexes for the metadata tables -CREATE INDEX metadatavalue_item_idx ON MetadataValue(item_id); -CREATE INDEX metadatavalue_item_idx2 ON MetadataValue(item_id,metadata_field_id); -CREATE INDEX metadatafield_schema_idx ON MetadataFieldRegistry(metadata_schema_id); - - -------------------------------------------------------- --- Create the checksum checker tables -------------------------------------------------------- --- list of the possible results as determined --- by the system or an administrator - -CREATE TABLE checksum_results -( - result_code VARCHAR(64) PRIMARY KEY, - result_description VARCHAR2(2000) -); - - --- This table has a one-to-one relationship --- with the bitstream table. A row will be inserted --- every time a row is inserted into the bitstream table, and --- that row will be updated every time the checksum is --- re-calculated. - -CREATE TABLE most_recent_checksum -( - bitstream_id INTEGER PRIMARY KEY, - to_be_processed NUMBER(1) NOT NULL, - expected_checksum VARCHAR(64) NOT NULL, - current_checksum VARCHAR(64) NOT NULL, - last_process_start_date TIMESTAMP NOT NULL, - last_process_end_date TIMESTAMP NOT NULL, - checksum_algorithm VARCHAR(64) NOT NULL, - matched_prev_checksum NUMBER(1) NOT NULL, - result VARCHAR(64) REFERENCES checksum_results(result_code) -); - - --- A row will be inserted into this table every --- time a checksum is re-calculated. - -CREATE SEQUENCE checksum_history_seq; - -CREATE TABLE checksum_history -( - check_id INTEGER PRIMARY KEY, - bitstream_id INTEGER, - process_start_date TIMESTAMP, - process_end_date TIMESTAMP, - checksum_expected VARCHAR(64), - checksum_calculated VARCHAR(64), - result VARCHAR(64) REFERENCES checksum_results(result_code) -); - --- this will insert into the result code --- the initial results - -insert into checksum_results -values -( - 'INVALID_HISTORY', - 'Install of the cheksum checking code do not consider this history as valid' -); - -insert into checksum_results -values -( - 'BITSTREAM_NOT_FOUND', - 'The bitstream could not be found' -); - -insert into checksum_results -values -( - 'CHECKSUM_MATCH', - 'Current checksum matched previous checksum' -); - -insert into checksum_results -values -( - 'CHECKSUM_NO_MATCH', - 'Current checksum does not match previous checksum' -); - -insert into checksum_results -values -( - 'CHECKSUM_PREV_NOT_FOUND', - 'Previous checksum was not found: no comparison possible' -); - -insert into checksum_results -values -( - 'BITSTREAM_INFO_NOT_FOUND', - 'Bitstream info not found' -); - -insert into checksum_results -values -( - 'CHECKSUM_ALGORITHM_INVALID', - 'Invalid checksum algorithm' -); -insert into checksum_results -values -( - 'BITSTREAM_NOT_PROCESSED', - 'Bitstream marked to_be_processed=false' -); -insert into checksum_results -values -( - 'BITSTREAM_MARKED_DELETED', - 'Bitstream marked deleted in bitstream table' -); - --- this will insert into the most recent checksum --- on install all existing bitstreams --- setting all bitstreams already set as --- deleted to not be processed - -insert into most_recent_checksum -( - bitstream_id, - to_be_processed, - expected_checksum, - current_checksum, - last_process_start_date, - last_process_end_date, - checksum_algorithm, - matched_prev_checksum -) -select - bitstream.bitstream_id, - '1', - CASE WHEN bitstream.checksum IS NULL THEN '' ELSE bitstream.checksum END, - CASE WHEN bitstream.checksum IS NULL THEN '' ELSE bitstream.checksum END, - TO_TIMESTAMP(TO_CHAR(current_timestamp, 'DD-MM-RRRR HH24:MI:SS'), 'DD-MM-RRRR HH24:MI:SS'), - TO_TIMESTAMP(TO_CHAR(current_timestamp, 'DD-MM-RRRR HH24:MI:SS'), 'DD-MM-RRRR HH24:MI:SS'), - CASE WHEN bitstream.checksum_algorithm IS NULL THEN 'MD5' ELSE bitstream.checksum_algorithm END, - '1' -from bitstream; - --- Update all the deleted checksums --- to not be checked --- because they have since been --- deleted from the system - -update most_recent_checksum -set to_be_processed = 0 -where most_recent_checksum.bitstream_id in ( -select bitstream_id -from bitstream where deleted = '1' ); - --- this will insert into history table --- for the initial start --- we want to tell the users to disregard the initial --- inserts into the checksum history table - -insert into checksum_history -( - bitstream_id, - process_start_date, - process_end_date, - checksum_expected, - checksum_calculated -) -select most_recent_checksum.bitstream_id, - most_recent_checksum.last_process_end_date, - TO_TIMESTAMP(TO_CHAR(current_timestamp, 'DD-MM-RRRR HH24:MI:SS'), 'DD-MM-RRRR HH24:MI:SS'), - most_recent_checksum.expected_checksum, - most_recent_checksum.expected_checksum -FROM most_recent_checksum; - --- update the history to indicate that this was --- the first time the software was installed -update checksum_history -set result = 'INVALID_HISTORY'; - - -------------------------------------------------------- --- Table and views for 'browse by subject' functionality -------------------------------------------------------- -CREATE SEQUENCE itemsbysubject_seq; - -------------------------------------------------------- --- ItemsBySubject table -------------------------------------------------------- -CREATE TABLE ItemsBySubject -( - items_by_subject_id INTEGER PRIMARY KEY, - item_id INTEGER REFERENCES Item(item_id), - subject VARCHAR2(2000), - sort_subject VARCHAR2(2000) -); - --- index by sort_subject -CREATE INDEX sort_subject_idx on ItemsBySubject(sort_subject); - -------------------------------------------------------- --- CollectionItemsBySubject view -------------------------------------------------------- -CREATE VIEW CollectionItemsBySubject as -SELECT Collection2Item.collection_id, ItemsBySubject.* -FROM ItemsBySubject, Collection2Item -WHERE ItemsBySubject.item_id = Collection2Item.item_id -; - -------------------------------------------------------- --- CommunityItemsBySubject view -------------------------------------------------------- -CREATE VIEW CommunityItemsBySubject as -SELECT Communities2Item.community_id, ItemsBySubject.* -FROM ItemsBySubject, Communities2Item -WHERE ItemsBySubject.item_id = Communities2Item.item_id -; diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V1.5__Upgrade_to_DSpace_1.5_schema.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V1.5__Upgrade_to_DSpace_1.5_schema.sql deleted file mode 100644 index bb217bd0d18d..000000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V1.5__Upgrade_to_DSpace_1.5_schema.sql +++ /dev/null @@ -1,142 +0,0 @@ --- --- The contents of this file are subject to the license and copyright --- detailed in the LICENSE and NOTICE files at the root of the source --- tree and available online at --- --- http://www.dspace.org/license/ --- - --- =============================================================== --- WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING --- --- DO NOT MANUALLY RUN THIS DATABASE MIGRATION. IT WILL BE EXECUTED --- AUTOMATICALLY (IF NEEDED) BY "FLYWAY" WHEN YOU STARTUP DSPACE. --- http://flywaydb.org/ --- =============================================================== - --- Remove NOT NULL restrictions from the checksum columns of most_recent_checksum -ALTER TABLE most_recent_checksum MODIFY expected_checksum null; -ALTER TABLE most_recent_checksum MODIFY current_checksum null; - ------------------------------------------------------- --- New Column language language in EPerson ------------------------------------------------------- - -alter table eperson ADD language VARCHAR2(64); -update eperson set language = 'en'; - --- totally unused column -alter table bundle drop column mets_bitstream_id; - -------------------------------------------------------------------------------- --- Necessary for Configurable Submission functionality: --- Modification to workspaceitem table to support keeping track --- of the last page reached within a step in the Configurable Submission Process -------------------------------------------------------------------------------- -ALTER TABLE workspaceitem ADD page_reached INTEGER; - - -------------------------------------------------------------------------- --- Increase the mimetype field size to support larger types, such as the --- new Word 2007 mimetypes. -------------------------------------------------------------------------- -ALTER TABLE BitstreamFormatRegistry MODIFY (mimetype VARCHAR(256)); - - -------------------------------------------------------------------------- --- Tables to manage cache of item counts for communities and collections -------------------------------------------------------------------------- - -CREATE TABLE collection_item_count ( - collection_id INTEGER PRIMARY KEY REFERENCES collection(collection_id), - count INTEGER -); - -CREATE TABLE community_item_count ( - community_id INTEGER PRIMARY KEY REFERENCES community(community_id), - count INTEGER -); - ------------------------------------------------------------------- --- Remove sequences and tables of the old browse system ------------------------------------------------------------------- - -DROP SEQUENCE itemsbyauthor_seq; -DROP SEQUENCE itemsbytitle_seq; -DROP SEQUENCE itemsbydate_seq; -DROP SEQUENCE itemsbydateaccessioned_seq; -DROP SEQUENCE itemsbysubject_seq; - -DROP TABLE ItemsByAuthor CASCADE CONSTRAINTS; -DROP TABLE ItemsByTitle CASCADE CONSTRAINTS; -DROP TABLE ItemsByDate CASCADE CONSTRAINTS; -DROP TABLE ItemsByDateAccessioned CASCADE CONSTRAINTS; -DROP TABLE ItemsBySubject CASCADE CONSTRAINTS; - -DROP TABLE History CASCADE CONSTRAINTS; -DROP TABLE HistoryState CASCADE CONSTRAINTS; - ----------------------------------------------------------------- --- Add indexes for foreign key columns ----------------------------------------------------------------- - -CREATE INDEX fe_bitstream_fk_idx ON FileExtension(bitstream_format_id); - -CREATE INDEX bit_bitstream_fk_idx ON Bitstream(bitstream_format_id); - -CREATE INDEX g2g_parent_fk_idx ON Group2Group(parent_id); -CREATE INDEX g2g_child_fk_idx ON Group2Group(child_id); - --- CREATE INDEX g2gc_parent_fk_idx ON Group2Group(parent_id); --- CREATE INDEX g2gc_child_fk_idx ON Group2Group(child_id); - -CREATE INDEX item_submitter_fk_idx ON Item(submitter_id); - -CREATE INDEX bundle_primary_fk_idx ON Bundle(primary_bitstream_id); - -CREATE INDEX item2bundle_bundle_fk_idx ON Item2Bundle(bundle_id); - -CREATE INDEX bundle2bits_bitstream_fk_idx ON Bundle2Bitstream(bitstream_id); - -CREATE INDEX metadatavalue_field_fk_idx ON MetadataValue(metadata_field_id); - -CREATE INDEX community_logo_fk_idx ON Community(logo_bitstream_id); - -CREATE INDEX collection_logo_fk_idx ON Collection(logo_bitstream_id); -CREATE INDEX collection_template_fk_idx ON Collection(template_item_id); -CREATE INDEX collection_workflow1_fk_idx ON Collection(workflow_step_1); -CREATE INDEX collection_workflow2_fk_idx ON Collection(workflow_step_2); -CREATE INDEX collection_workflow3_fk_idx ON Collection(workflow_step_3); -CREATE INDEX collection_submitter_fk_idx ON Collection(submitter); -CREATE INDEX collection_admin_fk_idx ON Collection(admin); - -CREATE INDEX com2com_parent_fk_idx ON Community2Community(parent_comm_id); -CREATE INDEX com2com_child_fk_idx ON Community2Community(child_comm_id); - -CREATE INDEX rp_eperson_fk_idx ON ResourcePolicy(eperson_id); -CREATE INDEX rp_epersongroup_fk_idx ON ResourcePolicy(epersongroup_id); - -CREATE INDEX epg2ep_eperson_fk_idx ON EPersonGroup2EPerson(eperson_id); - -CREATE INDEX workspace_item_fk_idx ON WorkspaceItem(item_id); -CREATE INDEX workspace_coll_fk_idx ON WorkspaceItem(collection_id); - --- CREATE INDEX workflow_item_fk_idx ON WorkflowItem(item_id); -CREATE INDEX workflow_coll_fk_idx ON WorkflowItem(collection_id); -CREATE INDEX workflow_owner_fk_idx ON WorkflowItem(owner); - -CREATE INDEX tasklist_eperson_fk_idx ON TasklistItem(eperson_id); -CREATE INDEX tasklist_workflow_fk_idx ON TasklistItem(workflow_id); - -CREATE INDEX subs_eperson_fk_idx ON Subscription(eperson_id); -CREATE INDEX subs_collection_fk_idx ON Subscription(collection_id); - -CREATE INDEX epg2wi_group_fk_idx ON epersongroup2workspaceitem(eperson_group_id); -CREATE INDEX epg2wi_workspace_fk_idx ON epersongroup2workspaceitem(workspace_item_id); - -CREATE INDEX Comm2Item_community_fk_idx ON Communities2Item( community_id ); - -CREATE INDEX mrc_result_fk_idx ON most_recent_checksum( result ); - -CREATE INDEX ch_result_fk_idx ON checksum_history( result ); - diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V1.6__Upgrade_to_DSpace_1.6_schema.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V1.6__Upgrade_to_DSpace_1.6_schema.sql deleted file mode 100644 index 659ca32983cc..000000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V1.6__Upgrade_to_DSpace_1.6_schema.sql +++ /dev/null @@ -1,93 +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/ --- - --- =============================================================== --- WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING --- --- DO NOT MANUALLY RUN THIS DATABASE MIGRATION. IT WILL BE EXECUTED --- AUTOMATICALLY (IF NEEDED) BY "FLYWAY" WHEN YOU STARTUP DSPACE. --- http://flywaydb.org/ --- =============================================================== - ------------------------------------------------------------------- --- New Column for Community Admin - Delegated Admin patch (DS-228) ------------------------------------------------------------------- -ALTER TABLE community ADD admin INTEGER REFERENCES epersongroup ( eperson_group_id ); -CREATE INDEX community_admin_fk_idx ON Community(admin); - -------------------------------------------------------------------------- --- DS-236 schema changes for Authority Control of Metadata Values -------------------------------------------------------------------------- -ALTER TABLE MetadataValue - ADD ( authority VARCHAR(100), - confidence INTEGER DEFAULT -1); - --------------------------------------------------------------------------- --- DS-295 CC License being assigned incorrect Mime Type during submission. --------------------------------------------------------------------------- -UPDATE bitstream SET bitstream_format_id = - (SELECT bitstream_format_id FROM bitstreamformatregistry WHERE short_description = 'CC License') - WHERE name = 'license_text' AND source = 'org.dspace.license.CreativeCommons'; - -UPDATE bitstream SET bitstream_format_id = - (SELECT bitstream_format_id FROM bitstreamformatregistry WHERE short_description = 'RDF XML') - WHERE name = 'license_rdf' AND source = 'org.dspace.license.CreativeCommons'; - -------------------------------------------------------------------------- --- DS-260 Cleanup of Owning collection column for template item created --- with the JSPUI after the collection creation -------------------------------------------------------------------------- -UPDATE item SET owning_collection = null WHERE item_id IN - (SELECT template_item_id FROM collection WHERE template_item_id IS NOT null); - --- Recreate restraints with a know name and deferrable option! --- (The previous version of these constraints is dropped by org.dspace.storage.rdbms.migration.V1_5_9__Drop_constraint_for_DSpace_1_6_schema) -ALTER TABLE community2collection ADD CONSTRAINT comm2coll_collection_fk FOREIGN KEY (collection_id) REFERENCES collection DEFERRABLE; -ALTER TABLE community2community ADD CONSTRAINT com2com_child_fk FOREIGN KEY (child_comm_id) REFERENCES community DEFERRABLE; -ALTER TABLE collection2item ADD CONSTRAINT coll2item_item_fk FOREIGN KEY (item_id) REFERENCES item DEFERRABLE; - - ------------------------------------------------------------------- --- New tables /sequences for the harvester functionality (DS-289) ------------------------------------------------------------------- -CREATE SEQUENCE harvested_collection_seq; -CREATE SEQUENCE harvested_item_seq; - -------------------------------------------------------- --- Create the harvest settings table -------------------------------------------------------- --- Values used by the OAIHarvester to harvest a collection --- HarvestInstance is the DAO class for this table - -CREATE TABLE harvested_collection -( - collection_id INTEGER REFERENCES collection(collection_id) ON DELETE CASCADE, - harvest_type INTEGER, - oai_source VARCHAR(256), - oai_set_id VARCHAR(256), - harvest_message VARCHAR2(512), - metadata_config_id VARCHAR(256), - harvest_status INTEGER, - harvest_start_time TIMESTAMP, - last_harvested TIMESTAMP, - id INTEGER PRIMARY KEY -); - -CREATE INDEX harvested_collection_fk_idx ON harvested_collection(collection_id); - - -CREATE TABLE harvested_item -( - item_id INTEGER REFERENCES item(item_id) ON DELETE CASCADE, - last_harvested TIMESTAMP, - oai_id VARCHAR(64), - id INTEGER PRIMARY KEY -); - -CREATE INDEX harvested_item_fk_idx ON harvested_item(item_id); - diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V1.7__Upgrade_to_DSpace_1.7_schema.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V1.7__Upgrade_to_DSpace_1.7_schema.sql deleted file mode 100644 index f4b2737fb3a8..000000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V1.7__Upgrade_to_DSpace_1.7_schema.sql +++ /dev/null @@ -1,20 +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/ --- - --- =============================================================== --- WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING --- --- DO NOT MANUALLY RUN THIS DATABASE MIGRATION. IT WILL BE EXECUTED --- AUTOMATICALLY (IF NEEDED) BY "FLYWAY" WHEN YOU STARTUP DSPACE. --- http://flywaydb.org/ --- =============================================================== - ------------------------------------------------------------------- --- Remove unused / obsolete sequence 'dctyperegistry_seq' (DS-729) ------------------------------------------------------------------- -DROP SEQUENCE dctyperegistry_seq; diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V1.8__Upgrade_to_DSpace_1.8_schema.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V1.8__Upgrade_to_DSpace_1.8_schema.sql deleted file mode 100644 index f96cddbe7fd4..000000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V1.8__Upgrade_to_DSpace_1.8_schema.sql +++ /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/ --- - --- =============================================================== --- WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING --- --- DO NOT MANUALLY RUN THIS DATABASE MIGRATION. IT WILL BE EXECUTED --- AUTOMATICALLY (IF NEEDED) BY "FLYWAY" WHEN YOU STARTUP DSPACE. --- http://flywaydb.org/ --- =============================================================== - -------------------------------------------- --- New column for bitstream order DS-749 -- -------------------------------------------- -ALTER TABLE bundle2bitstream ADD bitstream_order INTEGER; - ---Place the sequence id's in the order -UPDATE bundle2bitstream SET bitstream_order=(SELECT sequence_id FROM bitstream WHERE bitstream.bitstream_id=bundle2bitstream.bitstream_id); diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V3.0__Upgrade_to_DSpace_3.x_schema.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V3.0__Upgrade_to_DSpace_3.x_schema.sql deleted file mode 100644 index 472dc7dc5279..000000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V3.0__Upgrade_to_DSpace_3.x_schema.sql +++ /dev/null @@ -1,52 +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/ --- - --- =============================================================== --- WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING --- --- DO NOT MANUALLY RUN THIS DATABASE MIGRATION. IT WILL BE EXECUTED --- AUTOMATICALLY (IF NEEDED) BY "FLYWAY" WHEN YOU STARTUP DSPACE. --- http://flywaydb.org/ --- =============================================================== - -ALTER TABLE resourcepolicy - ADD ( - rpname VARCHAR2(30), - rptype VARCHAR2(30), - rpdescription VARCHAR2(100) - ); - - -ALTER TABLE item ADD discoverable NUMBER(1); - -CREATE TABLE versionhistory -( - versionhistory_id INTEGER NOT NULL PRIMARY KEY -); - -CREATE TABLE versionitem -( - versionitem_id INTEGER NOT NULL PRIMARY KEY, - item_id INTEGER REFERENCES Item(item_id), - version_number INTEGER, - eperson_id INTEGER REFERENCES EPerson(eperson_id), - version_date TIMESTAMP, - version_summary VARCHAR2(255), - versionhistory_id INTEGER REFERENCES VersionHistory(versionhistory_id) -); - -CREATE SEQUENCE versionitem_seq; -CREATE SEQUENCE versionhistory_seq; - - -------------------------------------------- --- New columns and longer hash for salted password hashing DS-861 -- -------------------------------------------- -ALTER TABLE EPerson modify( password VARCHAR(128)); -ALTER TABLE EPerson ADD salt VARCHAR(32); -ALTER TABLE EPerson ADD digest_algorithm VARCHAR(16); diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V4.0__Upgrade_to_DSpace_4.x_schema.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V4.0__Upgrade_to_DSpace_4.x_schema.sql deleted file mode 100644 index 8102376906a3..000000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V4.0__Upgrade_to_DSpace_4.x_schema.sql +++ /dev/null @@ -1,88 +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/ --- - --- =============================================================== --- WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING --- --- DO NOT MANUALLY RUN THIS DATABASE MIGRATION. IT WILL BE EXECUTED --- AUTOMATICALLY (IF NEEDED) BY "FLYWAY" WHEN YOU STARTUP DSPACE. --- http://flywaydb.org/ --- =============================================================== - -------------------------------------------- --- Ensure that discoverable has a sensible default -------------------------------------------- -update item set discoverable=1 WHERE discoverable IS NULL; - -------------------------------------------- --- Add support for DOIs (table and seq.) -- -------------------------------------------- - -CREATE TABLE Doi -( - doi_id INTEGER PRIMARY KEY, - doi VARCHAR2(256) UNIQUE, - resource_type_id INTEGER, - resource_id INTEGER, - status INTEGER -); - -CREATE SEQUENCE doi_seq; - --- index by resource id and resource type id -CREATE INDEX doi_resource_id_type_idx ON doi(resource_id, resource_type_id); - -------------------------------------------- --- Table of running web applications for 'dspace version' -- -------------------------------------------- - -CREATE TABLE Webapp -( - webapp_id INTEGER NOT NULL PRIMARY KEY, - AppName VARCHAR2(32), - URL VARCHAR2(1000), - Started TIMESTAMP, - isUI NUMBER(1) -); - -CREATE SEQUENCE webapp_seq; - -------------------------------------------------------- --- DS-824 RequestItem table -------------------------------------------------------- - -CREATE TABLE requestitem -( - requestitem_id INTEGER NOT NULL, - token varchar(48), - item_id INTEGER, - bitstream_id INTEGER, - allfiles NUMBER(1), - request_email VARCHAR2(64), - request_name VARCHAR2(64), - request_date TIMESTAMP, - accept_request NUMBER(1), - decision_date TIMESTAMP, - expires TIMESTAMP, - CONSTRAINT requestitem_pkey PRIMARY KEY (requestitem_id), - CONSTRAINT requestitem_token_key UNIQUE (token) -); - -CREATE SEQUENCE requestitem_seq; - -------------------------------------------------------- --- DS-1655 Disable "Initial Questions" page in Submission UI by default -------------------------------------------------------- -update workspaceitem set multiple_titles=1, published_before=1, multiple_files=1; -update workflowitem set multiple_titles=1, published_before=1, multiple_files=1; - -------------------------------------------------------- --- DS-1811 Removing a collection fails if non-Solr DAO has been used before for item count -------------------------------------------------------- -delete from collection_item_count; -delete from community_item_count; diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V4.9_2015.10.26__DS-2818_registry_update.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V4.9_2015.10.26__DS-2818_registry_update.sql deleted file mode 100644 index 6d75905ec980..000000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V4.9_2015.10.26__DS-2818_registry_update.sql +++ /dev/null @@ -1,64 +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/ --- - --- =============================================================== --- WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING --- --- DO NOT MANUALLY RUN THIS DATABASE MIGRATION. IT WILL BE EXECUTED --- AUTOMATICALLY (IF NEEDED) BY "FLYWAY" WHEN YOU STARTUP DSPACE. --- http://flywaydb.org/ --- =============================================================== - --- Special case of migration, we need to the EPerson schema in order to get our metadata for all queries to work --- but we cannot a DB connection until our database is up to date, so we need to create our registries manually in sql - -INSERT INTO metadataschemaregistry (metadata_schema_id, namespace, short_id) SELECT metadataschemaregistry_seq.nextval, 'http://dspace.org/eperson' as namespace, 'eperson' as short_id FROM dual - WHERE NOT EXISTS (SELECT metadata_schema_id,namespace,short_id FROM metadataschemaregistry WHERE namespace = 'http://dspace.org/eperson' AND short_id = 'eperson'); - - --- Insert eperson.firstname -INSERT INTO metadatafieldregistry (metadata_field_id, metadata_schema_id, element) - SELECT metadatafieldregistry_seq.nextval, - (SELECT metadata_schema_id FROM metadataschemaregistry WHERE short_id='eperson'), 'firstname' FROM dual - WHERE NOT EXISTS - (SELECT metadata_field_id,element FROM metadatafieldregistry WHERE element = 'firstname' AND qualifier IS NULL AND metadata_schema_id = (SELECT metadata_schema_id FROM metadataschemaregistry WHERE short_id='eperson')); - --- Insert eperson.lastname -INSERT INTO metadatafieldregistry (metadata_field_id, metadata_schema_id, element) - SELECT metadatafieldregistry_seq.nextval, - (SELECT metadata_schema_id FROM metadataschemaregistry WHERE short_id='eperson'), 'lastname' FROM dual - WHERE NOT EXISTS - (SELECT metadata_field_id,element FROM metadatafieldregistry WHERE element = 'lastname' AND qualifier IS NULL AND metadata_schema_id = (SELECT metadata_schema_id FROM metadataschemaregistry WHERE short_id='eperson')); - --- Insert eperson.phone -INSERT INTO metadatafieldregistry (metadata_field_id, metadata_schema_id, element) - SELECT metadatafieldregistry_seq.nextval, - (SELECT metadata_schema_id FROM metadataschemaregistry WHERE short_id='eperson'), 'phone' FROM dual - WHERE NOT EXISTS - (SELECT metadata_field_id,element FROM metadatafieldregistry WHERE element = 'phone' AND qualifier IS NULL AND metadata_schema_id = (SELECT metadata_schema_id FROM metadataschemaregistry WHERE short_id='eperson')); - --- Insert eperson.language -INSERT INTO metadatafieldregistry (metadata_field_id, metadata_schema_id, element) - SELECT metadatafieldregistry_seq.nextval, - (SELECT metadata_schema_id FROM metadataschemaregistry WHERE short_id='eperson'), 'language' FROM dual - WHERE NOT EXISTS - (SELECT metadata_field_id,element FROM metadatafieldregistry WHERE element = 'language' AND qualifier IS NULL AND metadata_schema_id = (SELECT metadata_schema_id FROM metadataschemaregistry WHERE short_id='eperson')); - --- Insert into dc.provenance -INSERT INTO metadatafieldregistry (metadata_field_id, metadata_schema_id, element) - SELECT metadatafieldregistry_seq.nextval, - (SELECT metadata_schema_id FROM metadataschemaregistry WHERE short_id='dc'), 'provenance' FROM dual - WHERE NOT EXISTS - (SELECT metadata_field_id,element FROM metadatafieldregistry WHERE element = 'provenance' AND qualifier IS NULL AND metadata_schema_id = (SELECT metadata_schema_id FROM metadataschemaregistry WHERE short_id='dc')); - --- Insert into dc.rights.license -INSERT INTO metadatafieldregistry (metadata_field_id, metadata_schema_id, element, qualifier) - SELECT metadatafieldregistry_seq.nextval, - (SELECT metadata_schema_id FROM metadataschemaregistry WHERE short_id='dc'), 'rights', 'license' FROM dual - WHERE NOT EXISTS - (SELECT metadata_field_id,element,qualifier FROM metadatafieldregistry WHERE element = 'rights' AND qualifier='license' AND metadata_schema_id = (SELECT metadata_schema_id FROM metadataschemaregistry WHERE short_id='dc')); \ No newline at end of file diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V5.0_2014.08.08__DS-1945_Helpdesk_Request_a_Copy.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V5.0_2014.08.08__DS-1945_Helpdesk_Request_a_Copy.sql deleted file mode 100644 index c86cfe31223e..000000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V5.0_2014.08.08__DS-1945_Helpdesk_Request_a_Copy.sql +++ /dev/null @@ -1,20 +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/ --- - --- =============================================================== --- WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING --- --- DO NOT MANUALLY RUN THIS DATABASE MIGRATION. IT WILL BE EXECUTED --- AUTOMATICALLY (IF NEEDED) BY "FLYWAY" WHEN YOU STARTUP DSPACE. --- http://flywaydb.org/ --- =============================================================== - ------------------------------------------------------- --- DS-1945 RequestItem Helpdesk, store request message ------------------------------------------------------- -ALTER TABLE requestitem ADD request_message VARCHAR2(2000); diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V5.0_2014.09.26__DS-1582_Metadata_For_All_Objects.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V5.0_2014.09.26__DS-1582_Metadata_For_All_Objects.sql deleted file mode 100644 index 8f0cd0d5e1d7..000000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V5.0_2014.09.26__DS-1582_Metadata_For_All_Objects.sql +++ /dev/null @@ -1,333 +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/ --- - --- =============================================================== --- WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING --- --- DO NOT MANUALLY RUN THIS DATABASE MIGRATION. IT WILL BE EXECUTED --- AUTOMATICALLY (IF NEEDED) BY "FLYWAY" WHEN YOU STARTUP DSPACE. --- http://flywaydb.org/ --- =============================================================== - ------------------------------------------------------- --- DS-1582 Metadata on all DSpace Objects --- NOTE: This script also has a complimentary Flyway Java Migration --- which drops the "item_id" constraint on metadatavalue --- org.dspace.storage.rdbms.migration.V5_0_2014_09_25__DS_1582_Metadata_For_All_Objects_drop_constraint ------------------------------------------------------- -alter table metadatavalue rename column item_id to resource_id; - -alter table metadatavalue MODIFY(resource_id not null); -alter table metadatavalue add resource_type_id integer; -UPDATE metadatavalue SET resource_type_id = 2; -alter table metadatavalue MODIFY(resource_type_id not null); - - - --- --------- --- community --- --------- - -INSERT INTO metadatavalue (metadata_value_id, resource_id, resource_type_id, metadata_field_id, text_value, text_lang, place) -SELECT -metadatavalue_seq.nextval as metadata_value_id, -community_id AS resource_id, -4 AS resource_type_id, -(select metadata_field_id from metadatafieldregistry where metadata_schema_id=(select metadata_schema_id from metadataschemaregistry where short_id='dc') and element = 'description' and qualifier is null) AS metadata_field_id, -introductory_text AS text_value, -null AS text_lang, -0 AS place -FROM community where not introductory_text is null; - -INSERT INTO metadatavalue (metadata_value_id, resource_id, resource_type_id, metadata_field_id, text_value, text_lang, place) -SELECT -metadatavalue_seq.nextval as metadata_value_id, -community_id AS resource_id, -4 AS resource_type_id, -(select metadata_field_id from metadatafieldregistry where metadata_schema_id=(select metadata_schema_id from metadataschemaregistry where short_id='dc') and element = 'description' and qualifier = 'abstract') AS metadata_field_id, -short_description AS text_value, -null AS text_lang, -0 AS place -FROM community where not short_description is null; - -INSERT INTO metadatavalue (metadata_value_id, resource_id, resource_type_id, metadata_field_id, text_value, text_lang, place) -SELECT -metadatavalue_seq.nextval as metadata_value_id, -community_id AS resource_id, -4 AS resource_type_id, -(select metadata_field_id from metadatafieldregistry where metadata_schema_id=(select metadata_schema_id from metadataschemaregistry where short_id='dc') and element = 'description' and qualifier = 'tableofcontents') AS metadata_field_id, -side_bar_text AS text_value, -null AS text_lang, -0 AS place -FROM community where not side_bar_text is null; - -INSERT INTO metadatavalue (metadata_value_id, resource_id, resource_type_id, metadata_field_id, text_value, text_lang, place) -SELECT -metadatavalue_seq.nextval as metadata_value_id, -community_id AS resource_id, -4 AS resource_type_id, -(select metadata_field_id from metadatafieldregistry where metadata_schema_id=(select metadata_schema_id from metadataschemaregistry where short_id='dc') and element = 'rights' and qualifier is null) AS metadata_field_id, -copyright_text AS text_value, -null AS text_lang, -0 AS place -FROM community where not copyright_text is null; - -INSERT INTO metadatavalue (metadata_value_id, resource_id, resource_type_id, metadata_field_id, text_value, text_lang, place) -SELECT -metadatavalue_seq.nextval as metadata_value_id, -community_id AS resource_id, -4 AS resource_type_id, -(select metadata_field_id from metadatafieldregistry where metadata_schema_id=(select metadata_schema_id from metadataschemaregistry where short_id='dc') and element = 'title' and qualifier is null) AS metadata_field_id, -name AS text_value, -null AS text_lang, -0 AS place -FROM community where not name is null; - -alter table community drop (introductory_text, short_description, side_bar_text, copyright_text, name); - - --- ---------- --- collection --- ---------- - - - -INSERT INTO metadatavalue (metadata_value_id, resource_id, resource_type_id, metadata_field_id, text_value, text_lang, place) -SELECT -metadatavalue_seq.nextval as metadata_value_id, -collection_id AS resource_id, -3 AS resource_type_id, -(select metadata_field_id from metadatafieldregistry where metadata_schema_id=(select metadata_schema_id from metadataschemaregistry where short_id='dc') and element = 'description' and qualifier is null) AS metadata_field_id, -introductory_text AS text_value, -null AS text_lang, -0 AS place -FROM collection where not introductory_text is null; - -INSERT INTO metadatavalue (metadata_value_id, resource_id, resource_type_id, metadata_field_id, text_value, text_lang, place) -SELECT -metadatavalue_seq.nextval as metadata_value_id, -collection_id AS resource_id, -3 AS resource_type_id, -(select metadata_field_id from metadatafieldregistry where metadata_schema_id=(select metadata_schema_id from metadataschemaregistry where short_id='dc') and element = 'description' and qualifier = 'abstract') AS metadata_field_id, -short_description AS text_value, -null AS text_lang, -0 AS place -FROM collection where not short_description is null; - -INSERT INTO metadatavalue (metadata_value_id, resource_id, resource_type_id, metadata_field_id, text_value, text_lang, place) -SELECT -metadatavalue_seq.nextval as metadata_value_id, -collection_id AS resource_id, -3 AS resource_type_id, -(select metadata_field_id from metadatafieldregistry where metadata_schema_id=(select metadata_schema_id from metadataschemaregistry where short_id='dc') and element = 'description' and qualifier = 'tableofcontents') AS metadata_field_id, -side_bar_text AS text_value, -null AS text_lang, -0 AS place -FROM collection where not side_bar_text is null; - -INSERT INTO metadatavalue (metadata_value_id, resource_id, resource_type_id, metadata_field_id, text_value, text_lang, place) -SELECT -metadatavalue_seq.nextval as metadata_value_id, -collection_id AS resource_id, -3 AS resource_type_id, -(select metadata_field_id from metadatafieldregistry where metadata_schema_id=(select metadata_schema_id from metadataschemaregistry where short_id='dc') and element = 'rights' and qualifier is null) AS metadata_field_id, -copyright_text AS text_value, -null AS text_lang, -0 AS place -FROM collection where not copyright_text is null; - -INSERT INTO metadatavalue (metadata_value_id, resource_id, resource_type_id, metadata_field_id, text_value, text_lang, place) -SELECT -metadatavalue_seq.nextval as metadata_value_id, -collection_id AS resource_id, -3 AS resource_type_id, -(select metadata_field_id from metadatafieldregistry where metadata_schema_id=(select metadata_schema_id from metadataschemaregistry where short_id='dc') and element = 'title' and qualifier is null) AS metadata_field_id, -name AS text_value, -null AS text_lang, -0 AS place -FROM collection where not name is null; - -INSERT INTO metadatavalue (metadata_value_id, resource_id, resource_type_id, metadata_field_id, text_value, text_lang, place) -SELECT -metadatavalue_seq.nextval as metadata_value_id, -collection_id AS resource_id, -3 AS resource_type_id, -(select metadata_field_id from metadatafieldregistry where metadata_schema_id=(select metadata_schema_id from metadataschemaregistry where short_id='dc') and element = 'provenance' and qualifier is null) AS metadata_field_id, -provenance_description AS text_value, -null AS text_lang, -0 AS place -FROM collection where not provenance_description is null; - -INSERT INTO metadatavalue (metadata_value_id, resource_id, resource_type_id, metadata_field_id, text_value, text_lang, place) -SELECT -metadatavalue_seq.nextval as metadata_value_id, -collection_id AS resource_id, -3 AS resource_type_id, -(select metadata_field_id from metadatafieldregistry where metadata_schema_id=(select metadata_schema_id from metadataschemaregistry where short_id='dc') and element = 'rights' and qualifier = 'license') AS metadata_field_id, -license AS text_value, -null AS text_lang, -0 AS place -FROM collection where not license is null; - -alter table collection drop (introductory_text, short_description, copyright_text, side_bar_text, name, license, provenance_description); - - --- --------- --- bundle --- --------- - -INSERT INTO metadatavalue (metadata_value_id, resource_id, resource_type_id, metadata_field_id, text_value, text_lang, place) -SELECT -metadatavalue_seq.nextval as metadata_value_id, -bundle_id AS resource_id, -1 AS resource_type_id, -(select metadata_field_id from metadatafieldregistry where metadata_schema_id=(select metadata_schema_id from metadataschemaregistry where short_id='dc') and element = 'title' and qualifier is null) AS metadata_field_id, -name AS text_value, -null AS text_lang, -0 AS place -FROM bundle where not name is null; - -alter table bundle drop column name; - - - --- --------- --- bitstream --- --------- - - -INSERT INTO metadatavalue (metadata_value_id, resource_id, resource_type_id, metadata_field_id, text_value, text_lang, place) -SELECT -metadatavalue_seq.nextval as metadata_value_id, -bitstream_id AS resource_id, -0 AS resource_type_id, -(select metadata_field_id from metadatafieldregistry where metadata_schema_id=(select metadata_schema_id from metadataschemaregistry where short_id='dc') and element = 'title' and qualifier is null) AS metadata_field_id, -name AS text_value, -null AS text_lang, -0 AS place -FROM bitstream where not name is null; - -INSERT INTO metadatavalue (metadata_value_id, resource_id, resource_type_id, metadata_field_id, text_value, text_lang, place) -SELECT -metadatavalue_seq.nextval as metadata_value_id, -bitstream_id AS resource_id, -0 AS resource_type_id, -(select metadata_field_id from metadatafieldregistry where metadata_schema_id=(select metadata_schema_id from metadataschemaregistry where short_id='dc') and element = 'description' and qualifier is null) AS metadata_field_id, -description AS text_value, -null AS text_lang, -0 AS place -FROM bitstream where not description is null; - -INSERT INTO metadatavalue (metadata_value_id, resource_id, resource_type_id, metadata_field_id, text_value, text_lang, place) -SELECT -metadatavalue_seq.nextval as metadata_value_id, -bitstream_id AS resource_id, -0 AS resource_type_id, -(select metadata_field_id from metadatafieldregistry where metadata_schema_id=(select metadata_schema_id from metadataschemaregistry where short_id='dc') and element = 'format' and qualifier is null) AS metadata_field_id, -user_format_description AS text_value, -null AS text_lang, -0 AS place -FROM bitstream where not user_format_description is null; - -INSERT INTO metadatavalue (metadata_value_id, resource_id, resource_type_id, metadata_field_id, text_value, text_lang, place) -SELECT -metadatavalue_seq.nextval as metadata_value_id, -bitstream_id AS resource_id, -0 AS resource_type_id, -(select metadata_field_id from metadatafieldregistry where metadata_schema_id=(select metadata_schema_id from metadataschemaregistry where short_id='dc') and element = 'source' and qualifier is null) AS metadata_field_id, -source AS text_value, -null AS text_lang, -0 AS place -FROM bitstream where not source is null; - -alter table bitstream drop (name, description, user_format_description, source); - - --- --------- --- epersongroup --- --------- - -INSERT INTO metadatavalue (metadata_value_id, resource_id, resource_type_id, metadata_field_id, text_value, text_lang, place) -SELECT -metadatavalue_seq.nextval as metadata_value_id, -eperson_group_id AS resource_id, -6 AS resource_type_id, -(select metadata_field_id from metadatafieldregistry where metadata_schema_id=(select metadata_schema_id from metadataschemaregistry where short_id='dc') and element = 'title' and qualifier is null) AS metadata_field_id, -name AS text_value, -null AS text_lang, -0 AS place -FROM epersongroup where not name is null; - -alter table epersongroup drop column name; - - - --- --------- --- eperson --- --------- - -INSERT INTO metadatavalue (metadata_value_id, resource_id, resource_type_id, metadata_field_id, text_value, text_lang, place) -SELECT -metadatavalue_seq.nextval as metadata_value_id, -eperson_id AS resource_id, -7 AS resource_type_id, -(select metadata_field_id from metadatafieldregistry where metadata_schema_id=(select metadata_schema_id from metadataschemaregistry where short_id='eperson') and element = 'firstname' and qualifier is null) AS metadata_field_id, -firstname AS text_value, -null AS text_lang, -0 AS place -FROM eperson where not firstname is null; - -INSERT INTO metadatavalue (metadata_value_id, resource_id, resource_type_id, metadata_field_id, text_value, text_lang, place) -SELECT -metadatavalue_seq.nextval as metadata_value_id, -eperson_id AS resource_id, -7 AS resource_type_id, -(select metadata_field_id from metadatafieldregistry where metadata_schema_id=(select metadata_schema_id from metadataschemaregistry where short_id='eperson') and element = 'lastname' and qualifier is null) AS metadata_field_id, -lastname AS text_value, -null AS text_lang, -0 AS place -FROM eperson where not lastname is null; - -INSERT INTO metadatavalue (metadata_value_id, resource_id, resource_type_id, metadata_field_id, text_value, text_lang, place) -SELECT -metadatavalue_seq.nextval as metadata_value_id, -eperson_id AS resource_id, -7 AS resource_type_id, -(select metadata_field_id from metadatafieldregistry where metadata_schema_id=(select metadata_schema_id from metadataschemaregistry where short_id='eperson') and element = 'phone' and qualifier is null) AS metadata_field_id, -phone AS text_value, -null AS text_lang, -0 AS place -FROM eperson where not phone is null; - - -INSERT INTO metadatavalue (metadata_value_id, resource_id, resource_type_id, metadata_field_id, text_value, text_lang, place) -SELECT -metadatavalue_seq.nextval as metadata_value_id, -eperson_id AS resource_id, -7 AS resource_type_id, -(select metadata_field_id from metadatafieldregistry where metadata_schema_id=(select metadata_schema_id from metadataschemaregistry where short_id='eperson') and element = 'language' and qualifier is null) AS metadata_field_id, -language AS text_value, -null AS text_lang, -0 AS place -FROM eperson where not language is null; - -alter table eperson drop (firstname, lastname, phone, language); - --- --------- --- dcvalue view --- --------- - -drop view dcvalue; - -CREATE VIEW dcvalue AS - SELECT MetadataValue.metadata_value_id AS "dc_value_id", MetadataValue.resource_id, - MetadataValue.metadata_field_id AS "dc_type_id", MetadataValue.text_value, - MetadataValue.text_lang, MetadataValue.place - FROM MetadataValue, MetadataFieldRegistry - WHERE MetadataValue.metadata_field_id = MetadataFieldRegistry.metadata_field_id - AND MetadataFieldRegistry.metadata_schema_id = 1 AND MetadataValue.resource_type_id = 2; diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V5.6_2016.08.23__DS-3097.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V5.6_2016.08.23__DS-3097.sql deleted file mode 100644 index 2e09b807de3b..000000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V5.6_2016.08.23__DS-3097.sql +++ /dev/null @@ -1,24 +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/ --- - ------------------------------------------------------- --- DS-3097 introduced new action id for WITHDRAWN_READ ------------------------------------------------------- - -UPDATE resourcepolicy SET action_id = 12 where action_id = 0 and resource_type_id = 0 and resource_id in ( - SELECT bundle2bitstream.bitstream_id FROM bundle2bitstream - LEFT JOIN item2bundle ON bundle2bitstream.bundle_id = item2bundle.bundle_id - LEFT JOIN item ON item2bundle.item_id = item.item_id - WHERE item.withdrawn = 1 -); - -UPDATE resourcepolicy SET action_id = 12 where action_id = 0 and resource_type_id = 1 and resource_id in ( - SELECT item2bundle.bundle_id FROM item2bundle - LEFT JOIN item ON item2bundle.item_id = item.item_id - WHERE item.withdrawn = 1 -); \ No newline at end of file diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V5.7_2017.04.11__DS-3563_Index_metadatavalue_resource_type_id_column.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V5.7_2017.04.11__DS-3563_Index_metadatavalue_resource_type_id_column.sql deleted file mode 100644 index 9f9836faf471..000000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V5.7_2017.04.11__DS-3563_Index_metadatavalue_resource_type_id_column.sql +++ /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/ --- - ------------------------------------------------------- --- DS-3563 Missing database index on metadatavalue.resource_type_id ------------------------------------------------------- --- Create an index on the metadata value resource_type_id column so that it can be searched efficiently. -declare - index_not_exists EXCEPTION; - PRAGMA EXCEPTION_INIT(index_not_exists, -1418); -begin - - execute immediate 'DROP INDEX metadatavalue_type_id_idx'; - exception - when index_not_exists then null; -end; -/ -CREATE INDEX metadatavalue_type_id_idx ON metadatavalue (resource_type_id); \ No newline at end of file diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2015.03.07__DS-2701_Hibernate_migration.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2015.03.07__DS-2701_Hibernate_migration.sql deleted file mode 100644 index dd857e763df0..000000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2015.03.07__DS-2701_Hibernate_migration.sql +++ /dev/null @@ -1,469 +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/ --- - ------------------------------------------------------- --- DS-2701 Service based API / Hibernate integration ------------------------------------------------------- -DROP VIEW community2item; - -CREATE TABLE dspaceobject -( - uuid RAW(16) NOT NULL PRIMARY KEY -); - -CREATE TABLE site -( - uuid RAW(16) NOT NULL PRIMARY KEY REFERENCES dspaceobject(uuid) -); - -ALTER TABLE eperson ADD uuid RAW(16) DEFAULT SYS_GUID(); -INSERT INTO dspaceobject (uuid) SELECT uuid FROM eperson; -ALTER TABLE eperson ADD FOREIGN KEY (uuid) REFERENCES dspaceobject; -ALTER TABLE eperson MODIFY uuid NOT NULL; -ALTER TABLE eperson ADD CONSTRAINT eperson_id_unique PRIMARY KEY (uuid); -UPDATE eperson SET require_certificate = '0' WHERE require_certificate IS NULL; -UPDATE eperson SET self_registered = '0' WHERE self_registered IS NULL; - - - -UPDATE metadatavalue SET text_value='Administrator' - WHERE resource_type_id=6 AND resource_id=1; -UPDATE metadatavalue SET text_value='Anonymous' - WHERE resource_type_id=6 AND resource_id=0; - -ALTER TABLE epersongroup ADD uuid RAW(16) DEFAULT SYS_GUID(); -INSERT INTO dspaceobject (uuid) SELECT uuid FROM epersongroup; -ALTER TABLE epersongroup ADD FOREIGN KEY (uuid) REFERENCES dspaceobject; -ALTER TABLE epersongroup MODIFY uuid NOT NULL; -ALTER TABLE epersongroup ADD CONSTRAINT epersongroup_id_unique PRIMARY KEY (uuid); - -ALTER TABLE item ADD uuid RAW(16) DEFAULT SYS_GUID(); -INSERT INTO dspaceobject (uuid) SELECT uuid FROM item; -ALTER TABLE item ADD FOREIGN KEY (uuid) REFERENCES dspaceobject; -ALTER TABLE item MODIFY uuid NOT NULL; -ALTER TABLE item ADD CONSTRAINT item_id_unique PRIMARY KEY (uuid); - -ALTER TABLE community ADD uuid RAW(16) DEFAULT SYS_GUID(); -INSERT INTO dspaceobject (uuid) SELECT uuid FROM community; -ALTER TABLE community ADD FOREIGN KEY (uuid) REFERENCES dspaceobject; -ALTER TABLE community MODIFY uuid NOT NULL; -ALTER TABLE community ADD CONSTRAINT community_id_unique PRIMARY KEY (uuid); - - -ALTER TABLE collection ADD uuid RAW(16) DEFAULT SYS_GUID(); -INSERT INTO dspaceobject (uuid) SELECT uuid FROM collection; -ALTER TABLE collection ADD FOREIGN KEY (uuid) REFERENCES dspaceobject; -ALTER TABLE collection MODIFY uuid NOT NULL; -ALTER TABLE collection ADD CONSTRAINT collection_id_unique PRIMARY KEY (uuid); - -ALTER TABLE bundle ADD uuid RAW(16) DEFAULT SYS_GUID(); -INSERT INTO dspaceobject (uuid) SELECT uuid FROM bundle; -ALTER TABLE bundle ADD FOREIGN KEY (uuid) REFERENCES dspaceobject; -ALTER TABLE bundle MODIFY uuid NOT NULL; -ALTER TABLE bundle ADD CONSTRAINT bundle_id_unique PRIMARY KEY (uuid); - -ALTER TABLE bitstream ADD uuid RAW(16) DEFAULT SYS_GUID(); -INSERT INTO dspaceobject (uuid) SELECT uuid FROM bitstream; -ALTER TABLE bitstream ADD FOREIGN KEY (uuid) REFERENCES dspaceobject; -ALTER TABLE bitstream MODIFY uuid NOT NULL; -ALTER TABLE bitstream ADD CONSTRAINT bitstream_id_unique PRIMARY KEY (uuid); -UPDATE bitstream SET sequence_id = -1 WHERE sequence_id IS NULL; -UPDATE bitstream SET size_bytes = -1 WHERE size_bytes IS NULL; -UPDATE bitstream SET deleted = '0' WHERE deleted IS NULL; -UPDATE bitstream SET store_number = -1 WHERE store_number IS NULL; - --- Migrate EPersonGroup2EPerson table -ALTER TABLE EPersonGroup2EPerson RENAME COLUMN eperson_group_id to eperson_group_legacy_id; -ALTER TABLE EPersonGroup2EPerson RENAME COLUMN eperson_id to eperson_legacy_id; -ALTER TABLE EPersonGroup2EPerson ADD eperson_group_id RAW(16) REFERENCES EpersonGroup(uuid); -ALTER TABLE EPersonGroup2EPerson ADD eperson_id RAW(16) REFERENCES Eperson(uuid); -CREATE INDEX EpersonGroup2Eperson_group on EpersonGroup2Eperson(eperson_group_id); -CREATE INDEX EpersonGroup2Eperson_person on EpersonGroup2Eperson(eperson_id); -UPDATE EPersonGroup2EPerson SET eperson_group_id = (SELECT EPersonGroup.uuid FROM EpersonGroup WHERE EPersonGroup2EPerson.eperson_group_legacy_id = EPersonGroup.eperson_group_id); -UPDATE EPersonGroup2EPerson SET eperson_id = (SELECT eperson.uuid FROM eperson WHERE EPersonGroup2EPerson.eperson_legacy_id = eperson.eperson_id); -ALTER TABLE EPersonGroup2EPerson MODIFY eperson_group_id NOT NULL; -ALTER TABLE EPersonGroup2EPerson MODIFY eperson_id NOT NULL; -ALTER TABLE EPersonGroup2EPerson DROP COLUMN eperson_group_legacy_id; -ALTER TABLE EPersonGroup2EPerson DROP COLUMN eperson_legacy_id; -ALTER TABLE epersongroup2eperson DROP COLUMN id; -ALTER TABLE EPersonGroup2EPerson add CONSTRAINT EPersonGroup2EPerson_unique primary key (eperson_group_id,eperson_id); - --- Migrate GROUP2GROUP table -ALTER TABLE Group2Group RENAME COLUMN parent_id to parent_legacy_id; -ALTER TABLE Group2Group RENAME COLUMN child_id to child_legacy_id; -ALTER TABLE Group2Group ADD parent_id RAW(16) REFERENCES EpersonGroup(uuid); -ALTER TABLE Group2Group ADD child_id RAW(16) REFERENCES EpersonGroup(uuid); -CREATE INDEX Group2Group_parent on Group2Group(parent_id); -CREATE INDEX Group2Group_child on Group2Group(child_id); -UPDATE Group2Group SET parent_id = (SELECT EPersonGroup.uuid FROM EpersonGroup WHERE Group2Group.parent_legacy_id = EPersonGroup.eperson_group_id); -UPDATE Group2Group SET child_id = (SELECT EpersonGroup.uuid FROM EpersonGroup WHERE Group2Group.child_legacy_id = EpersonGroup.eperson_group_id); -ALTER TABLE Group2Group MODIFY parent_id NOT NULL; -ALTER TABLE Group2Group MODIFY child_id NOT NULL; -ALTER TABLE Group2Group DROP COLUMN parent_legacy_id; -ALTER TABLE Group2Group DROP COLUMN child_legacy_id; -ALTER TABLE Group2Group DROP COLUMN id; -ALTER TABLE Group2Group add CONSTRAINT Group2Group_unique primary key (parent_id,child_id); - --- Migrate collection2item -ALTER TABLE Collection2Item RENAME COLUMN collection_id to collection_legacy_id; -ALTER TABLE Collection2Item RENAME COLUMN item_id to item_legacy_id; -ALTER TABLE Collection2Item ADD collection_id RAW(16) REFERENCES Collection(uuid); -ALTER TABLE Collection2Item ADD item_id RAW(16) REFERENCES Item(uuid); -CREATE INDEX Collecion2Item_collection on Collection2Item(collection_id); -CREATE INDEX Collecion2Item_item on Collection2Item(item_id); -UPDATE Collection2Item SET collection_id = (SELECT Collection.uuid FROM Collection WHERE Collection2Item.collection_legacy_id = Collection.collection_id); -UPDATE Collection2Item SET item_id = (SELECT Item.uuid FROM Item WHERE Collection2Item.item_legacy_id = Item.item_id); -ALTER TABLE Collection2Item MODIFY collection_id NOT NULL; -ALTER TABLE Collection2Item MODIFY item_id NOT NULL; -ALTER TABLE Collection2Item DROP COLUMN collection_legacy_id; -ALTER TABLE Collection2Item DROP COLUMN item_legacy_id; -ALTER TABLE Collection2Item DROP COLUMN id; --- Magic query that will delete all duplicate collection item_id references from the database (if we don't do this the primary key creation will fail) -DELETE FROM collection2item WHERE rowid NOT IN (SELECT MIN(rowid) FROM collection2item GROUP BY collection_id,item_id); -ALTER TABLE Collection2Item add CONSTRAINT collection2item_unique primary key (collection_id,item_id); - --- Migrate Community2Community -ALTER TABLE Community2Community RENAME COLUMN parent_comm_id to parent_legacy_id; -ALTER TABLE Community2Community RENAME COLUMN child_comm_id to child_legacy_id; -ALTER TABLE Community2Community ADD parent_comm_id RAW(16) REFERENCES Community(uuid); -ALTER TABLE Community2Community ADD child_comm_id RAW(16) REFERENCES Community(uuid); -CREATE INDEX Community2Community_parent on Community2Community(parent_comm_id); -CREATE INDEX Community2Community_child on Community2Community(child_comm_id); -UPDATE Community2Community SET parent_comm_id = (SELECT Community.uuid FROM Community WHERE Community2Community.parent_legacy_id = Community.community_id); -UPDATE Community2Community SET child_comm_id = (SELECT Community.uuid FROM Community WHERE Community2Community.child_legacy_id = Community.community_id); -ALTER TABLE Community2Community MODIFY parent_comm_id NOT NULL; -ALTER TABLE Community2Community MODIFY child_comm_id NOT NULL; -ALTER TABLE Community2Community DROP COLUMN parent_legacy_id; -ALTER TABLE Community2Community DROP COLUMN child_legacy_id; -ALTER TABLE Community2Community DROP COLUMN id; -ALTER TABLE Community2Community add CONSTRAINT Community2Community_unique primary key (parent_comm_id,child_comm_id); - --- Migrate community2collection -ALTER TABLE community2collection RENAME COLUMN collection_id to collection_legacy_id; -ALTER TABLE community2collection RENAME COLUMN community_id to community_legacy_id; -ALTER TABLE community2collection ADD collection_id RAW(16) REFERENCES Collection(uuid); -ALTER TABLE community2collection ADD community_id RAW(16) REFERENCES Community(uuid); -CREATE INDEX community2collection_collectio on community2collection(collection_id); -CREATE INDEX community2collection_community on community2collection(community_id); -UPDATE community2collection SET collection_id = (SELECT Collection.uuid FROM Collection WHERE community2collection.collection_legacy_id = Collection.collection_id); -UPDATE community2collection SET community_id = (SELECT Community.uuid FROM Community WHERE community2collection.community_legacy_id = Community.community_id); -ALTER TABLE community2collection MODIFY collection_id NOT NULL; -ALTER TABLE community2collection MODIFY community_id NOT NULL; -ALTER TABLE community2collection DROP COLUMN collection_legacy_id; -ALTER TABLE community2collection DROP COLUMN community_legacy_id; -ALTER TABLE community2collection DROP COLUMN id; -ALTER TABLE community2collection add CONSTRAINT community2collection_unique primary key (collection_id,community_id); - - --- Migrate Group2GroupCache table -ALTER TABLE Group2GroupCache RENAME COLUMN parent_id to parent_legacy_id; -ALTER TABLE Group2GroupCache RENAME COLUMN child_id to child_legacy_id; -ALTER TABLE Group2GroupCache ADD parent_id RAW(16) REFERENCES EpersonGroup(uuid); -ALTER TABLE Group2GroupCache ADD child_id RAW(16) REFERENCES EpersonGroup(uuid); -CREATE INDEX Group2GroupCache_parent on Group2GroupCache(parent_id); -CREATE INDEX Group2GroupCache_child on Group2GroupCache(child_id); -UPDATE Group2GroupCache SET parent_id = (SELECT EPersonGroup.uuid FROM EpersonGroup WHERE Group2GroupCache.parent_legacy_id = EPersonGroup.eperson_group_id); -UPDATE Group2GroupCache SET child_id = (SELECT EpersonGroup.uuid FROM EpersonGroup WHERE Group2GroupCache.child_legacy_id = EpersonGroup.eperson_group_id); -ALTER TABLE Group2GroupCache MODIFY parent_id NOT NULL; -ALTER TABLE Group2GroupCache MODIFY child_id NOT NULL; -ALTER TABLE Group2GroupCache DROP COLUMN parent_legacy_id; -ALTER TABLE Group2GroupCache DROP COLUMN child_legacy_id; -ALTER TABLE Group2GroupCache DROP COLUMN id; -ALTER TABLE Group2GroupCache add CONSTRAINT Group2GroupCache_unique primary key (parent_id,child_id); - --- Migrate Item2Bundle -ALTER TABLE item2bundle RENAME COLUMN bundle_id to bundle_legacy_id; -ALTER TABLE item2bundle RENAME COLUMN item_id to item_legacy_id; -ALTER TABLE item2bundle ADD bundle_id RAW(16) REFERENCES Bundle(uuid); -ALTER TABLE item2bundle ADD item_id RAW(16) REFERENCES Item(uuid); -CREATE INDEX item2bundle_bundle on item2bundle(bundle_id); -CREATE INDEX item2bundle_item on item2bundle(item_id); -UPDATE item2bundle SET bundle_id = (SELECT Bundle.uuid FROM Bundle WHERE item2bundle.bundle_legacy_id = Bundle.bundle_id); -UPDATE item2bundle SET item_id = (SELECT Item.uuid FROM Item WHERE item2bundle.item_legacy_id = Item.item_id); -ALTER TABLE item2bundle MODIFY bundle_id NOT NULL; -ALTER TABLE item2bundle MODIFY item_id NOT NULL; -ALTER TABLE item2bundle DROP COLUMN bundle_legacy_id; -ALTER TABLE item2bundle DROP COLUMN item_legacy_id; -ALTER TABLE item2bundle DROP COLUMN id; -ALTER TABLE item2bundle add CONSTRAINT item2bundle_unique primary key (bundle_id,item_id); - ---Migrate Bundle2Bitsteam -ALTER TABLE bundle2bitstream RENAME COLUMN bundle_id to bundle_legacy_id; -ALTER TABLE bundle2bitstream RENAME COLUMN bitstream_id to bitstream_legacy_id; -ALTER TABLE bundle2bitstream ADD bundle_id RAW(16) REFERENCES Bundle(uuid); -ALTER TABLE bundle2bitstream ADD bitstream_id RAW(16) REFERENCES Bitstream(uuid); -CREATE INDEX bundle2bitstream_bundle on bundle2bitstream(bundle_id); -CREATE INDEX bundle2bitstream_bitstream on bundle2bitstream(bitstream_id); -UPDATE bundle2bitstream SET bundle_id = (SELECT bundle.uuid FROM bundle WHERE bundle2bitstream.bundle_legacy_id = bundle.bundle_id); -UPDATE bundle2bitstream SET bitstream_id = (SELECT bitstream.uuid FROM bitstream WHERE bundle2bitstream.bitstream_legacy_id = bitstream.bitstream_id); -ALTER TABLE bundle2bitstream RENAME COLUMN bitstream_order to bitstream_order_legacy; -ALTER TABLE bundle2bitstream ADD bitstream_order INTEGER; -MERGE INTO bundle2bitstream dst -USING ( SELECT ROWID AS r_id - , ROW_NUMBER () OVER ( PARTITION BY bundle_id - ORDER BY bitstream_order_legacy, bitstream_id - ) AS new_order - FROM bundle2bitstream - ) src -ON (dst.ROWID = src.r_id) -WHEN MATCHED THEN UPDATE -SET dst.bitstream_order = (src.new_order-1) -; -ALTER TABLE bundle2bitstream MODIFY bundle_id NOT NULL; -ALTER TABLE bundle2bitstream MODIFY bitstream_id NOT NULL; -ALTER TABLE bundle2bitstream DROP COLUMN bundle_legacy_id; -ALTER TABLE bundle2bitstream DROP COLUMN bitstream_legacy_id; -ALTER TABLE bundle2bitstream DROP COLUMN id; -ALTER TABLE bundle2bitstream add CONSTRAINT bundle2bitstream_unique primary key (bitstream_id,bundle_id,bitstream_order); - - --- Migrate item -ALTER TABLE item RENAME COLUMN submitter_id to submitter_id_legacy_id; -ALTER TABLE item ADD submitter_id RAW(16) REFERENCES EPerson(uuid); -CREATE INDEX item_submitter on item(submitter_id); -UPDATE item SET submitter_id = (SELECT eperson.uuid FROM eperson WHERE item.submitter_id_legacy_id = eperson.eperson_id); -ALTER TABLE item DROP COLUMN submitter_id_legacy_id; - -ALTER TABLE item RENAME COLUMN owning_collection to owning_collection_legacy; -ALTER TABLE item ADD owning_collection RAW(16) REFERENCES Collection(uuid); -CREATE INDEX item_collection on item(owning_collection); -UPDATE item SET owning_collection = (SELECT Collection.uuid FROM Collection WHERE item.owning_collection_legacy = collection.collection_id); -ALTER TABLE item DROP COLUMN owning_collection_legacy; - -UPDATE item SET in_archive = '0' WHERE in_archive IS NULL; -UPDATE item SET discoverable = '0' WHERE discoverable IS NULL; -UPDATE item SET withdrawn = '0' WHERE withdrawn IS NULL; - --- Migrate bundle -ALTER TABLE bundle RENAME COLUMN primary_bitstream_id to primary_bitstream_legacy_id; -ALTER TABLE bundle ADD primary_bitstream_id RAW(16) REFERENCES Bitstream(uuid); -CREATE INDEX bundle_primary on bundle(primary_bitstream_id); -UPDATE bundle SET primary_bitstream_id = (SELECT Bitstream.uuid FROM Bitstream WHERE bundle.primary_bitstream_legacy_id = Bitstream.bitstream_id); -ALTER TABLE bundle DROP COLUMN primary_bitstream_legacy_id; - - --- Migrate community references -ALTER TABLE Community RENAME COLUMN admin to admin_legacy; -ALTER TABLE Community ADD admin RAW(16) REFERENCES EPersonGroup(uuid); -CREATE INDEX Community_admin on Community(admin); -UPDATE Community SET admin = (SELECT EPersonGroup.uuid FROM EPersonGroup WHERE Community.admin_legacy = EPersonGroup.eperson_group_id); -ALTER TABLE Community DROP COLUMN admin_legacy; - -ALTER TABLE Community RENAME COLUMN logo_bitstream_id to logo_bitstream_legacy_id; -ALTER TABLE Community ADD logo_bitstream_id RAW(16) REFERENCES Bitstream(uuid); -CREATE INDEX Community_bitstream on Community(logo_bitstream_id); -UPDATE Community SET logo_bitstream_id = (SELECT Bitstream.uuid FROM Bitstream WHERE Community.logo_bitstream_legacy_id = Bitstream.bitstream_id); -ALTER TABLE Community DROP COLUMN logo_bitstream_legacy_id; - - ---Migrate Collection references -ALTER TABLE Collection RENAME COLUMN workflow_step_1 to workflow_step_1_legacy; -ALTER TABLE Collection RENAME COLUMN workflow_step_2 to workflow_step_2_legacy; -ALTER TABLE Collection RENAME COLUMN workflow_step_3 to workflow_step_3_legacy; -ALTER TABLE Collection RENAME COLUMN submitter to submitter_legacy; -ALTER TABLE Collection RENAME COLUMN template_item_id to template_item_legacy_id; -ALTER TABLE Collection RENAME COLUMN logo_bitstream_id to logo_bitstream_legacy_id; -ALTER TABLE Collection RENAME COLUMN admin to admin_legacy; -ALTER TABLE Collection ADD workflow_step_1 RAW(16) REFERENCES EPersonGroup(uuid); -ALTER TABLE Collection ADD workflow_step_2 RAW(16) REFERENCES EPersonGroup(uuid); -ALTER TABLE Collection ADD workflow_step_3 RAW(16) REFERENCES EPersonGroup(uuid); -ALTER TABLE Collection ADD submitter RAW(16) REFERENCES EPersonGroup(uuid); -ALTER TABLE Collection ADD template_item_id RAW(16); -ALTER TABLE Collection ADD logo_bitstream_id RAW(16); -ALTER TABLE Collection ADD admin RAW(16) REFERENCES EPersonGroup(uuid); -CREATE INDEX Collection_workflow1 on Collection(workflow_step_1); -CREATE INDEX Collection_workflow2 on Collection(workflow_step_2); -CREATE INDEX Collection_workflow3 on Collection(workflow_step_3); -CREATE INDEX Collection_submitter on Collection(submitter); -CREATE INDEX Collection_template on Collection(template_item_id); -CREATE INDEX Collection_bitstream on Collection(logo_bitstream_id); -UPDATE Collection SET workflow_step_1 = (SELECT EPersonGroup.uuid FROM EPersonGroup WHERE Collection.workflow_step_1_legacy = EPersonGroup.eperson_group_id); -UPDATE Collection SET workflow_step_2 = (SELECT EPersonGroup.uuid FROM EPersonGroup WHERE Collection.workflow_step_2_legacy = EPersonGroup.eperson_group_id); -UPDATE Collection SET workflow_step_3 = (SELECT EPersonGroup.uuid FROM EPersonGroup WHERE Collection.workflow_step_3_legacy = EPersonGroup.eperson_group_id); -UPDATE Collection SET submitter = (SELECT EPersonGroup.uuid FROM EPersonGroup WHERE Collection.submitter_legacy = EPersonGroup.eperson_group_id); -UPDATE Collection SET template_item_id = (SELECT Item.uuid FROM Item WHERE Collection.template_item_legacy_id = Item.item_id); -UPDATE Collection SET logo_bitstream_id = (SELECT Bitstream.uuid FROM Bitstream WHERE Collection.logo_bitstream_legacy_id = Bitstream.bitstream_id); -UPDATE Collection SET admin = (SELECT EPersonGroup.uuid FROM EPersonGroup WHERE Collection.admin_legacy = EPersonGroup.eperson_group_id); -ALTER TABLE Collection DROP COLUMN workflow_step_1_legacy; -ALTER TABLE Collection DROP COLUMN workflow_step_2_legacy; -ALTER TABLE Collection DROP COLUMN workflow_step_3_legacy; -ALTER TABLE Collection DROP COLUMN submitter_legacy; -ALTER TABLE Collection DROP COLUMN template_item_legacy_id; -ALTER TABLE Collection DROP COLUMN logo_bitstream_legacy_id; -ALTER TABLE Collection DROP COLUMN admin_legacy; - - --- Migrate resource policy references -ALTER TABLE ResourcePolicy RENAME COLUMN eperson_id to eperson_id_legacy_id; -ALTER TABLE ResourcePolicy ADD eperson_id RAW(16) REFERENCES EPerson(uuid); -CREATE INDEX resourcepolicy_person on resourcepolicy(eperson_id); -UPDATE ResourcePolicy SET eperson_id = (SELECT eperson.uuid FROM eperson WHERE ResourcePolicy.eperson_id_legacy_id = eperson.eperson_id); -ALTER TABLE ResourcePolicy DROP COLUMN eperson_id_legacy_id; - -ALTER TABLE ResourcePolicy RENAME COLUMN epersongroup_id to epersongroup_id_legacy_id; -ALTER TABLE ResourcePolicy ADD epersongroup_id RAW(16) REFERENCES EPersonGroup(uuid); -CREATE INDEX resourcepolicy_group on resourcepolicy(epersongroup_id); -UPDATE ResourcePolicy SET epersongroup_id = (SELECT epersongroup.uuid FROM epersongroup WHERE ResourcePolicy.epersongroup_id_legacy_id = epersongroup.eperson_group_id); -ALTER TABLE ResourcePolicy DROP COLUMN epersongroup_id_legacy_id; - -ALTER TABLE ResourcePolicy ADD dspace_object RAW(16) REFERENCES dspaceobject(uuid); -CREATE INDEX resourcepolicy_object on resourcepolicy(dspace_object); -UPDATE ResourcePolicy SET dspace_object = (SELECT eperson.uuid FROM eperson WHERE ResourcePolicy.resource_id = eperson.eperson_id AND ResourcePolicy.resource_type_id = 7) WHERE ResourcePolicy.resource_type_id = 7; -UPDATE ResourcePolicy SET dspace_object = (SELECT epersongroup.uuid FROM epersongroup WHERE ResourcePolicy.resource_id = epersongroup.eperson_group_id AND ResourcePolicy.resource_type_id = 6) WHERE ResourcePolicy.resource_type_id = 6; -UPDATE ResourcePolicy SET dspace_object = (SELECT community.uuid FROM community WHERE ResourcePolicy.resource_id = community.community_id AND ResourcePolicy.resource_type_id = 4) WHERE ResourcePolicy.resource_type_id = 4; -UPDATE ResourcePolicy SET dspace_object = (SELECT collection.uuid FROM collection WHERE ResourcePolicy.resource_id = collection.collection_id AND ResourcePolicy.resource_type_id = 3) WHERE ResourcePolicy.resource_type_id = 3; -UPDATE ResourcePolicy SET dspace_object = (SELECT item.uuid FROM item WHERE ResourcePolicy.resource_id = item.item_id AND ResourcePolicy.resource_type_id = 2) WHERE ResourcePolicy.resource_type_id = 2; -UPDATE ResourcePolicy SET dspace_object = (SELECT bundle.uuid FROM bundle WHERE ResourcePolicy.resource_id = bundle.bundle_id AND ResourcePolicy.resource_type_id = 1) WHERE ResourcePolicy.resource_type_id = 1; -UPDATE ResourcePolicy SET dspace_object = (SELECT bitstream.uuid FROM bitstream WHERE ResourcePolicy.resource_id = bitstream.bitstream_id AND ResourcePolicy.resource_type_id = 0) WHERE ResourcePolicy.resource_type_id = 0; -UPDATE resourcepolicy SET resource_type_id = -1 WHERE resource_type_id IS NULL; -UPDATE resourcepolicy SET action_id = -1 WHERE action_id IS NULL; - - --- Migrate Subscription -ALTER TABLE Subscription RENAME COLUMN eperson_id to eperson_legacy_id; -ALTER TABLE Subscription ADD eperson_id RAW(16) REFERENCES EPerson(uuid); -CREATE INDEX Subscription_person on Subscription(eperson_id); -UPDATE Subscription SET eperson_id = (SELECT eperson.uuid FROM eperson WHERE Subscription.eperson_legacy_id = eperson.eperson_id); -ALTER TABLE Subscription DROP COLUMN eperson_legacy_id; - -ALTER TABLE Subscription RENAME COLUMN collection_id to collection_legacy_id; -ALTER TABLE Subscription ADD collection_id RAW(16) REFERENCES Collection(uuid); -CREATE INDEX Subscription_collection on Subscription(collection_id); -UPDATE Subscription SET collection_id = (SELECT collection.uuid FROM collection WHERE Subscription.collection_legacy_id = collection.collection_id); -ALTER TABLE Subscription DROP COLUMN collection_legacy_id; - - --- Migrate versionitem -ALTER TABLE versionitem RENAME COLUMN eperson_id to eperson_legacy_id; -ALTER TABLE versionitem ADD eperson_id RAW(16) REFERENCES EPerson(uuid); -CREATE INDEX versionitem_person on versionitem(eperson_id); -UPDATE versionitem SET eperson_id = (SELECT eperson.uuid FROM eperson WHERE versionitem.eperson_legacy_id = eperson.eperson_id); -ALTER TABLE versionitem DROP COLUMN eperson_legacy_id; - -ALTER TABLE versionitem RENAME COLUMN item_id to item_legacy_id; -ALTER TABLE versionitem ADD item_id RAW(16) REFERENCES Item(uuid); -CREATE INDEX versionitem_item on versionitem(item_id); -UPDATE versionitem SET item_id = (SELECT item.uuid FROM item WHERE versionitem.item_legacy_id = item.item_id); -ALTER TABLE versionitem DROP COLUMN item_legacy_id; -UPDATE versionitem SET version_number = -1 WHERE version_number IS NULL; - --- Migrate handle table -ALTER TABLE handle RENAME COLUMN resource_id to resource_legacy_id; -ALTER TABLE handle ADD resource_id RAW(16) REFERENCES dspaceobject(uuid); -CREATE INDEX handle_object on handle(resource_id); -UPDATE handle SET resource_id = (SELECT community.uuid FROM community WHERE handle.resource_legacy_id = community.community_id AND handle.resource_type_id = 4); -UPDATE handle SET resource_id = (SELECT collection.uuid FROM collection WHERE handle.resource_legacy_id = collection.collection_id AND handle.resource_type_id = 3); -UPDATE handle SET resource_id = (SELECT item.uuid FROM item WHERE handle.resource_legacy_id = item.item_id AND handle.resource_type_id = 2); - --- Migrate metadata value table -DROP VIEW dcvalue; - -ALTER TABLE metadatavalue ADD dspace_object_id RAW(16) REFERENCES dspaceobject(uuid); --- CREATE INDEX metadatavalue_field on metadatavalue(metadata_field_id); -CREATE INDEX metadatavalue_object on metadatavalue(dspace_object_id); -CREATE INDEX metadatavalue_field_object on metadatavalue(metadata_field_id, dspace_object_id); -UPDATE metadatavalue SET dspace_object_id = (SELECT eperson.uuid FROM eperson WHERE metadatavalue.resource_id = eperson.eperson_id AND metadatavalue.resource_type_id = 7) WHERE metadatavalue.resource_type_id= 7; -UPDATE metadatavalue SET dspace_object_id = (SELECT epersongroup.uuid FROM epersongroup WHERE metadatavalue.resource_id = epersongroup.eperson_group_id AND metadatavalue.resource_type_id = 6) WHERE metadatavalue.resource_type_id= 6; -UPDATE metadatavalue SET dspace_object_id = (SELECT community.uuid FROM community WHERE metadatavalue.resource_id = community.community_id AND metadatavalue.resource_type_id = 4) WHERE metadatavalue.resource_type_id= 4; -UPDATE metadatavalue SET dspace_object_id = (SELECT collection.uuid FROM collection WHERE metadatavalue.resource_id = collection.collection_id AND metadatavalue.resource_type_id = 3) WHERE metadatavalue.resource_type_id= 3; -UPDATE metadatavalue SET dspace_object_id = (SELECT item.uuid FROM item WHERE metadatavalue.resource_id = item.item_id AND metadatavalue.resource_type_id = 2) WHERE metadatavalue.resource_type_id= 2; -UPDATE metadatavalue SET dspace_object_id = (SELECT bundle.uuid FROM bundle WHERE metadatavalue.resource_id = bundle.bundle_id AND metadatavalue.resource_type_id = 1) WHERE metadatavalue.resource_type_id= 1; -UPDATE metadatavalue SET dspace_object_id = (SELECT bitstream.uuid FROM bitstream WHERE metadatavalue.resource_id = bitstream.bitstream_id AND metadatavalue.resource_type_id = 0) WHERE metadatavalue.resource_type_id= 0; -DROP INDEX metadatavalue_item_idx; -DROP INDEX metadatavalue_item_idx2; -ALTER TABLE metadatavalue DROP COLUMN resource_id; -ALTER TABLE metadatavalue DROP COLUMN resource_type_id; -UPDATE MetadataValue SET confidence = -1 WHERE confidence IS NULL; -UPDATE metadatavalue SET place = -1 WHERE place IS NULL; - --- Alter harvested item -ALTER TABLE harvested_item RENAME COLUMN item_id to item_legacy_id; -ALTER TABLE harvested_item ADD item_id RAW(16) REFERENCES item(uuid); -CREATE INDEX harvested_item_item on harvested_item(item_id); -UPDATE harvested_item SET item_id = (SELECT item.uuid FROM item WHERE harvested_item.item_legacy_id = item.item_id); -ALTER TABLE harvested_item DROP COLUMN item_legacy_id; - --- Alter harvested collection -ALTER TABLE harvested_collection RENAME COLUMN collection_id to collection_legacy_id; -ALTER TABLE harvested_collection ADD collection_id RAW(16) REFERENCES Collection(uuid); -CREATE INDEX harvested_collection_collectio on harvested_collection(collection_id); -UPDATE harvested_collection SET collection_id = (SELECT collection.uuid FROM collection WHERE harvested_collection.collection_legacy_id = collection.collection_id); -ALTER TABLE harvested_collection DROP COLUMN collection_legacy_id; - -UPDATE harvested_collection SET harvest_type = -1 WHERE harvest_type IS NULL; -UPDATE harvested_collection SET harvest_status = -1 WHERE harvest_status IS NULL; - - ---Alter workspaceitem -ALTER TABLE workspaceitem RENAME COLUMN item_id to item_legacy_id; -ALTER TABLE workspaceitem ADD item_id RAW(16) REFERENCES Item(uuid); -CREATE INDEX workspaceitem_item on workspaceitem(item_id); -UPDATE workspaceitem SET item_id = (SELECT item.uuid FROM item WHERE workspaceitem.item_legacy_id = item.item_id); -ALTER TABLE workspaceitem DROP COLUMN item_legacy_id; - -ALTER TABLE workspaceitem RENAME COLUMN collection_id to collection_legacy_id; -ALTER TABLE workspaceitem ADD collection_id RAW(16) REFERENCES Collection(uuid); -CREATE INDEX workspaceitem_coll on workspaceitem(collection_id); -UPDATE workspaceitem SET collection_id = (SELECT collection.uuid FROM collection WHERE workspaceitem.collection_legacy_id = collection.collection_id); -ALTER TABLE workspaceitem DROP COLUMN collection_legacy_id; - -UPDATE workspaceitem SET multiple_titles = '0' WHERE multiple_titles IS NULL; -UPDATE workspaceitem SET published_before = '0' WHERE published_before IS NULL; -UPDATE workspaceitem SET multiple_files = '0' WHERE multiple_files IS NULL; -UPDATE workspaceitem SET stage_reached = -1 WHERE stage_reached IS NULL; -UPDATE workspaceitem SET page_reached = -1 WHERE page_reached IS NULL; - ---Alter epersongroup2workspaceitem -ALTER TABLE epersongroup2workspaceitem RENAME COLUMN eperson_group_id to eperson_group_legacy_id; -ALTER TABLE epersongroup2workspaceitem ADD eperson_group_id RAW(16) REFERENCES epersongroup(uuid); -CREATE INDEX epersongroup2workspaceitem_gro on epersongroup2workspaceitem(eperson_group_id); -UPDATE epersongroup2workspaceitem SET eperson_group_id = (SELECT epersongroup.uuid FROM epersongroup WHERE epersongroup2workspaceitem.eperson_group_legacy_id = epersongroup.eperson_group_id); -ALTER TABLE epersongroup2workspaceitem DROP COLUMN eperson_group_legacy_id; - -ALTER TABLE epersongroup2workspaceitem DROP COLUMN id; -ALTER TABLE epersongroup2workspaceitem MODIFY workspace_item_id NOT NULL; -ALTER TABLE epersongroup2workspaceitem MODIFY eperson_group_id NOT NULL; -ALTER TABLE epersongroup2workspaceitem add CONSTRAINT epersongroup2wsitem_unqiue primary key (workspace_item_id,eperson_group_id); - ---Alter most_recent_checksum -ALTER TABLE most_recent_checksum RENAME COLUMN bitstream_id to bitstream_legacy_id; -ALTER TABLE most_recent_checksum ADD bitstream_id RAW(16) REFERENCES Bitstream(uuid); -CREATE INDEX most_recent_checksum_bitstream on most_recent_checksum(bitstream_id); -UPDATE most_recent_checksum SET bitstream_id = (SELECT Bitstream.uuid FROM Bitstream WHERE most_recent_checksum.bitstream_legacy_id = Bitstream.bitstream_id); -ALTER TABLE most_recent_checksum DROP COLUMN bitstream_legacy_id; - -UPDATE most_recent_checksum SET to_be_processed = '0' WHERE to_be_processed IS NULL; -UPDATE most_recent_checksum SET matched_prev_checksum = '0' WHERE matched_prev_checksum IS NULL; - ---Alter checksum_history -ALTER TABLE checksum_history RENAME COLUMN bitstream_id to bitstream_legacy_id; -ALTER TABLE checksum_history ADD bitstream_id RAW(16) REFERENCES Bitstream(uuid); -CREATE INDEX checksum_history_bitstream on checksum_history(bitstream_id); -UPDATE checksum_history SET bitstream_id = (SELECT Bitstream.uuid FROM Bitstream WHERE checksum_history.bitstream_legacy_id = Bitstream.bitstream_id); -ALTER TABLE checksum_history DROP COLUMN bitstream_legacy_id; - -RENAME checksum_history_seq TO checksum_history_check_id_seq; - ---Alter table doi -ALTER TABLE doi ADD dspace_object RAW(16) REFERENCES dspaceobject(uuid); -CREATE INDEX doi_object on doi(dspace_object); -UPDATE doi SET dspace_object = (SELECT community.uuid FROM community WHERE doi.resource_id = community.community_id AND doi.resource_type_id = 4) WHERE doi.resource_type_id = 4; -UPDATE doi SET dspace_object = (SELECT collection.uuid FROM collection WHERE doi.resource_id = collection.collection_id AND doi.resource_type_id = 3) WHERE doi.resource_type_id = 3; -UPDATE doi SET dspace_object = (SELECT item.uuid FROM item WHERE doi.resource_id = item.item_id AND doi.resource_type_id = 2) WHERE doi.resource_type_id = 2; -UPDATE doi SET dspace_object = (SELECT bundle.uuid FROM bundle WHERE doi.resource_id = bundle.bundle_id AND doi.resource_type_id = 1) WHERE doi.resource_type_id = 1; -UPDATE doi SET dspace_object = (SELECT bitstream.uuid FROM bitstream WHERE doi.resource_id = bitstream.bitstream_id AND doi.resource_type_id = 0) WHERE doi.resource_type_id = 0; - ---Update table bitstreamformatregistry -UPDATE bitstreamformatregistry SET support_level = -1 WHERE support_level IS NULL; - ---Update table requestitem -UPDATE requestitem SET allfiles = '0' WHERE allfiles IS NULL; -UPDATE requestitem SET accept_request = '0' WHERE accept_request IS NULL; - ---Update table webapp -UPDATE webapp SET isui = -1 WHERE isui IS NULL; diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2015_03_06_01__DS_3378_lost_oracle_indexes.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2015_03_06_01__DS_3378_lost_oracle_indexes.sql deleted file mode 100644 index 8f1a7ad157a2..000000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2015_03_06_01__DS_3378_lost_oracle_indexes.sql +++ /dev/null @@ -1,18 +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/ --- - ------------------------------------------------------- --- DS_3378 Lost oracle indexes ------------------------------------------------------- -CREATE UNIQUE INDEX eperson_eperson on eperson(eperson_id); -CREATE UNIQUE INDEX epersongroup_eperson_group on epersongroup(eperson_group_id); -CREATE UNIQUE INDEX community_community on community(community_id); -CREATE UNIQUE INDEX collection_collection on collection(collection_id); -CREATE UNIQUE INDEX item_item on item(item_id); -CREATE UNIQUE INDEX bundle_bundle on bundle(bundle_id); -CREATE UNIQUE INDEX bitstream_bitstream on bitstream(bitstream_id); diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2016.01.03__DS-3024.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2016.01.03__DS-3024.sql deleted file mode 100644 index 8ad6f7fcd247..000000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2016.01.03__DS-3024.sql +++ /dev/null @@ -1,25 +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/ --- - ------------------------------------------------------- --- DS-3024 Invent "permanent" groups ------------------------------------------------------- - -ALTER TABLE epersongroup - ADD (permanent NUMBER(1) DEFAULT 0); -UPDATE epersongroup SET permanent = 1 - WHERE uuid IN ( - SELECT dspace_object_id - FROM metadataschemaregistry s - JOIN metadatafieldregistry f USING (metadata_schema_id) - JOIN metadatavalue v USING (metadata_field_id) - WHERE s.short_id = 'dc' - AND f.element = 'title' - AND f.qualifier IS NULL - AND dbms_lob.compare(v.text_value, 'Administrator') = 0 OR dbms_lob.compare(v.text_value,'Anonymous') = 0 - ); diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2016.02.25__DS-3004-slow-searching-as-admin.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2016.02.25__DS-3004-slow-searching-as-admin.sql deleted file mode 100644 index 18cb4a50841d..000000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2016.02.25__DS-3004-slow-searching-as-admin.sql +++ /dev/null @@ -1,30 +0,0 @@ --- --- The contents of this file are subject to the license and copyright --- detailed in the LICENSE and NOTICE files at the root of the source --- tree and available online at --- --- http://www.dspace.org/license/ --- - ---------------------------------------------------------------- --- DS-3024 extremely slow searching when logged in as admin ---------------------------------------------------------------- --- This script will put the group name on the epersongroup --- record itself for performance reasons. It will also make --- sure that a group name is unique (so that for example no two --- Administrator groups can be created). ---------------------------------------------------------------- - -ALTER TABLE epersongroup -ADD name VARCHAR2(250); - -CREATE UNIQUE INDEX epersongroup_unique_idx_name on epersongroup(name); - -UPDATE epersongroup -SET name = -(SELECT text_value - FROM metadatavalue v - JOIN metadatafieldregistry field on v.metadata_field_id = field.metadata_field_id - JOIN metadataschemaregistry s ON field.metadata_schema_id = s.metadata_schema_id - WHERE s.short_id = 'dc' AND element = 'title' AND qualifier IS NULL - AND v.dspace_object_id = epersongroup.uuid); \ No newline at end of file diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2016.04.01__DS-1955_Increase_embargo_reason.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2016.04.01__DS-1955_Increase_embargo_reason.sql deleted file mode 100644 index e0a103749c2b..000000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2016.04.01__DS-1955_Increase_embargo_reason.sql +++ /dev/null @@ -1,25 +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/ --- - --- =============================================================== --- WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING --- --- DO NOT MANUALLY RUN THIS DATABASE MIGRATION. IT WILL BE EXECUTED --- AUTOMATICALLY (IF NEEDED) BY "FLYWAY" WHEN YOU STARTUP DSPACE. --- http://flywaydb.org/ --- =============================================================== - ------------------------------------------------------- --- DS-1955 resize rpdescription for embargo reason ------------------------------------------------------- - --- We cannot alter type between varchar2 & clob directly so an in between column is required -ALTER TABLE resourcepolicy ADD rpdescription_clob CLOB; -UPDATE resourcepolicy SET rpdescription_clob=rpdescription, rpdescription=null; -ALTER TABLE resourcepolicy DROP COLUMN rpdescription; -ALTER TABLE resourcepolicy RENAME COLUMN rpdescription_clob TO rpdescription; \ No newline at end of file diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2016.04.04__DS-3086-OAI-Performance-fix.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2016.04.04__DS-3086-OAI-Performance-fix.sql deleted file mode 100644 index 7b13d10b6d4f..000000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2016.04.04__DS-3086-OAI-Performance-fix.sql +++ /dev/null @@ -1,46 +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/ --- - ---------------------------------------------------------------- --- DS-3086 OAI Harvesting performance ---------------------------------------------------------------- --- This script will create indexes on the key fields of the --- metadataschemaregistry and metadatafieldregistry tables to --- increase the performance of the queries. It will also add --- "ON DELETE CASCADE" to improve the performance of Item deletion. ---------------------------------------------------------------- - -CREATE UNIQUE INDEX metadataschema_idx_short_id on metadataschemaregistry(short_id); - -CREATE INDEX metadatafield_idx_elem_qual on metadatafieldregistry(element, qualifier); - -CREATE INDEX resourcepolicy_idx_rptype on resourcepolicy(rptype); - --- Add "ON DELETE CASCADE" to foreign key constraint to Item -ALTER TABLE RESOURCEPOLICY ADD DSPACE_OBJECT_NEW RAW(16); -UPDATE RESOURCEPOLICY SET DSPACE_OBJECT_NEW = DSPACE_OBJECT; -ALTER TABLE RESOURCEPOLICY DROP COLUMN DSPACE_OBJECT; -ALTER TABLE RESOURCEPOLICY RENAME COLUMN DSPACE_OBJECT_NEW to DSPACE_OBJECT; - -ALTER TABLE RESOURCEPOLICY -ADD CONSTRAINT RESOURCEPOLICY_DSPACE_OBJ_FK -FOREIGN KEY (DSPACE_OBJECT) -REFERENCES dspaceobject(uuid) -ON DELETE CASCADE; - --- Add "ON DELETE CASCADE" to foreign key constraint to Item -ALTER TABLE METADATAVALUE ADD DSPACE_OBJECT_NEW RAW(16); -UPDATE METADATAVALUE SET DSPACE_OBJECT_NEW = DSPACE_OBJECT_ID; -ALTER TABLE METADATAVALUE DROP COLUMN DSPACE_OBJECT_ID; -ALTER TABLE METADATAVALUE RENAME COLUMN DSPACE_OBJECT_NEW to DSPACE_OBJECT_ID; - -ALTER TABLE METADATAVALUE -ADD CONSTRAINT METADATAVALUE_DSPACE_OBJECT_FK -FOREIGN KEY (DSPACE_OBJECT_ID) -REFERENCES DSPACEOBJECT(UUID) -ON DELETE CASCADE; \ No newline at end of file diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2016.04.14__DS-3125-fix-bundle-bitstream-delete-rights.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2016.04.14__DS-3125-fix-bundle-bitstream-delete-rights.sql deleted file mode 100644 index a1b303f0365a..000000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2016.04.14__DS-3125-fix-bundle-bitstream-delete-rights.sql +++ /dev/null @@ -1,33 +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/ --- - ---------------------------------------------------------------- --- DS-3125 Submitters cannot delete bistreams of workspaceitems ---------------------------------------------------------------- --- This script will add delete rights on all bundles/bitstreams --- for people who already have REMOVE rights. --- In previous versions REMOVE rights was enough to ensure that --- you could delete an object. ---------------------------------------------------------------- -INSERT INTO resourcepolicy (policy_id, resource_type_id, resource_id, action_id, start_date, end_date, rpname, -rptype, rpdescription, eperson_id, epersongroup_id, dspace_object) -SELECT -resourcepolicy_seq.nextval AS policy_id, -resource_type_id, -resource_id, --- Insert the Constants.DELETE action -2 AS action_id, -start_date, -end_date, -rpname, -rptype, -rpdescription, -eperson_id, -epersongroup_id, -dspace_object -FROM resourcepolicy WHERE action_id=4 AND (resource_type_id=0 OR resource_type_id=1 OR resource_type_id=2); \ No newline at end of file diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2016.05.10__DS-3168-fix-requestitem_item_id_column.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2016.05.10__DS-3168-fix-requestitem_item_id_column.sql deleted file mode 100644 index 2ba3517e1988..000000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2016.05.10__DS-3168-fix-requestitem_item_id_column.sql +++ /dev/null @@ -1,24 +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/ --- - ---------------------------------------------------------------- --- DS-3168 Embargo request Unknown Entity RequestItem ---------------------------------------------------------------- --- convert the item_id and bitstream_id columns from integer to UUID ---------------------------------------------------------------- -ALTER TABLE requestitem RENAME COLUMN item_id to item_legacy_id; -ALTER TABLE requestitem ADD item_id RAW(16) REFERENCES Item(uuid); -CREATE INDEX requestitem_item on requestitem(item_id); -UPDATE requestitem SET item_id = (SELECT item.uuid FROM item WHERE requestitem.item_legacy_id = item.item_id); -ALTER TABLE requestitem DROP COLUMN item_legacy_id; - -ALTER TABLE requestitem RENAME COLUMN bitstream_id to bitstream_legacy_id; -ALTER TABLE requestitem ADD bitstream_id RAW(16) REFERENCES Bitstream(uuid); -CREATE INDEX requestitem_bitstream on requestitem(bitstream_id); -UPDATE requestitem SET bitstream_id = (SELECT Bitstream.uuid FROM Bitstream WHERE requestitem.bitstream_legacy_id = Bitstream.bitstream_id); -ALTER TABLE requestitem DROP COLUMN bitstream_legacy_id; \ No newline at end of file diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2016.07.21__DS-2775.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2016.07.21__DS-2775.sql deleted file mode 100644 index 74783974468c..000000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2016.07.21__DS-2775.sql +++ /dev/null @@ -1,30 +0,0 @@ --- --- The contents of this file are subject to the license and copyright --- detailed in the LICENSE and NOTICE files at the root of the source --- tree and available online at --- --- http://www.dspace.org/license/ --- - ------------------------------------------------------- --- DS-2775 Drop unused sequences ------------------------------------------------------- - -DROP SEQUENCE bitstream_seq; -DROP SEQUENCE bundle2bitstream_seq; -DROP SEQUENCE bundle_seq; -DROP SEQUENCE collection2item_seq; -DROP SEQUENCE collection_seq; -DROP SEQUENCE community2collection_seq; -DROP SEQUENCE community2community_seq; -DROP SEQUENCE community_seq; -DROP SEQUENCE dcvalue_seq; -DROP SEQUENCE eperson_seq; -DROP SEQUENCE epersongroup2eperson_seq; -DROP SEQUENCE epersongroup2workspaceitem_seq; -DROP SEQUENCE epersongroup_seq; -DROP SEQUENCE group2group_seq; -DROP SEQUENCE group2groupcache_seq; -DROP SEQUENCE historystate_seq; -DROP SEQUENCE item2bundle_seq; -DROP SEQUENCE item_seq; diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2016.07.26__DS-3277_fix_handle_assignment.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2016.07.26__DS-3277_fix_handle_assignment.sql deleted file mode 100644 index 96f125f78b61..000000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2016.07.26__DS-3277_fix_handle_assignment.sql +++ /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/ --- - ----------------------------------------------------------------------------------- --- DS-3277 : 'handle_id' column needs its own separate sequence, so that Handles --- can be minted from 'handle_seq' ----------------------------------------------------------------------------------- --- Create a new sequence for 'handle_id' column. --- The role of this sequence is to simply provide a unique internal ID to the database. -CREATE SEQUENCE handle_id_seq; --- Initialize new 'handle_id_seq' to the maximum value of 'handle_id' -DECLARE - curr NUMBER := 0; -BEGIN - SELECT max(handle_id) INTO curr FROM handle; - - curr := curr + 1; - - EXECUTE IMMEDIATE 'DROP SEQUENCE handle_id_seq'; - - EXECUTE IMMEDIATE 'CREATE SEQUENCE handle_id_seq START WITH ' || NVL(curr,1); -END; -/ - --- Ensure the 'handle_seq' is updated to the maximum *suffix* in 'handle' column, --- as this sequence is used to mint new Handles. --- Code borrowed from update-sequences.sql and updateseq.sql -DECLARE - curr NUMBER := 0; -BEGIN - SELECT max(to_number(regexp_replace(handle, '.*/', ''), '999999999999')) INTO curr FROM handle WHERE REGEXP_LIKE(handle, '^.*/[0123456789]*$'); - - curr := curr + 1; - - EXECUTE IMMEDIATE 'DROP SEQUENCE handle_seq'; - - EXECUTE IMMEDIATE 'CREATE SEQUENCE handle_seq START WITH ' || NVL(curr,1); -END; -/ \ No newline at end of file diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2016.08.23__DS-3097.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2016.08.23__DS-3097.sql deleted file mode 100644 index e1220c8c7cce..000000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2016.08.23__DS-3097.sql +++ /dev/null @@ -1,24 +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/ --- - ------------------------------------------------------- --- DS-3097 introduced new action id for WITHDRAWN_READ ------------------------------------------------------- - -UPDATE resourcepolicy SET action_id = 12 where action_id = 0 and dspace_object in ( - SELECT bundle2bitstream.bitstream_id FROM bundle2bitstream - LEFT JOIN item2bundle ON bundle2bitstream.bundle_id = item2bundle.bundle_id - LEFT JOIN item ON item2bundle.item_id = item.uuid - WHERE item.withdrawn = 1 -); - -UPDATE resourcepolicy SET action_id = 12 where action_id = 0 and dspace_object in ( - SELECT item2bundle.bundle_id FROM item2bundle - LEFT JOIN item ON item2bundle.item_id = item.uuid - WHERE item.withdrawn = 1 -); \ No newline at end of file diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2016.11.29__DS-3410-lost-indexes.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2016.11.29__DS-3410-lost-indexes.sql deleted file mode 100644 index 5c3c3842aaea..000000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2016.11.29__DS-3410-lost-indexes.sql +++ /dev/null @@ -1,17 +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/ --- - ---------------------------------------------------------------- --- DS-3410 ---------------------------------------------------------------- --- This script will create lost indexes ---------------------------------------------------------------- - -CREATE INDEX resourcepolicy_object on resourcepolicy(dspace_object); -CREATE INDEX metadatavalue_object on metadatavalue(dspace_object_id); -CREATE INDEX metadatavalue_field_object on metadatavalue(metadata_field_id, dspace_object_id); \ No newline at end of file diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2016.11.30__DS-3409.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2016.11.30__DS-3409.sql deleted file mode 100644 index 47b2d18be8a3..000000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V6.0_2016.11.30__DS-3409.sql +++ /dev/null @@ -1,16 +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/ --- - ------------------------------------------------------- --- DS-3097 Handle of collections and communities are lost due to bug at V6.0_2015.03.07__DS-2701_Hibernate_migration.sql ------------------------------------------------------- - -UPDATE handle SET resource_id = (SELECT community.uuid FROM community WHERE handle.resource_legacy_id = community.community_id AND handle.resource_type_id = 4) where handle.resource_type_id = 4; -UPDATE handle SET resource_id = (SELECT collection.uuid FROM collection WHERE handle.resource_legacy_id = collection.collection_id AND handle.resource_type_id = 3) where handle.resource_type_id = 3; -UPDATE handle SET resource_id = (SELECT item.uuid FROM item WHERE handle.resource_legacy_id = item.item_id AND handle.resource_type_id = 2) where handle.resource_type_id = 2; - \ No newline at end of file diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2017.10.12__DS-3542-stateless-sessions.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2017.10.12__DS-3542-stateless-sessions.sql deleted file mode 100644 index 30cfae91c83a..000000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2017.10.12__DS-3542-stateless-sessions.sql +++ /dev/null @@ -1,20 +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/ --- - --- =============================================================== --- WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING --- --- DO NOT MANUALLY RUN THIS DATABASE MIGRATION. IT WILL BE EXECUTED --- AUTOMATICALLY (IF NEEDED) BY "FLYWAY" WHEN YOU STARTUP DSPACE. --- http://flywaydb.org/ --- =============================================================== - ------------------------------------------------------------------------------------------------------------- --- This adds an extra column to the eperson table where we save a salt for stateless authentication ------------------------------------------------------------------------------------------------------------- -ALTER TABLE eperson ADD session_salt varchar(32); \ No newline at end of file diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2018.04.16__dspace-entities.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2018.04.16__dspace-entities.sql deleted file mode 100644 index fc1c0b2e2319..000000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2018.04.16__dspace-entities.sql +++ /dev/null @@ -1,65 +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/ --- - --- =============================================================== --- WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING --- --- DO NOT MANUALLY RUN THIS DATABASE MIGRATION. IT WILL BE EXECUTED --- AUTOMATICALLY (IF NEEDED) BY "FLYWAY" WHEN YOU STARTUP DSPACE. --- http://flywaydb.org/ --- =============================================================== - -------------------------------------------------------------- --- This will create the setup for the dspace 7 entities usage -------------------------------------------------------------- -CREATE SEQUENCE entity_type_id_seq; -CREATE SEQUENCE relationship_type_id_seq; -CREATE SEQUENCE relationship_id_seq; - -CREATE TABLE entity_type -( - id INTEGER NOT NULL PRIMARY KEY, - label varchar(32) UNIQUE NOT NULL -); - -CREATE TABLE relationship_type -( - id INTEGER NOT NULL PRIMARY KEY, - left_type INTEGER NOT NULL, - right_type INTEGER NOT NULL, - left_label varchar(32) NOT NULL, - right_label varchar(32) NOT NULL, - left_min_cardinality INTEGER, - left_max_cardinality INTEGER, - right_min_cardinality INTEGER, - right_max_cardinality INTEGER, - FOREIGN KEY (left_type) REFERENCES entity_type(id), - FOREIGN KEY (right_type) REFERENCES entity_type(id), - CONSTRAINT u_relationship_type_constraint UNIQUE (left_type, right_type, left_label, right_label) - -); - -CREATE TABLE relationship -( - id INTEGER NOT NULL PRIMARY KEY, - left_id raw(16) NOT NULL REFERENCES item(uuid), - type_id INTEGER NOT NULL REFERENCES relationship_type(id), - right_id raw(16) NOT NULL REFERENCES item(uuid), - left_place INTEGER, - right_place INTEGER, - CONSTRAINT u_constraint UNIQUE (left_id, type_id, right_id) - -); - -CREATE INDEX entity_type_label_idx ON entity_type(label); -CREATE INDEX rl_ty_by_left_type_idx ON relationship_type(left_type); -CREATE INDEX rl_ty_by_right_type_idx ON relationship_type(right_type); -CREATE INDEX rl_ty_by_left_label_idx ON relationship_type(left_label); -CREATE INDEX rl_ty_by_right_label_idx ON relationship_type(right_label); -CREATE INDEX relationship_by_left_id_idx ON relationship(left_id); -CREATE INDEX relationship_by_right_id_idx ON relationship(right_id); diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2018.06.07__DS-3851-permission.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2018.06.07__DS-3851-permission.sql deleted file mode 100644 index 68ed690f89e8..000000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2018.06.07__DS-3851-permission.sql +++ /dev/null @@ -1,24 +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/ --- - --- =============================================================== --- WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING --- --- DO NOT MANUALLY RUN THIS DATABASE MIGRATION. IT WILL BE EXECUTED --- AUTOMATICALLY (IF NEEDED) BY "FLYWAY" WHEN YOU STARTUP DSPACE. --- http://flywaydb.org/ --- =============================================================== - ----------------------------------------------------------------------------------------------------------------- --- This adds TYPE_INHERITED to all old archived items permission due to the change on resource policy management ----------------------------------------------------------------------------------------------------------------- -UPDATE resourcepolicy set rptype = 'TYPE_INHERITED' - where resource_type_id = 2 and rptype is null - and dspace_object in ( - select uuid from item where in_archive = 1 - ); \ No newline at end of file diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2019.05.02__DS-4239-workflow-xml-migration.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2019.05.02__DS-4239-workflow-xml-migration.sql deleted file mode 100644 index b23170f43732..000000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2019.05.02__DS-4239-workflow-xml-migration.sql +++ /dev/null @@ -1,17 +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/ --- - ---------------------------------------------------------------- --- DS-4239 Migrate the workflow.xml to spring ---------------------------------------------------------------- --- This script will rename the default workflow "default" name --- to the new "defaultWorkflow" identifier ---------------------------------------------------------------- - -UPDATE cwf_pooltask SET workflow_id='defaultWorkflow' WHERE workflow_id='default'; -UPDATE cwf_claimtask SET workflow_id='defaultWorkflow' WHERE workflow_id='default'; \ No newline at end of file diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2019.07.31__Retrieval_of_name_variant.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2019.07.31__Retrieval_of_name_variant.sql deleted file mode 100644 index cebae09f651c..000000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2019.07.31__Retrieval_of_name_variant.sql +++ /dev/null @@ -1,18 +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/ --- - ------------------------------------------------------------------------------------ --- Create columns leftwardValue and rightwardValue in table relationship --- Rename columns left_label and right_label to leftward_type and rightward_type ------------------------------------------------------------------------------------ - -ALTER TABLE relationship ADD leftward_value VARCHAR2(50); -ALTER TABLE relationship ADD rightward_value VARCHAR2(50); - -ALTER TABLE relationship_type RENAME COLUMN left_label TO leftward_type; -ALTER TABLE relationship_type RENAME COLUMN right_label TO rightward_type; diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2019.11.13__relationship_type_copy_left_right.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2019.11.13__relationship_type_copy_left_right.sql deleted file mode 100644 index 0db294c1c13a..000000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2019.11.13__relationship_type_copy_left_right.sql +++ /dev/null @@ -1,14 +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/ --- - ------------------------------------------------------------------------------------ --- Create columns copy_left and copy_right for RelationshipType ------------------------------------------------------------------------------------ - -ALTER TABLE relationship_type ADD copy_to_left NUMBER(1) DEFAULT 0 NOT NULL; -ALTER TABLE relationship_type ADD copy_to_right NUMBER(1) DEFAULT 0 NOT NULL; diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2019_06_14__scripts-and-process.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2019_06_14__scripts-and-process.sql deleted file mode 100644 index a7015e3033bf..000000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2019_06_14__scripts-and-process.sql +++ /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/ --- - --- =============================================================== --- WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING --- --- DO NOT MANUALLY RUN THIS DATABASE MIGRATION. IT WILL BE EXECUTED --- AUTOMATICALLY (IF NEEDED) BY "FLYWAY" WHEN YOU STARTUP DSPACE. --- http://flywaydb.org/ --- =============================================================== -CREATE SEQUENCE process_id_seq; - -CREATE TABLE process -( - process_id INTEGER NOT NULL PRIMARY KEY, - user_id RAW(16) NOT NULL, - start_time TIMESTAMP, - finished_time TIMESTAMP, - creation_time TIMESTAMP NOT NULL, - script VARCHAR(256) NOT NULL, - status VARCHAR(32), - parameters VARCHAR(512) -); - -CREATE TABLE process2bitstream -( - process_id INTEGER REFERENCES process(process_id), - bitstream_id RAW(16) REFERENCES bitstream(uuid), - CONSTRAINT PK_process2bitstream PRIMARY KEY (process_id, bitstream_id) -); - -CREATE INDEX process_user_id_idx ON process(user_id); -CREATE INDEX process_status_idx ON process(status); -CREATE INDEX process_name_idx on process(script); -CREATE INDEX process_start_time_idx on process(start_time); \ No newline at end of file diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2020.01.08__DS-626-statistics-tracker.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2020.01.08__DS-626-statistics-tracker.sql deleted file mode 100644 index a108fd74b468..000000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2020.01.08__DS-626-statistics-tracker.sql +++ /dev/null @@ -1,29 +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/ --- - --- =============================================================== --- WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING --- --- DO NOT MANUALLY RUN THIS DATABASE MIGRATION. IT WILL BE EXECUTED --- AUTOMATICALLY (IF NEEDED) BY "FLYWAY" WHEN YOU STARTUP DSPACE. --- http://flywaydb.org/ --- =============================================================== - -------------------------------------------------------------- --- This will create the setup for the IRUS statistics harvester -------------------------------------------------------------- - -CREATE SEQUENCE openurltracker_seq; - -CREATE TABLE openurltracker -( - tracker_id NUMBER, - tracker_url VARCHAR2(1000), - uploaddate DATE, - CONSTRAINT openurltracker_PK PRIMARY KEY (tracker_id) -); \ No newline at end of file diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2021.01.22__Remove_basic_workflow.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2021.01.22__Remove_basic_workflow.sql deleted file mode 100644 index f71173abe607..000000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2021.01.22__Remove_basic_workflow.sql +++ /dev/null @@ -1,17 +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/ --- - ------------------------------------------------------------------------------------ --- Drop the 'workflowitem' and 'tasklistitem' tables ------------------------------------------------------------------------------------ - -DROP TABLE workflowitem CASCADE CONSTRAINTS; -DROP TABLE tasklistitem CASCADE CONSTRAINTS; - -DROP SEQUENCE workflowitem_seq; -DROP SEQUENCE tasklistitem_seq; \ No newline at end of file diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2021.02.08__tilted_rels.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2021.02.08__tilted_rels.sql deleted file mode 100644 index 95d07be477d5..000000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2021.02.08__tilted_rels.sql +++ /dev/null @@ -1,13 +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/ --- - ------------------------------------------------------------------------------------ --- Create columns copy_left and copy_right for RelationshipType ------------------------------------------------------------------------------------ - -ALTER TABLE relationship_type ADD tilted INTEGER; diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2021.03.18__Move_entity_type_to_dspace_schema.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2021.03.18__Move_entity_type_to_dspace_schema.sql deleted file mode 100644 index 9c39091f89dc..000000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2021.03.18__Move_entity_type_to_dspace_schema.sql +++ /dev/null @@ -1,56 +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/ --- - --- =============================================================== --- WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING --- --- DO NOT MANUALLY RUN THIS DATABASE MIGRATION. IT WILL BE EXECUTED --- AUTOMATICALLY (IF NEEDED) BY "FLYWAY" WHEN YOU STARTUP DSPACE. --- http://flywaydb.org/ --- =============================================================== - -------------------------------------------------------------------------------------------------------- --- Move all 'relationship.type' metadata fields to 'dspace.entity.type'. Remove 'relationship' schema. -------------------------------------------------------------------------------------------------------- --- Special case: we need to the 'dspace' schema to already exist. If users don't already have it we must create it --- manually via SQL, as by default it won't be created until database updates are finished. -INSERT INTO metadataschemaregistry (metadata_schema_id, namespace, short_id) - SELECT metadataschemaregistry_seq.nextval, 'http://dspace.org/dspace' as namespace, 'dspace' as short_id FROM dual - WHERE NOT EXISTS - (SELECT metadata_schema_id,namespace,short_id FROM metadataschemaregistry - WHERE namespace = 'http://dspace.org/dspace' AND short_id = 'dspace'); - - --- Add 'dspace.entity.type' field to registry (if missing) -INSERT INTO metadatafieldregistry (metadata_field_id, metadata_schema_id, element, qualifier) - SELECT metadatafieldregistry_seq.nextval, - (SELECT metadata_schema_id FROM metadataschemaregistry WHERE short_id='dspace'), 'entity', 'type' FROM dual - WHERE NOT EXISTS - (SELECT metadata_field_id,element,qualifier FROM metadatafieldregistry - WHERE metadata_schema_id = (SELECT metadata_schema_id FROM metadataschemaregistry WHERE short_id='dspace') - AND element = 'entitye' AND qualifier='type'); - --- Moves all 'relationship.type' field values to a new 'dspace.entity.type' field -UPDATE metadatavalue - SET metadata_field_id = - (SELECT metadata_field_id FROM metadatafieldregistry - WHERE metadata_schema_id = (SELECT metadata_schema_id FROM metadataschemaregistry WHERE short_id='dspace') - AND element = 'entity' AND qualifier='type') - WHERE metadata_field_id = - (SELECT metadata_field_id FROM metadatafieldregistry - WHERE metadata_schema_id = (SELECT metadata_schema_id FROM metadataschemaregistry WHERE short_id='relationship') - AND element = 'type' AND qualifier is NULL); - - --- Delete 'relationship.type' field from registry -DELETE FROM metadatafieldregistry - WHERE metadata_schema_id = (SELECT metadata_schema_id FROM metadataschemaregistry WHERE short_id = 'relationship') - AND element = 'type' AND qualifier is NULL; - --- Delete 'relationship' schema (which is now empty) -DELETE FROM metadataschemaregistry WHERE short_id = 'relationship' AND namespace = 'http://dspace.org/relationship'; diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2021.09.24__Move_entity_type_from_item_template_to_collection.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2021.09.24__Move_entity_type_from_item_template_to_collection.sql deleted file mode 100644 index 5a6abda04101..000000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2021.09.24__Move_entity_type_from_item_template_to_collection.sql +++ /dev/null @@ -1,28 +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/ --- - --- =============================================================== --- WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING --- --- DO NOT MANUALLY RUN THIS DATABASE MIGRATION. IT WILL BE EXECUTED --- AUTOMATICALLY (IF NEEDED) BY "FLYWAY" WHEN YOU STARTUP DSPACE. --- http://flywaydb.org/ --- =============================================================== - -------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------- -UPDATE metadatavalue SET dspace_object_id = (SELECT uuid - FROM collection - WHERE template_item_id = dspace_object_id) -WHERE dspace_object_id IN (SELECT template_item_id - FROM Collection) - AND metadata_field_id - IN (SELECT metadata_field_id - FROM metadatafieldregistry mfr LEFT JOIN metadataschemaregistry msr - ON mfr.metadata_schema_id = msr.metadata_schema_id - WHERE msr.short_id = 'dspace' AND mfr.element = 'entity' AND mfr.qualifier = 'type'); \ No newline at end of file diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2021.10.04__alter_collection_table_drop_workflow_stem_columns.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2021.10.04__alter_collection_table_drop_workflow_stem_columns.sql deleted file mode 100644 index ae8f1e7ef5d2..000000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2021.10.04__alter_collection_table_drop_workflow_stem_columns.sql +++ /dev/null @@ -1,15 +0,0 @@ --- --- The contents of this file are subject to the license and copyright --- detailed in the LICENSE and NOTICE files at the root of the source --- tree and available online at --- --- http://www.dspace.org/license/ --- - -------------------------------------------------------------------------------------- ----- ALTER table collection -------------------------------------------------------------------------------------- - -ALTER TABLE collection DROP COLUMN workflow_step_1; -ALTER TABLE collection DROP COLUMN workflow_step_2; -ALTER TABLE collection DROP COLUMN workflow_step_3; \ No newline at end of file diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.1_2021.10.18__Fix_MDV_place_after_migrating_from_DSpace_5.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.1_2021.10.18__Fix_MDV_place_after_migrating_from_DSpace_5.sql deleted file mode 100644 index 9c39c15e66e2..000000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.1_2021.10.18__Fix_MDV_place_after_migrating_from_DSpace_5.sql +++ /dev/null @@ -1,24 +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/ --- - ----------------------------------------------------- --- Make sure the metadatavalue.place column starts at 0 instead of 1 ----------------------------------------------------- -MERGE INTO metadatavalue mdv -USING ( - SELECT dspace_object_id, metadata_field_id, MIN(place) AS minplace - FROM metadatavalue - GROUP BY dspace_object_id, metadata_field_id -) mp -ON ( - mdv.dspace_object_id = mp.dspace_object_id - AND mdv.metadata_field_id = mp.metadata_field_id - AND mp.minplace > 0 -) -WHEN MATCHED THEN UPDATE -SET mdv.place = mdv.place - mp.minplace; \ No newline at end of file diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.3_2022.04.29__orcid_queue_and_history.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.3_2022.04.29__orcid_queue_and_history.sql deleted file mode 100644 index 3fe424cf6cfc..000000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.3_2022.04.29__orcid_queue_and_history.sql +++ /dev/null @@ -1,54 +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/ --- - ------------------------------------------------------------------------------------ --- Create tables for ORCID Queue and History ------------------------------------------------------------------------------------ - -CREATE SEQUENCE orcid_queue_id_seq; - -CREATE TABLE orcid_queue -( - id INTEGER NOT NULL, - owner_id RAW(16) NOT NULL, - entity_id RAW(16), - put_code VARCHAR(255), - record_type VARCHAR(255), - description VARCHAR(255), - operation VARCHAR(255), - metadata CLOB, - attempts INTEGER, - CONSTRAINT orcid_queue_pkey PRIMARY KEY (id), - CONSTRAINT orcid_queue_owner_id_fkey FOREIGN KEY (owner_id) REFERENCES item (uuid), - CONSTRAINT orcid_queue_entity_id_fkey FOREIGN KEY (entity_id) REFERENCES item (uuid) -); - -CREATE INDEX orcid_queue_owner_id_index on orcid_queue(owner_id); - - -CREATE SEQUENCE orcid_history_id_seq; - -CREATE TABLE orcid_history -( - id INTEGER NOT NULL, - owner_id RAW(16) NOT NULL, - entity_id RAW(16), - put_code VARCHAR(255), - timestamp_last_attempt TIMESTAMP, - response_message CLOB, - status INTEGER, - metadata CLOB, - operation VARCHAR(255), - record_type VARCHAR(255), - description VARCHAR(255), - CONSTRAINT orcid_history_pkey PRIMARY KEY (id), - CONSTRAINT orcid_history_owner_id_fkey FOREIGN KEY (owner_id) REFERENCES item (uuid), - CONSTRAINT orcid_history_entity_id_fkey FOREIGN KEY (entity_id) REFERENCES item (uuid) -); - -CREATE INDEX orcid_history_owner_id_index on orcid_history(owner_id); \ No newline at end of file diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.3_2022.05.16__Orcid_token_table.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.3_2022.05.16__Orcid_token_table.sql deleted file mode 100644 index 14bf8531439f..000000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.3_2022.05.16__Orcid_token_table.sql +++ /dev/null @@ -1,24 +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/ --- - ------------------------------------------------------------------------------------ --- Create table for ORCID access tokens ------------------------------------------------------------------------------------ - -CREATE SEQUENCE orcid_token_id_seq; - -CREATE TABLE orcid_token -( - id INTEGER NOT NULL, - eperson_id RAW(16) NOT NULL UNIQUE, - profile_item_id RAW(16), - access_token VARCHAR2(100) NOT NULL, - CONSTRAINT orcid_token_pkey PRIMARY KEY (id), - CONSTRAINT orcid_token_eperson_id_fkey FOREIGN KEY (eperson_id) REFERENCES eperson (uuid), - CONSTRAINT orcid_token_profile_item_id_fkey FOREIGN KEY (profile_item_id) REFERENCES item (uuid) -); diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.3_2022.06.16__process_to_group.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.3_2022.06.16__process_to_group.sql deleted file mode 100644 index 0e7d417ae52d..000000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.3_2022.06.16__process_to_group.sql +++ /dev/null @@ -1,18 +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/ --- - -------------------------------------------------------------------------------- --- Table to store Groups related to a Process on its creation -------------------------------------------------------------------------------- - -CREATE TABLE Process2Group -( - process_id INTEGER REFERENCES Process(process_id), - group_id UUID REFERENCES epersongroup (uuid) ON DELETE CASCADE, - CONSTRAINT PK_Process2Group PRIMARY KEY (process_id, group_id) -); \ No newline at end of file diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.3_2022.06.20__add_last_version_status_column_to_relationship_table.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.3_2022.06.20__add_last_version_status_column_to_relationship_table.sql deleted file mode 100644 index 3eb9ae6dd4f8..000000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.3_2022.06.20__add_last_version_status_column_to_relationship_table.sql +++ /dev/null @@ -1,10 +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/ --- - --- NOTE: default 0 ensures that existing relations have "latest_version_status" set to "both" (first constant in enum, see Relationship class) -ALTER TABLE relationship ADD latest_version_status INTEGER DEFAULT 0 NOT NULL; diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.5_2022.12.01__add_table_subscriptionparamter_change_columns_subscription_table.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.5_2022.12.01__add_table_subscriptionparamter_change_columns_subscription_table.sql deleted file mode 100644 index 3862830230e3..000000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.5_2022.12.01__add_table_subscriptionparamter_change_columns_subscription_table.sql +++ /dev/null @@ -1,45 +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/ --- - ------------------------------------------------------------------------------------ --- ADD table subscription_parameter ------------------------------------------------------------------------------------ - - -CREATE SEQUENCE if NOT EXISTS subscription_parameter_seq; ------------------------------------------------------------------------------------ --- ADD table subscription_parameter ------------------------------------------------------------------------------------ -CREATE TABLE if NOT EXISTS subscription_parameter -( - subscription_parameter_id INTEGER NOT NULL, - name VARCHAR(255), - value VARCHAR(255), - subscription_id INTEGER NOT NULL, - CONSTRAINT subscription_parameter_pkey PRIMARY KEY (subscription_parameter_id), - CONSTRAINT subscription_parameter_subscription_fkey FOREIGN KEY (subscription_id) - REFERENCES subscription (subscription_id) ON DELETE CASCADE -); --- -- - -ALTER TABLE subscription ADD COLUMN if NOT EXISTS dspace_object_id UUID; ----- -- -ALTER TABLE subscription ADD COLUMN if NOT EXISTS type CHARACTER VARYING(255); --- -UPDATE subscription SET dspace_object_id = collection_id , type = 'content'; --- -ALTER TABLE subscription DROP CONSTRAINT IF EXISTS subscription_dspaceobject_fkey; -ALTER TABLE subscription ADD CONSTRAINT subscription_dspaceobject_fkey FOREIGN KEY (dspace_object_id) REFERENCES dspaceobject (uuid); --- -ALTER TABLE subscription DROP CONSTRAINT IF EXISTS subscription_collection_id_fkey; ----- -- -ALTER TABLE subscription DROP COLUMN IF EXISTS collection_id; --- -- -INSERT INTO subscription_parameter (subscription_parameter_id, name, value, subscription_id) -SELECT getnextid('subscription_parameter'), 'frequency', 'D', subscription_id from "subscription" ; - diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.5_2022.12.09__Supervision_Orders_table.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.5_2022.12.09__Supervision_Orders_table.sql deleted file mode 100644 index c7bb0b502ec2..000000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.5_2022.12.09__Supervision_Orders_table.sql +++ /dev/null @@ -1,78 +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/ --- - -------------------------------------------------------------------------------- --- Table to store supervision orders -------------------------------------------------------------------------------- - -CREATE TABLE supervision_orders -( - id INTEGER PRIMARY KEY, - item_id UUID REFERENCES Item(uuid) ON DELETE CASCADE, - eperson_group_id UUID REFERENCES epersongroup(uuid) ON DELETE CASCADE -); - -CREATE SEQUENCE supervision_orders_seq; - -INSERT INTO supervision_orders (id, item_id, eperson_group_id) -SELECT supervision_orders_seq.nextval AS id, w.item_id, e.uuid -FROM epersongroup2workspaceitem ew INNER JOIN workspaceitem w -ON ew.workspace_item_id = w.workspace_item_id -INNER JOIN epersongroup e -ON ew.eperson_group_id = e.uuid; - - --- UPDATE policies for supervision orders --- items, bundles and bitstreams - -DECLARE -BEGIN - -FOR rec IN -( -SELECT so.item_id as dspace_object, so.eperson_group_id, rp.resource_type_id -FROM supervision_orders so -INNER JOIN RESOURCEPOLICY rp on so.item_id = rp.dspace_object -AND so.eperson_group_id = rp.epersongroup_id -WHERE rp.rptype IS NULL - -UNION - -SELECT ib.bundle_id as dspace_object, so.eperson_group_id, rp.resource_type_id -FROM supervision_orders so -INNER JOIN item2bundle ib ON so.item_id = ib.item_id -INNER JOIN RESOURCEPOLICY rp on ib.bundle_id = rp.dspace_object -AND so.eperson_group_id = rp.epersongroup_id -WHERE rp.rptype IS NULL - -UNION - -SELECT bs.bitstream_id as dspace_object, so.eperson_group_id, rp.resource_type_id -FROM supervision_orders so -INNER JOIN item2bundle ib ON so.item_id = ib.item_id -INNER JOIN bundle2bitstream bs ON ib.bundle_id = bs.bundle_id -INNER JOIN RESOURCEPOLICY rp on bs.bitstream_id = rp.dspace_object -AND so.eperson_group_id = rp.epersongroup_id -WHERE rp.rptype IS NULL -) - -LOOP - -UPDATE RESOURCEPOLICY SET rptype = 'TYPE_SUBMISSION' -where dspace_object = rec.dspace_object -AND epersongroup_id = rec.eperson_group_id -AND rptype IS NULL; - -END LOOP; -END; - -------------------------------------------------------------------------------- --- drop epersongroup2workspaceitem table -------------------------------------------------------------------------------- - -DROP TABLE epersongroup2workspaceitem; \ No newline at end of file diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.5_2022.12.15__system_wide_alerts.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.5_2022.12.15__system_wide_alerts.sql deleted file mode 100644 index 9d13138fdada..000000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.5_2022.12.15__system_wide_alerts.sql +++ /dev/null @@ -1,22 +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/ --- - ------------------------------------------------------------------------------------ --- Create table for System wide alerts ------------------------------------------------------------------------------------ - -CREATE SEQUENCE alert_id_seq; - -CREATE TABLE systemwidealert -( - alert_id INTEGER NOT NULL PRIMARY KEY, - message VARCHAR(512), - allow_sessions VARCHAR(64), - countdown_to TIMESTAMP, - active BOOLEAN -); diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.6_2023.03.24__Update_PNG_in_bitstream_format_registry.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.6_2023.03.24__Update_PNG_in_bitstream_format_registry.sql deleted file mode 100644 index 8aec44a7f6f2..000000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.6_2023.03.24__Update_PNG_in_bitstream_format_registry.sql +++ /dev/null @@ -1,17 +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/ --- - ------------------------------------------------------------------------------------ --- Update short description for PNG mimetype in the bitstream format registry --- See: https://github.com/DSpace/DSpace/pull/8722 ------------------------------------------------------------------------------------ - -UPDATE bitstreamformatregistry -SET short_description='PNG' -WHERE short_description='image/png' - AND mimetype='image/png'; diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.6_2023.03.29__orcid_queue_and_history_descriptions_to_text_type.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.6_2023.03.29__orcid_queue_and_history_descriptions_to_text_type.sql deleted file mode 100644 index 509e0a286935..000000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.6_2023.03.29__orcid_queue_and_history_descriptions_to_text_type.sql +++ /dev/null @@ -1,10 +0,0 @@ --- --- The contents of this file are subject to the license and copyright --- detailed in the LICENSE and NOTICE files at the root of the source --- tree and available online at --- --- http://www.dspace.org/license/ --- - -ALTER TABLE orcid_history MODIFY (description CLOB); -ALTER TABLE orcid_queue MODIFY (description CLOB); diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/update-sequences.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/update-sequences.sql deleted file mode 100644 index b4d4d755cbe7..000000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/update-sequences.sql +++ /dev/null @@ -1,77 +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/ --- - --- SQL code to update the ID (primary key) generating sequences, if some --- import operation has set explicit IDs. --- --- Sequences are used to generate IDs for new rows in the database. If a --- bulk import operation, such as an SQL dump, specifies primary keys for --- imported data explicitly, the sequences are out of sync and need updating. --- This SQL code does just that. --- --- This should rarely be needed; any bulk import should be performed using the --- org.dspace.content API which is safe to use concurrently and in multiple --- JVMs. The SQL code below will typically only be required after a direct --- SQL data dump from a backup or somesuch. - --- The 'updateseq' procedure was derived from incseq.sql found at: --- http://www.akadia.com/services/scripts/incseq.sql - -DECLARE - PROCEDURE updateseq ( seq IN VARCHAR, - tbl IN VARCHAR, - attr IN VARCHAR, - cond IN VARCHAR DEFAULT '' ) IS - curr NUMBER := 0; - BEGIN - EXECUTE IMMEDIATE 'SELECT max(' || attr - || ') FROM ' || tbl - || ' ' || cond - INTO curr; - curr := curr + 1; - EXECUTE IMMEDIATE 'DROP SEQUENCE ' || seq; - EXECUTE IMMEDIATE 'CREATE SEQUENCE ' - || seq - || ' START WITH ' - || NVL(curr, 1); - END updateseq; - -BEGIN - updateseq('bitstreamformatregistry_seq', 'bitstreamformatregistry', - 'bitstream_format_id'); - updateseq('fileextension_seq', 'fileextension', 'file_extension_id'); - updateseq('resourcepolicy_seq', 'resourcepolicy', 'policy_id'); - updateseq('workspaceitem_seq', 'workspaceitem', 'workspace_item_id'); - updateseq('registrationdata_seq', 'registrationdata', - 'registrationdata_id'); - updateseq('subscription_seq', 'subscription', 'subscription_id'); - updateseq('metadatafieldregistry_seq', 'metadatafieldregistry', - 'metadata_field_id'); - updateseq('metadatavalue_seq', 'metadatavalue', 'metadata_value_id'); - updateseq('metadataschemaregistry_seq', 'metadataschemaregistry', - 'metadata_schema_id'); - updateseq('harvested_collection_seq', 'harvested_collection', 'id'); - updateseq('harvested_item_seq', 'harvested_item', 'id'); - updateseq('webapp_seq', 'webapp', 'webapp_id'); - updateseq('requestitem_seq', 'requestitem', 'requestitem_id'); - updateseq('handle_id_seq', 'handle', 'handle_id'); - - -- Handle Sequence is a special case. Since Handles minted by DSpace - -- use the 'handle_seq', we need to ensure the next assigned handle - -- will *always* be unique. So, 'handle_seq' always needs to be set - -- to the value of the *largest* handle suffix. That way when the - -- next handle is assigned, it will use the next largest number. This - -- query does the following: - -- For all 'handle' values which have a number in their suffix - -- (after '/'), find the maximum suffix value, convert it to a - -- number, and set the 'handle_seq' to start at the next value (see - -- updateseq above for more). - updateseq('handle_seq', 'handle', - q'{to_number(regexp_replace(handle, '.*/', ''), '999999999999')}', - q'{WHERE REGEXP_LIKE(handle, '^.*/[0123456789]*$')}'); -END; \ No newline at end of file diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/workflow/oracle/basicWorkflow/V5.7_2017.05.05__DS-3431.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/workflow/oracle/basicWorkflow/V5.7_2017.05.05__DS-3431.sql deleted file mode 100644 index 9bca3a17c99e..000000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/workflow/oracle/basicWorkflow/V5.7_2017.05.05__DS-3431.sql +++ /dev/null @@ -1,503 +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/ --- - -------------------------------------------------------------------------- --- DS-3431 Workflow system is vulnerable to unauthorized manipulations -- -------------------------------------------------------------------------- - ------------------------------------------------------------------------ --- grant claiming permissions to all workflow step groups (step 1-3) -- ------------------------------------------------------------------------ -INSERT INTO resourcepolicy - (policy_id, resource_type_id, action_id, rptype, epersongroup_id, resource_id) - SELECT - resourcepolicy_seq.NEXTVAL AS policy_id, - '3' AS resource_type_id, - '5' AS action_id, - 'TYPE_WORKFLOW' AS rptype, - workflow_step_1 AS epersongroup_id, - collection_id AS dspace_object - FROM collection - WHERE workflow_step_1 IS NOT NULL - AND NOT EXISTS ( - SELECT 1 FROM resourcepolicy WHERE resource_type_id = 3 AND action_id = 5 AND epersongroup_id = workflow_step_1 and resource_id = collection_id - ); - -INSERT INTO resourcepolicy - (policy_id, resource_type_id, action_id, rptype, epersongroup_id, resource_id) - SELECT - resourcepolicy_seq.NEXTVAL AS policy_id, - '3' AS resource_type_id, - '6' AS action_id, - 'TYPE_WORKFLOW' AS rptype, - workflow_step_2 AS epersongroup_id, - collection_id AS dspace_object - FROM collection - WHERE workflow_step_2 IS NOT NULL - AND NOT EXISTS ( - SELECT 1 FROM resourcepolicy WHERE resource_type_id = 3 AND action_id = 6 AND epersongroup_id = workflow_step_2 and resource_id = collection_id - ); - -INSERT INTO resourcepolicy - (policy_id, resource_type_id, action_id, rptype, epersongroup_id, resource_id) - SELECT - resourcepolicy_seq.NEXTVAL AS policy_id, - '3' AS resource_type_id, - '7' AS action_id, - 'TYPE_WORKFLOW' AS rptype, - workflow_step_3 AS epersongroup_id, - collection_id AS dspace_object - FROM collection - WHERE workflow_step_3 IS NOT NULL - AND NOT EXISTS ( - SELECT 1 FROM resourcepolicy WHERE resource_type_id = 3 AND action_id = 7 AND epersongroup_id = workflow_step_3 and resource_id = collection_id - ); - ------------------------------------------------------------------------ --- grant add permissions to all workflow step groups (step 1-3) -- ------------------------------------------------------------------------ -INSERT INTO resourcepolicy - (policy_id, resource_type_id, action_id, rptype, epersongroup_id, resource_id) - SELECT - resourcepolicy_seq.NEXTVAL AS policy_id, - '3' AS resource_type_id, - '3' AS action_id, - 'TYPE_WORKFLOW' AS rptype, - workflow_step_1 AS epersongroup_id, - collection_id AS dspace_object - FROM collection - WHERE workflow_step_1 IS NOT NULL - AND NOT EXISTS ( - SELECT 1 FROM resourcepolicy WHERE resource_type_id = 3 AND action_id = 3 AND epersongroup_id = workflow_step_1 and resource_id = collection_id - ); - -INSERT INTO resourcepolicy - (policy_id, resource_type_id, action_id, rptype, epersongroup_id, resource_id) - SELECT - resourcepolicy_seq.NEXTVAL AS policy_id, - '3' AS resource_type_id, - '3' AS action_id, - 'TYPE_WORKFLOW' AS rptype, - workflow_step_2 AS epersongroup_id, - collection_id AS dspace_object - FROM collection - WHERE workflow_step_2 IS NOT NULL - AND NOT EXISTS ( - SELECT 1 FROM resourcepolicy WHERE resource_type_id = 3 AND action_id = 3 AND epersongroup_id = workflow_step_2 and resource_id = collection_id - ); - -INSERT INTO resourcepolicy - (policy_id, resource_type_id, action_id, rptype, epersongroup_id, resource_id) - SELECT - resourcepolicy_seq.NEXTVAL AS policy_id, - '3' AS resource_type_id, - '3' AS action_id, - 'TYPE_WORKFLOW' AS rptype, - workflow_step_3 AS epersongroup_id, - collection_id AS dspace_object - FROM collection - WHERE workflow_step_3 IS NOT NULL - AND NOT EXISTS ( - SELECT 1 FROM resourcepolicy WHERE resource_type_id = 3 AND action_id = 3 AND epersongroup_id = workflow_step_3 and resource_id = collection_id - ); - ----------------------------------------------------------------------------------- --- grant read/write/delete/add/remove permission on workflow items to reviewers -- ----------------------------------------------------------------------------------- -INSERT INTO resourcepolicy - (policy_id, resource_type_id, action_id, rptype, eperson_id, resource_id) - SELECT - resourcepolicy_seq.NEXTVAL AS policy_id, - '2' AS resource_type_id, - '0' AS action_id, - 'TYPE_WORKFLOW' AS rptype, - owner AS eperson_id, - item_id AS dspace_object - FROM workflowitem - WHERE - owner IS NOT NULL - AND (state = 2 OR state = 4 OR state = 6) - AND NOT EXISTS ( - SELECT 1 FROM resourcepolicy WHERE resource_type_id = 2 AND action_id = 0 AND eperson_id = owner AND resource_id = item_id - ); - -INSERT INTO resourcepolicy - (policy_id, resource_type_id, action_id, rptype, eperson_id, resource_id) - SELECT - resourcepolicy_seq.NEXTVAL AS policy_id, - '2' AS resource_type_id, - '1' AS action_id, - 'TYPE_WORKFLOW' AS rptype, - owner AS eperson_id, - item_id AS dspace_object - FROM workflowitem - WHERE - owner IS NOT NULL - AND (state = 2 OR state = 4 OR state = 6) - AND NOT EXISTS ( - SELECT 1 FROM resourcepolicy WHERE resource_type_id = 2 AND action_id = 1 AND eperson_id = owner AND resource_id = item_id - ); - -INSERT INTO resourcepolicy - (policy_id, resource_type_id, action_id, rptype, eperson_id, resource_id) - SELECT - resourcepolicy_seq.NEXTVAL AS policy_id, - '2' AS resource_type_id, - '2' AS action_id, - 'TYPE_WORKFLOW' AS rptype, - owner AS eperson_id, - item_id AS dspace_object - FROM workflowitem - WHERE - owner IS NOT NULL - AND (state = 2 OR state = 4 OR state = 6) - AND NOT EXISTS ( - SELECT 1 FROM resourcepolicy WHERE resource_type_id = 2 AND action_id = 2 AND eperson_id = owner AND resource_id = item_id - ); - -INSERT INTO resourcepolicy - (policy_id, resource_type_id, action_id, rptype, eperson_id, resource_id) - SELECT - resourcepolicy_seq.NEXTVAL AS policy_id, - '2' AS resource_type_id, - '3' AS action_id, - 'TYPE_WORKFLOW' AS rptype, - owner AS eperson_id, - item_id AS dspace_object - FROM workflowitem - WHERE - owner IS NOT NULL - AND (state = 2 OR state = 4 OR state = 6) - AND NOT EXISTS ( - SELECT 1 FROM resourcepolicy WHERE resource_type_id = 2 AND action_id = 3 AND eperson_id = owner AND resource_id = item_id - ); - -INSERT INTO resourcepolicy - (policy_id, resource_type_id, action_id, rptype, eperson_id, resource_id) - SELECT - resourcepolicy_seq.NEXTVAL AS policy_id, - '2' AS resource_type_id, - '4' AS action_id, - 'TYPE_WORKFLOW' AS rptype, - owner AS eperson_id, - item_id AS dspace_object - FROM workflowitem - WHERE - owner IS NOT NULL - AND (state = 2 OR state = 4 OR state = 6) - AND NOT EXISTS ( - SELECT 1 FROM resourcepolicy WHERE resource_type_id = 2 AND action_id = 4 AND eperson_id = owner AND resource_id = item_id - ); - ------------------------------------------------------------------------------------ --- grant read/write/delete/add/remove permission on Bundle ORIGINAL to reviewers -- ------------------------------------------------------------------------------------ -INSERT INTO resourcepolicy - (policy_id, resource_type_id, action_id, rptype, eperson_id, resource_id) - SELECT - resourcepolicy_seq.NEXTVAL AS policy_id, - '1' AS resource_type_id, - '0' AS action_id, - 'TYPE_WORKFLOW' AS rptype, - wfi.owner AS eperson_id, - i2b.bundle_id AS dspace_object - FROM workflowitem wfi - JOIN item2bundle i2b - ON i2b.item_id = wfi.item_id - JOIN metadatavalue mv - ON mv.resource_id = i2b.bundle_id - JOIN metadatafieldregistry mfr - ON mv.metadata_field_id = mfr.metadata_field_id - JOIN metadataschemaregistry msr - ON mfr.metadata_schema_id = msr.metadata_schema_id - WHERE - msr.namespace = 'http://dublincore.org/documents/dcmi-terms/' - AND mfr.element = 'title' - AND mfr.qualifier IS NULL - AND mv.text_value LIKE 'ORIGINAL' - AND wfi.owner IS NOT NULL - AND (wfi.state = 2 OR wfi.state = 4 OR wfi.state = 6) - AND NOT EXISTS( - SELECT 1 FROM resourcepolicy WHERE resource_type_id = 1 AND action_id = 0 AND resourcepolicy.eperson_id = owner AND resourcepolicy.resource_id = i2b.bundle_id - ); - -INSERT INTO resourcepolicy - (policy_id, resource_type_id, action_id, rptype, eperson_id, resource_id) - SELECT - resourcepolicy_seq.NEXTVAL AS policy_id, - '1' AS resource_type_id, - '1' AS action_id, - 'TYPE_WORKFLOW' AS rptype, - wfi.owner AS eperson_id, - i2b.bundle_id AS dspace_object - FROM workflowitem wfi - JOIN item2bundle i2b - ON i2b.item_id = wfi.item_id - JOIN metadatavalue mv - ON mv.resource_id = i2b.bundle_id - JOIN metadatafieldregistry mfr - ON mv.metadata_field_id = mfr.metadata_field_id - JOIN metadataschemaregistry msr - ON mfr.metadata_schema_id = msr.metadata_schema_id - WHERE - msr.namespace = 'http://dublincore.org/documents/dcmi-terms/' - AND mfr.element = 'title' - AND mfr.qualifier IS NULL - AND mv.text_value LIKE 'ORIGINAL' - AND wfi.owner IS NOT NULL - AND (wfi.state = 2 OR wfi.state = 4 OR wfi.state = 6) - AND NOT EXISTS( - SELECT 1 FROM resourcepolicy WHERE resource_type_id = 1 AND action_id = 1 AND resourcepolicy.eperson_id = owner AND resourcepolicy.resource_id = i2b.bundle_id - ); - -INSERT INTO resourcepolicy - (policy_id, resource_type_id, action_id, rptype, eperson_id, resource_id) - SELECT - resourcepolicy_seq.NEXTVAL AS policy_id, - '1' AS resource_type_id, - '2' AS action_id, - 'TYPE_WORKFLOW' AS rptype, - wfi.owner AS eperson_id, - i2b.bundle_id AS dspace_object - FROM workflowitem wfi - JOIN item2bundle i2b - ON i2b.item_id = wfi.item_id - JOIN metadatavalue mv - ON mv.resource_id = i2b.bundle_id - JOIN metadatafieldregistry mfr - ON mv.metadata_field_id = mfr.metadata_field_id - JOIN metadataschemaregistry msr - ON mfr.metadata_schema_id = msr.metadata_schema_id - WHERE - msr.namespace = 'http://dublincore.org/documents/dcmi-terms/' - AND mfr.element = 'title' - AND mfr.qualifier IS NULL - AND mv.text_value LIKE 'ORIGINAL' - AND wfi.owner IS NOT NULL - AND (wfi.state = 2 OR wfi.state = 4 OR wfi.state = 6) - AND NOT EXISTS( - SELECT 1 FROM resourcepolicy WHERE resource_type_id = 1 AND action_id = 2 AND resourcepolicy.eperson_id = owner AND resourcepolicy.resource_id = i2b.bundle_id - ); - -INSERT INTO resourcepolicy - (policy_id, resource_type_id, action_id, rptype, eperson_id, resource_id) - SELECT - resourcepolicy_seq.NEXTVAL AS policy_id, - '1' AS resource_type_id, - '3' AS action_id, - 'TYPE_WORKFLOW' AS rptype, - wfi.owner AS eperson_id, - i2b.bundle_id AS dspace_object - FROM workflowitem wfi - JOIN item2bundle i2b - ON i2b.item_id = wfi.item_id - JOIN metadatavalue mv - ON mv.resource_id = i2b.bundle_id - JOIN metadatafieldregistry mfr - ON mv.metadata_field_id = mfr.metadata_field_id - JOIN metadataschemaregistry msr - ON mfr.metadata_schema_id = msr.metadata_schema_id - WHERE - msr.namespace = 'http://dublincore.org/documents/dcmi-terms/' - AND mfr.element = 'title' - AND mfr.qualifier IS NULL - AND mv.text_value LIKE 'ORIGINAL' - AND wfi.owner IS NOT NULL - AND (wfi.state = 2 OR wfi.state = 4 OR wfi.state = 6) - AND NOT EXISTS( - SELECT 1 FROM resourcepolicy WHERE resource_type_id = 1 AND action_id = 3 AND resourcepolicy.eperson_id = owner AND resourcepolicy.resource_id = i2b.bundle_id - ); - -INSERT INTO resourcepolicy - (policy_id, resource_type_id, action_id, rptype, eperson_id, resource_id) - SELECT - resourcepolicy_seq.NEXTVAL AS policy_id, - '1' AS resource_type_id, - '4' AS action_id, - 'TYPE_WORKFLOW' AS rptype, - wfi.owner AS eperson_id, - i2b.bundle_id AS dspace_object - FROM workflowitem wfi - JOIN item2bundle i2b - ON i2b.item_id = wfi.item_id - JOIN metadatavalue mv - ON mv.resource_id = i2b.bundle_id - JOIN metadatafieldregistry mfr - ON mv.metadata_field_id = mfr.metadata_field_id - JOIN metadataschemaregistry msr - ON mfr.metadata_schema_id = msr.metadata_schema_id - WHERE - msr.namespace = 'http://dublincore.org/documents/dcmi-terms/' - AND mfr.element = 'title' - AND mfr.qualifier IS NULL - AND mv.text_value LIKE 'ORIGINAL' - AND wfi.owner IS NOT NULL - AND (wfi.state = 2 OR wfi.state = 4 OR wfi.state = 6) - AND NOT EXISTS( - SELECT 1 FROM resourcepolicy WHERE resource_type_id = 1 AND action_id = 4 AND resourcepolicy.eperson_id = owner AND resourcepolicy.resource_id = i2b.bundle_id - ); - - -------------------------------------------------------------------------------- --- grant read/write/delete/add/remove permission on all Bitstreams of Bundle -- --- ORIGINAL to reviewers -- -------------------------------------------------------------------------------- -INSERT INTO resourcepolicy - (policy_id, resource_type_id, action_id, rptype, eperson_id, resource_id) - SELECT - resourcepolicy_seq.NEXTVAL AS policy_id, - '0' AS resource_type_id, - '0' AS action_id, - 'TYPE_WORKFLOW' AS rptype, - wfi.owner AS eperson_id, - b2b.bitstream_id AS dspace_object - FROM workflowitem wfi - JOIN item2bundle i2b - ON i2b.item_id = wfi.item_id - JOIN bundle2bitstream b2b - ON b2b.bundle_id = i2b.bundle_id - JOIN metadatavalue mv - ON mv.resource_id = i2b.bundle_id - JOIN metadatafieldregistry mfr - ON mv.metadata_field_id = mfr.metadata_field_id - JOIN metadataschemaregistry msr - ON mfr.metadata_schema_id = msr.metadata_schema_id - WHERE - msr.namespace = 'http://dublincore.org/documents/dcmi-terms/' - AND mfr.element = 'title' - AND mfr.qualifier IS NULL - AND mv.text_value LIKE 'ORIGINAL' - AND wfi.owner IS NOT NULL - AND (wfi.state = 2 OR wfi.state = 4 OR wfi.state = 6) - AND NOT EXISTS( - SELECT 1 FROM resourcepolicy WHERE resource_type_id = 0 AND action_id = 0 AND resourcepolicy.eperson_id = owner AND resourcepolicy.resource_id = b2b.bitstream_id - ); - -INSERT INTO resourcepolicy - (policy_id, resource_type_id, action_id, rptype, eperson_id, resource_id) - SELECT - resourcepolicy_seq.NEXTVAL AS policy_id, - '0' AS resource_type_id, - '1' AS action_id, - 'TYPE_WORKFLOW' AS rptype, - wfi.owner AS eperson_id, - b2b.bitstream_id AS dspace_object - FROM workflowitem wfi - JOIN item2bundle i2b - ON i2b.item_id = wfi.item_id - JOIN bundle2bitstream b2b - ON b2b.bundle_id = i2b.bundle_id - JOIN metadatavalue mv - ON mv.resource_id = i2b.bundle_id - JOIN metadatafieldregistry mfr - ON mv.metadata_field_id = mfr.metadata_field_id - JOIN metadataschemaregistry msr - ON mfr.metadata_schema_id = msr.metadata_schema_id - WHERE - msr.namespace = 'http://dublincore.org/documents/dcmi-terms/' - AND mfr.element = 'title' - AND mfr.qualifier IS NULL - AND mv.text_value LIKE 'ORIGINAL' - AND wfi.owner IS NOT NULL - AND (wfi.state = 2 OR wfi.state = 4 OR wfi.state = 6) - AND NOT EXISTS( - SELECT 1 FROM resourcepolicy WHERE resource_type_id = 0 AND action_id = 1 AND resourcepolicy.eperson_id = owner AND resourcepolicy.resource_id = b2b.bitstream_id - ); - -INSERT INTO resourcepolicy - (policy_id, resource_type_id, action_id, rptype, eperson_id, resource_id) - SELECT - resourcepolicy_seq.NEXTVAL policy_id, - '0' AS resource_type_id, - '2' AS action_id, - 'TYPE_WORKFLOW' AS rptype, - wfi.owner AS eperson_id, - b2b.bitstream_id AS dspace_object - FROM workflowitem wfi - JOIN item2bundle i2b - ON i2b.item_id = wfi.item_id - JOIN bundle2bitstream b2b - ON b2b.bundle_id = i2b.bundle_id - JOIN metadatavalue mv - ON mv.resource_id = i2b.bundle_id - JOIN metadatafieldregistry mfr - ON mv.metadata_field_id = mfr.metadata_field_id - JOIN metadataschemaregistry msr - ON mfr.metadata_schema_id = msr.metadata_schema_id - WHERE - msr.namespace = 'http://dublincore.org/documents/dcmi-terms/' - AND mfr.element = 'title' - AND mfr.qualifier IS NULL - AND mv.text_value LIKE 'ORIGINAL' - AND wfi.owner IS NOT NULL - AND (wfi.state = 2 OR wfi.state = 4 OR wfi.state = 6) - AND NOT EXISTS( - SELECT 1 FROM resourcepolicy WHERE resource_type_id = 0 AND action_id = 2 AND resourcepolicy.eperson_id = owner AND resourcepolicy.resource_id = b2b.bitstream_id - ); - -INSERT INTO resourcepolicy - (policy_id, resource_type_id, action_id, rptype, eperson_id, resource_id) - SELECT - resourcepolicy_seq.NEXTVAL AS policy_id, - '0' AS resource_type_id, - '3' AS action_id, - 'TYPE_WORKFLOW' AS rptype, - wfi.owner AS eperson_id, - b2b.bitstream_id AS dspace_object - FROM workflowitem wfi - JOIN item2bundle i2b - ON i2b.item_id = wfi.item_id - JOIN bundle2bitstream b2b - ON b2b.bundle_id = i2b.bundle_id - JOIN metadatavalue mv - ON mv.resource_id = i2b.bundle_id - JOIN metadatafieldregistry mfr - ON mv.metadata_field_id = mfr.metadata_field_id - JOIN metadataschemaregistry msr - ON mfr.metadata_schema_id = msr.metadata_schema_id - WHERE - msr.namespace = 'http://dublincore.org/documents/dcmi-terms/' - AND mfr.element = 'title' - AND mfr.qualifier IS NULL - AND mv.text_value LIKE 'ORIGINAL' - AND wfi.owner IS NOT NULL - AND (wfi.state = 2 OR wfi.state = 4 OR wfi.state = 6) - AND NOT EXISTS( - SELECT 1 FROM resourcepolicy WHERE resource_type_id = 0 AND action_id = 3 AND resourcepolicy.eperson_id = owner AND resourcepolicy.resource_id = b2b.bitstream_id - ); - -INSERT INTO resourcepolicy - (policy_id, resource_type_id, action_id, rptype, eperson_id, resource_id) - SELECT - resourcepolicy_seq.NEXTVAL AS policy_id, - '0' AS resource_type_id, - '4' AS action_id, - 'TYPE_WORKFLOW' AS rptype, - wfi.owner AS eperson_id, - b2b.bitstream_id AS dspace_object - FROM workflowitem wfi - JOIN item2bundle i2b - ON i2b.item_id = wfi.item_id - JOIN bundle2bitstream b2b - ON b2b.bundle_id = i2b.bundle_id - JOIN metadatavalue mv - ON mv.resource_id = i2b.bundle_id - JOIN metadatafieldregistry mfr - ON mv.metadata_field_id = mfr.metadata_field_id - JOIN metadataschemaregistry msr - ON mfr.metadata_schema_id = msr.metadata_schema_id - WHERE - msr.namespace = 'http://dublincore.org/documents/dcmi-terms/' - AND mfr.element = 'title' - AND mfr.qualifier IS NULL - AND mv.text_value LIKE 'ORIGINAL' - AND wfi.owner IS NOT NULL - AND (wfi.state = 2 OR wfi.state = 4 OR wfi.state = 6) - AND NOT EXISTS( - SELECT 1 FROM resourcepolicy WHERE resource_type_id = 0 AND action_id = 4 AND resourcepolicy.eperson_id = owner AND resourcepolicy.resource_id = b2b.bitstream_id - ); diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/workflow/oracle/basicWorkflow/V6.0_2015.08.11__DS-2701_Basic_Workflow_Migration.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/workflow/oracle/basicWorkflow/V6.0_2015.08.11__DS-2701_Basic_Workflow_Migration.sql deleted file mode 100644 index 917078594cfa..000000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/workflow/oracle/basicWorkflow/V6.0_2015.08.11__DS-2701_Basic_Workflow_Migration.sql +++ /dev/null @@ -1,37 +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/ --- - ------------------------------------------------------- --- DS-2701 Service based API / Hibernate integration ------------------------------------------------------- --- Alter workflow item -ALTER TABLE workflowitem RENAME COLUMN item_id to item_legacy_id; -ALTER TABLE workflowitem ADD item_id RAW(16) REFERENCES Item(uuid); -UPDATE workflowitem SET item_id = (SELECT item.uuid FROM item WHERE workflowitem.item_legacy_id = item.item_id); -ALTER TABLE workflowitem DROP COLUMN item_legacy_id; - --- Migrate task list item -ALTER TABLE TasklistItem RENAME COLUMN eperson_id to eperson_legacy_id; -ALTER TABLE TasklistItem ADD eperson_id RAW(16) REFERENCES EPerson(uuid); -UPDATE TasklistItem SET eperson_id = (SELECT eperson.uuid FROM eperson WHERE TasklistItem.eperson_legacy_id = eperson.eperson_id); -ALTER TABLE TasklistItem DROP COLUMN eperson_legacy_id; - --- Migrate task workflow item -ALTER TABLE workflowitem RENAME COLUMN collection_id to collection_legacy_id; -ALTER TABLE workflowitem ADD collection_id RAW(16) REFERENCES Collection(uuid); -UPDATE workflowitem SET collection_id = (SELECT collection.uuid FROM collection WHERE workflowitem.collection_legacy_id = collection.collection_id); -ALTER TABLE workflowitem DROP COLUMN collection_legacy_id; -ALTER TABLE workflowitem RENAME COLUMN owner to owner_legacy_id; -ALTER TABLE workflowitem ADD owner RAW(16) REFERENCES EPerson (uuid); -UPDATE workflowitem SET owner = (SELECT eperson.uuid FROM eperson WHERE workflowitem.owner_legacy_id = eperson.eperson_id); -ALTER TABLE workflowitem DROP COLUMN owner_legacy_id; -UPDATE workflowitem SET state = -1 WHERE state IS NULL; -UPDATE workflowitem SET multiple_titles = '0' WHERE multiple_titles IS NULL; -UPDATE workflowitem SET published_before = '0' WHERE published_before IS NULL; -UPDATE workflowitem SET multiple_files = '0' WHERE multiple_files IS NULL; - diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/workflow/oracle/basicWorkflow/V6.1_2017.01.03__DS-3431.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/workflow/oracle/basicWorkflow/V6.1_2017.01.03__DS-3431.sql deleted file mode 100644 index b3887a5af4d1..000000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/workflow/oracle/basicWorkflow/V6.1_2017.01.03__DS-3431.sql +++ /dev/null @@ -1,503 +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/ --- - -------------------------------------------------------------------------- --- DS-3431 Workflow system is vulnerable to unauthorized manipulations -- -------------------------------------------------------------------------- - ------------------------------------------------------------------------ --- grant claiming permissions to all workflow step groups (step 1-3) -- ------------------------------------------------------------------------ -INSERT INTO resourcepolicy - (policy_id, resource_type_id, action_id, rptype, epersongroup_id, dspace_object) - SELECT - resourcepolicy_seq.NEXTVAL AS policy_id, - '3' AS resource_type_id, - '5' AS action_id, - 'TYPE_WORKFLOW' AS rptype, - workflow_step_1 AS epersongroup_id, - uuid AS dspace_object - FROM collection - WHERE workflow_step_1 IS NOT NULL - AND NOT EXISTS ( - SELECT 1 FROM resourcepolicy WHERE resource_type_id = 3 AND action_id = 5 AND epersongroup_id = workflow_step_1 and dspace_object = uuid - ); - -INSERT INTO resourcepolicy - (policy_id, resource_type_id, action_id, rptype, epersongroup_id, dspace_object) - SELECT - resourcepolicy_seq.NEXTVAL AS policy_id, - '3' AS resource_type_id, - '6' AS action_id, - 'TYPE_WORKFLOW' AS rptype, - workflow_step_2 AS epersongroup_id, - uuid AS dspace_object - FROM collection - WHERE workflow_step_2 IS NOT NULL - AND NOT EXISTS ( - SELECT 1 FROM resourcepolicy WHERE resource_type_id = 3 AND action_id = 6 AND epersongroup_id = workflow_step_2 and dspace_object = uuid - ); - -INSERT INTO resourcepolicy - (policy_id, resource_type_id, action_id, rptype, epersongroup_id, dspace_object) - SELECT - resourcepolicy_seq.NEXTVAL AS policy_id, - '3' AS resource_type_id, - '7' AS action_id, - 'TYPE_WORKFLOW' AS rptype, - workflow_step_3 AS epersongroup_id, - uuid AS dspace_object - FROM collection - WHERE workflow_step_3 IS NOT NULL - AND NOT EXISTS ( - SELECT 1 FROM resourcepolicy WHERE resource_type_id = 3 AND action_id = 7 AND epersongroup_id = workflow_step_3 and dspace_object = uuid - ); - ------------------------------------------------------------------------ --- grant add permissions to all workflow step groups (step 1-3) -- ------------------------------------------------------------------------ -INSERT INTO resourcepolicy -(policy_id, resource_type_id, action_id, rptype, epersongroup_id, dspace_object) - SELECT - resourcepolicy_seq.NEXTVAL AS policy_id, - '3' AS resource_type_id, - '3' AS action_id, - 'TYPE_WORKFLOW' AS rptype, - workflow_step_1 AS epersongroup_id, - uuid AS dspace_object - FROM collection - WHERE workflow_step_1 IS NOT NULL - AND NOT EXISTS ( - SELECT 1 FROM resourcepolicy WHERE resource_type_id = 3 AND action_id = 3 AND epersongroup_id = workflow_step_1 and dspace_object = uuid - ); - -INSERT INTO resourcepolicy -(policy_id, resource_type_id, action_id, rptype, epersongroup_id, dspace_object) - SELECT - resourcepolicy_seq.NEXTVAL AS policy_id, - '3' AS resource_type_id, - '3' AS action_id, - 'TYPE_WORKFLOW' AS rptype, - workflow_step_2 AS epersongroup_id, - uuid AS dspace_object - FROM collection - WHERE workflow_step_2 IS NOT NULL - AND NOT EXISTS ( - SELECT 1 FROM resourcepolicy WHERE resource_type_id = 3 AND action_id = 3 AND epersongroup_id = workflow_step_2 and dspace_object = uuid - ); - -INSERT INTO resourcepolicy -(policy_id, resource_type_id, action_id, rptype, epersongroup_id, dspace_object) - SELECT - resourcepolicy_seq.NEXTVAL AS policy_id, - '3' AS resource_type_id, - '3' AS action_id, - 'TYPE_WORKFLOW' AS rptype, - workflow_step_3 AS epersongroup_id, - uuid AS dspace_object - FROM collection - WHERE workflow_step_3 IS NOT NULL - AND NOT EXISTS ( - SELECT 1 FROM resourcepolicy WHERE resource_type_id = 3 AND action_id = 3 AND epersongroup_id = workflow_step_3 and dspace_object = uuid - ); - ----------------------------------------------------------------------------------- --- grant read/write/delete/add/remove permission on workflow items to reviewers -- ----------------------------------------------------------------------------------- -INSERT INTO resourcepolicy - (policy_id, resource_type_id, action_id, rptype, eperson_id, dspace_object) - SELECT - resourcepolicy_seq.NEXTVAL AS policy_id, - '2' AS resource_type_id, - '0' AS action_id, - 'TYPE_WORKFLOW' AS rptype, - owner AS eperson_id, - item_id AS dspace_object - FROM workflowitem - WHERE - owner IS NOT NULL - AND (state = 2 OR state = 4 OR state = 6) - AND NOT EXISTS ( - SELECT 1 FROM resourcepolicy WHERE resource_type_id = 2 AND action_id = 0 AND eperson_id = owner AND dspace_object = item_id - ); - -INSERT INTO resourcepolicy - (policy_id, resource_type_id, action_id, rptype, eperson_id, dspace_object) - SELECT - resourcepolicy_seq.NEXTVAL AS policy_id, - '2' AS resource_type_id, - '1' AS action_id, - 'TYPE_WORKFLOW' AS rptype, - owner AS eperson_id, - item_id AS dspace_object - FROM workflowitem - WHERE - owner IS NOT NULL - AND (state = 2 OR state = 4 OR state = 6) - AND NOT EXISTS ( - SELECT 1 FROM resourcepolicy WHERE resource_type_id = 2 AND action_id = 1 AND eperson_id = owner AND dspace_object = item_id - ); - -INSERT INTO resourcepolicy - (policy_id, resource_type_id, action_id, rptype, eperson_id, dspace_object) - SELECT - resourcepolicy_seq.NEXTVAL AS policy_id, - '2' AS resource_type_id, - '2' AS action_id, - 'TYPE_WORKFLOW' AS rptype, - owner AS eperson_id, - item_id AS dspace_object - FROM workflowitem - WHERE - owner IS NOT NULL - AND (state = 2 OR state = 4 OR state = 6) - AND NOT EXISTS ( - SELECT 1 FROM resourcepolicy WHERE resource_type_id = 2 AND action_id = 2 AND eperson_id = owner AND dspace_object = item_id - ); - -INSERT INTO resourcepolicy - (policy_id, resource_type_id, action_id, rptype, eperson_id, dspace_object) - SELECT - resourcepolicy_seq.NEXTVAL AS policy_id, - '2' AS resource_type_id, - '3' AS action_id, - 'TYPE_WORKFLOW' AS rptype, - owner AS eperson_id, - item_id AS dspace_object - FROM workflowitem - WHERE - owner IS NOT NULL - AND (state = 2 OR state = 4 OR state = 6) - AND NOT EXISTS ( - SELECT 1 FROM resourcepolicy WHERE resource_type_id = 2 AND action_id = 3 AND eperson_id = owner AND dspace_object = item_id - ); - -INSERT INTO resourcepolicy - (policy_id, resource_type_id, action_id, rptype, eperson_id, dspace_object) - SELECT - resourcepolicy_seq.NEXTVAL AS policy_id, - '2' AS resource_type_id, - '4' AS action_id, - 'TYPE_WORKFLOW' AS rptype, - owner AS eperson_id, - item_id AS dspace_object - FROM workflowitem - WHERE - owner IS NOT NULL - AND (state = 2 OR state = 4 OR state = 6) - AND NOT EXISTS ( - SELECT 1 FROM resourcepolicy WHERE resource_type_id = 2 AND action_id = 4 AND eperson_id = owner AND dspace_object = item_id - ); - ------------------------------------------------------------------------------------ --- grant read/write/delete/add/remove permission on Bundle ORIGINAL to reviewers -- ------------------------------------------------------------------------------------ -INSERT INTO resourcepolicy - (policy_id, resource_type_id, action_id, rptype, eperson_id, dspace_object) - SELECT - resourcepolicy_seq.NEXTVAL AS policy_id, - '1' AS resource_type_id, - '0' AS action_id, - 'TYPE_WORKFLOW' AS rptype, - wfi.owner AS eperson_id, - i2b.bundle_id AS dspace_object - FROM workflowitem wfi - JOIN item2bundle i2b - ON i2b.item_id = wfi.item_id - JOIN metadatavalue mv - ON mv.dspace_object_id = i2b.bundle_id - JOIN metadatafieldregistry mfr - ON mv.metadata_field_id = mfr.metadata_field_id - JOIN metadataschemaregistry msr - ON mfr.metadata_schema_id = msr.metadata_schema_id - WHERE - msr.namespace = 'http://dublincore.org/documents/dcmi-terms/' - AND mfr.element = 'title' - AND mfr.qualifier IS NULL - AND mv.text_value LIKE 'ORIGINAL' - AND wfi.owner IS NOT NULL - AND (wfi.state = 2 OR wfi.state = 4 OR wfi.state = 6) - AND NOT EXISTS( - SELECT 1 FROM resourcepolicy WHERE resource_type_id = 1 AND action_id = 0 AND resourcepolicy.eperson_id = owner AND resourcepolicy.dspace_object = i2b.bundle_id - ); - -INSERT INTO resourcepolicy - (policy_id, resource_type_id, action_id, rptype, eperson_id, dspace_object) - SELECT - resourcepolicy_seq.NEXTVAL AS policy_id, - '1' AS resource_type_id, - '1' AS action_id, - 'TYPE_WORKFLOW' AS rptype, - wfi.owner AS eperson_id, - i2b.bundle_id AS dspace_object - FROM workflowitem wfi - JOIN item2bundle i2b - ON i2b.item_id = wfi.item_id - JOIN metadatavalue mv - ON mv.dspace_object_id = i2b.bundle_id - JOIN metadatafieldregistry mfr - ON mv.metadata_field_id = mfr.metadata_field_id - JOIN metadataschemaregistry msr - ON mfr.metadata_schema_id = msr.metadata_schema_id - WHERE - msr.namespace = 'http://dublincore.org/documents/dcmi-terms/' - AND mfr.element = 'title' - AND mfr.qualifier IS NULL - AND mv.text_value LIKE 'ORIGINAL' - AND wfi.owner IS NOT NULL - AND (wfi.state = 2 OR wfi.state = 4 OR wfi.state = 6) - AND NOT EXISTS( - SELECT 1 FROM resourcepolicy WHERE resource_type_id = 1 AND action_id = 1 AND resourcepolicy.eperson_id = owner AND resourcepolicy.dspace_object = i2b.bundle_id - ); - -INSERT INTO resourcepolicy - (policy_id, resource_type_id, action_id, rptype, eperson_id, dspace_object) - SELECT - resourcepolicy_seq.NEXTVAL AS policy_id, - '1' AS resource_type_id, - '2' AS action_id, - 'TYPE_WORKFLOW' AS rptype, - wfi.owner AS eperson_id, - i2b.bundle_id AS dspace_object - FROM workflowitem wfi - JOIN item2bundle i2b - ON i2b.item_id = wfi.item_id - JOIN metadatavalue mv - ON mv.dspace_object_id = i2b.bundle_id - JOIN metadatafieldregistry mfr - ON mv.metadata_field_id = mfr.metadata_field_id - JOIN metadataschemaregistry msr - ON mfr.metadata_schema_id = msr.metadata_schema_id - WHERE - msr.namespace = 'http://dublincore.org/documents/dcmi-terms/' - AND mfr.element = 'title' - AND mfr.qualifier IS NULL - AND mv.text_value LIKE 'ORIGINAL' - AND wfi.owner IS NOT NULL - AND (wfi.state = 2 OR wfi.state = 4 OR wfi.state = 6) - AND NOT EXISTS( - SELECT 1 FROM resourcepolicy WHERE resource_type_id = 1 AND action_id = 2 AND resourcepolicy.eperson_id = owner AND resourcepolicy.dspace_object = i2b.bundle_id - ); - -INSERT INTO resourcepolicy - (policy_id, resource_type_id, action_id, rptype, eperson_id, dspace_object) - SELECT - resourcepolicy_seq.NEXTVAL AS policy_id, - '1' AS resource_type_id, - '3' AS action_id, - 'TYPE_WORKFLOW' AS rptype, - wfi.owner AS eperson_id, - i2b.bundle_id AS dspace_object - FROM workflowitem wfi - JOIN item2bundle i2b - ON i2b.item_id = wfi.item_id - JOIN metadatavalue mv - ON mv.dspace_object_id = i2b.bundle_id - JOIN metadatafieldregistry mfr - ON mv.metadata_field_id = mfr.metadata_field_id - JOIN metadataschemaregistry msr - ON mfr.metadata_schema_id = msr.metadata_schema_id - WHERE - msr.namespace = 'http://dublincore.org/documents/dcmi-terms/' - AND mfr.element = 'title' - AND mfr.qualifier IS NULL - AND mv.text_value LIKE 'ORIGINAL' - AND wfi.owner IS NOT NULL - AND (wfi.state = 2 OR wfi.state = 4 OR wfi.state = 6) - AND NOT EXISTS( - SELECT 1 FROM resourcepolicy WHERE resource_type_id = 1 AND action_id = 3 AND resourcepolicy.eperson_id = owner AND resourcepolicy.dspace_object = i2b.bundle_id - ); - -INSERT INTO resourcepolicy - (policy_id, resource_type_id, action_id, rptype, eperson_id, dspace_object) - SELECT - resourcepolicy_seq.NEXTVAL AS policy_id, - '1' AS resource_type_id, - '4' AS action_id, - 'TYPE_WORKFLOW' AS rptype, - wfi.owner AS eperson_id, - i2b.bundle_id AS dspace_object - FROM workflowitem wfi - JOIN item2bundle i2b - ON i2b.item_id = wfi.item_id - JOIN metadatavalue mv - ON mv.dspace_object_id = i2b.bundle_id - JOIN metadatafieldregistry mfr - ON mv.metadata_field_id = mfr.metadata_field_id - JOIN metadataschemaregistry msr - ON mfr.metadata_schema_id = msr.metadata_schema_id - WHERE - msr.namespace = 'http://dublincore.org/documents/dcmi-terms/' - AND mfr.element = 'title' - AND mfr.qualifier IS NULL - AND mv.text_value LIKE 'ORIGINAL' - AND wfi.owner IS NOT NULL - AND (wfi.state = 2 OR wfi.state = 4 OR wfi.state = 6) - AND NOT EXISTS( - SELECT 1 FROM resourcepolicy WHERE resource_type_id = 1 AND action_id = 4 AND resourcepolicy.eperson_id = owner AND resourcepolicy.dspace_object = i2b.bundle_id - ); - - -------------------------------------------------------------------------------- --- grant read/write/delete/add/remove permission on all Bitstreams of Bundle -- --- ORIGINAL to reviewers -- -------------------------------------------------------------------------------- -INSERT INTO resourcepolicy - (policy_id, resource_type_id, action_id, rptype, eperson_id, dspace_object) - SELECT - resourcepolicy_seq.NEXTVAL AS policy_id, - '0' AS resource_type_id, - '0' AS action_id, - 'TYPE_WORKFLOW' AS rptype, - wfi.owner AS eperson_id, - b2b.bitstream_id AS dspace_object - FROM workflowitem wfi - JOIN item2bundle i2b - ON i2b.item_id = wfi.item_id - JOIN bundle2bitstream b2b - ON b2b.bundle_id = i2b.bundle_id - JOIN metadatavalue mv - ON mv.dspace_object_id = i2b.bundle_id - JOIN metadatafieldregistry mfr - ON mv.metadata_field_id = mfr.metadata_field_id - JOIN metadataschemaregistry msr - ON mfr.metadata_schema_id = msr.metadata_schema_id - WHERE - msr.namespace = 'http://dublincore.org/documents/dcmi-terms/' - AND mfr.element = 'title' - AND mfr.qualifier IS NULL - AND mv.text_value LIKE 'ORIGINAL' - AND wfi.owner IS NOT NULL - AND (wfi.state = 2 OR wfi.state = 4 OR wfi.state = 6) - AND NOT EXISTS( - SELECT 1 FROM resourcepolicy WHERE resource_type_id = 0 AND action_id = 0 AND resourcepolicy.eperson_id = owner AND resourcepolicy.dspace_object = b2b.bitstream_id - ); - -INSERT INTO resourcepolicy - (policy_id, resource_type_id, action_id, rptype, eperson_id, dspace_object) - SELECT - resourcepolicy_seq.NEXTVAL AS policy_id, - '0' AS resource_type_id, - '1' AS action_id, - 'TYPE_WORKFLOW' AS rptype, - wfi.owner AS eperson_id, - b2b.bitstream_id AS dspace_object - FROM workflowitem wfi - JOIN item2bundle i2b - ON i2b.item_id = wfi.item_id - JOIN bundle2bitstream b2b - ON b2b.bundle_id = i2b.bundle_id - JOIN metadatavalue mv - ON mv.dspace_object_id = i2b.bundle_id - JOIN metadatafieldregistry mfr - ON mv.metadata_field_id = mfr.metadata_field_id - JOIN metadataschemaregistry msr - ON mfr.metadata_schema_id = msr.metadata_schema_id - WHERE - msr.namespace = 'http://dublincore.org/documents/dcmi-terms/' - AND mfr.element = 'title' - AND mfr.qualifier IS NULL - AND mv.text_value LIKE 'ORIGINAL' - AND wfi.owner IS NOT NULL - AND (wfi.state = 2 OR wfi.state = 4 OR wfi.state = 6) - AND NOT EXISTS( - SELECT 1 FROM resourcepolicy WHERE resource_type_id = 0 AND action_id = 1 AND resourcepolicy.eperson_id = owner AND resourcepolicy.dspace_object = b2b.bitstream_id - ); - -INSERT INTO resourcepolicy - (policy_id, resource_type_id, action_id, rptype, eperson_id, dspace_object) - SELECT - resourcepolicy_seq.NEXTVAL policy_id, - '0' AS resource_type_id, - '2' AS action_id, - 'TYPE_WORKFLOW' AS rptype, - wfi.owner AS eperson_id, - b2b.bitstream_id AS dspace_object - FROM workflowitem wfi - JOIN item2bundle i2b - ON i2b.item_id = wfi.item_id - JOIN bundle2bitstream b2b - ON b2b.bundle_id = i2b.bundle_id - JOIN metadatavalue mv - ON mv.dspace_object_id = i2b.bundle_id - JOIN metadatafieldregistry mfr - ON mv.metadata_field_id = mfr.metadata_field_id - JOIN metadataschemaregistry msr - ON mfr.metadata_schema_id = msr.metadata_schema_id - WHERE - msr.namespace = 'http://dublincore.org/documents/dcmi-terms/' - AND mfr.element = 'title' - AND mfr.qualifier IS NULL - AND mv.text_value LIKE 'ORIGINAL' - AND wfi.owner IS NOT NULL - AND (wfi.state = 2 OR wfi.state = 4 OR wfi.state = 6) - AND NOT EXISTS( - SELECT 1 FROM resourcepolicy WHERE resource_type_id = 0 AND action_id = 2 AND resourcepolicy.eperson_id = owner AND resourcepolicy.dspace_object = b2b.bitstream_id - ); - -INSERT INTO resourcepolicy - (policy_id, resource_type_id, action_id, rptype, eperson_id, dspace_object) - SELECT - resourcepolicy_seq.NEXTVAL AS policy_id, - '0' AS resource_type_id, - '3' AS action_id, - 'TYPE_WORKFLOW' AS rptype, - wfi.owner AS eperson_id, - b2b.bitstream_id AS dspace_object - FROM workflowitem wfi - JOIN item2bundle i2b - ON i2b.item_id = wfi.item_id - JOIN bundle2bitstream b2b - ON b2b.bundle_id = i2b.bundle_id - JOIN metadatavalue mv - ON mv.dspace_object_id = i2b.bundle_id - JOIN metadatafieldregistry mfr - ON mv.metadata_field_id = mfr.metadata_field_id - JOIN metadataschemaregistry msr - ON mfr.metadata_schema_id = msr.metadata_schema_id - WHERE - msr.namespace = 'http://dublincore.org/documents/dcmi-terms/' - AND mfr.element = 'title' - AND mfr.qualifier IS NULL - AND mv.text_value LIKE 'ORIGINAL' - AND wfi.owner IS NOT NULL - AND (wfi.state = 2 OR wfi.state = 4 OR wfi.state = 6) - AND NOT EXISTS( - SELECT 1 FROM resourcepolicy WHERE resource_type_id = 0 AND action_id = 3 AND resourcepolicy.eperson_id = owner AND resourcepolicy.dspace_object = b2b.bitstream_id - ); - -INSERT INTO resourcepolicy - (policy_id, resource_type_id, action_id, rptype, eperson_id, dspace_object) - SELECT - resourcepolicy_seq.NEXTVAL AS policy_id, - '0' AS resource_type_id, - '4' AS action_id, - 'TYPE_WORKFLOW' AS rptype, - wfi.owner AS eperson_id, - b2b.bitstream_id AS dspace_object - FROM workflowitem wfi - JOIN item2bundle i2b - ON i2b.item_id = wfi.item_id - JOIN bundle2bitstream b2b - ON b2b.bundle_id = i2b.bundle_id - JOIN metadatavalue mv - ON mv.dspace_object_id = i2b.bundle_id - JOIN metadatafieldregistry mfr - ON mv.metadata_field_id = mfr.metadata_field_id - JOIN metadataschemaregistry msr - ON mfr.metadata_schema_id = msr.metadata_schema_id - WHERE - msr.namespace = 'http://dublincore.org/documents/dcmi-terms/' - AND mfr.element = 'title' - AND mfr.qualifier IS NULL - AND mv.text_value LIKE 'ORIGINAL' - AND wfi.owner IS NOT NULL - AND (wfi.state = 2 OR wfi.state = 4 OR wfi.state = 6) - AND NOT EXISTS( - SELECT 1 FROM resourcepolicy WHERE resource_type_id = 0 AND action_id = 4 AND resourcepolicy.eperson_id = owner AND resourcepolicy.dspace_object = b2b.bitstream_id - ); diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/workflow/oracle/xmlworkflow/V6.0_2015.08.11__DS-2701_Xml_Workflow_Migration.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/workflow/oracle/xmlworkflow/V6.0_2015.08.11__DS-2701_Xml_Workflow_Migration.sql deleted file mode 100644 index 7a992836eea6..000000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/workflow/oracle/xmlworkflow/V6.0_2015.08.11__DS-2701_Xml_Workflow_Migration.sql +++ /dev/null @@ -1,141 +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/ --- - ------------------------------------------------------- --- DS-2701 Service based API / Hibernate integration ------------------------------------------------------- -UPDATE collection SET workflow_step_1 = null; -UPDATE collection SET workflow_step_2 = null; -UPDATE collection SET workflow_step_3 = null; - --- cwf_workflowitem - -DROP INDEX cwf_workflowitem_coll_fk_idx; - -ALTER TABLE cwf_workflowitem RENAME COLUMN item_id to item_legacy_id; -ALTER TABLE cwf_workflowitem ADD item_id RAW(16) REFERENCES Item(uuid); -UPDATE cwf_workflowitem SET item_id = (SELECT item.uuid FROM item WHERE cwf_workflowitem.item_legacy_id = item.item_id); -ALTER TABLE cwf_workflowitem DROP COLUMN item_legacy_id; - -ALTER TABLE cwf_workflowitem RENAME COLUMN collection_id to collection_legacy_id; -ALTER TABLE cwf_workflowitem ADD collection_id RAW(16) REFERENCES Collection(uuid); -UPDATE cwf_workflowitem SET collection_id = (SELECT collection.uuid FROM collection WHERE cwf_workflowitem.collection_legacy_id = collection.collection_id); -ALTER TABLE cwf_workflowitem DROP COLUMN collection_legacy_id; - -UPDATE cwf_workflowitem SET multiple_titles = '0' WHERE multiple_titles IS NULL; -UPDATE cwf_workflowitem SET published_before = '0' WHERE published_before IS NULL; -UPDATE cwf_workflowitem SET multiple_files = '0' WHERE multiple_files IS NULL; - -CREATE INDEX cwf_workflowitem_coll_fk_idx ON cwf_workflowitem(collection_id); - --- cwf_collectionrole - -ALTER TABLE cwf_collectionrole DROP CONSTRAINT cwf_collectionrole_unique; -DROP INDEX cwf_cr_coll_role_fk_idx; -DROP INDEX cwf_cr_coll_fk_idx; - -ALTER TABLE cwf_collectionrole RENAME COLUMN collection_id to collection_legacy_id; -ALTER TABLE cwf_collectionrole ADD collection_id RAW(16) REFERENCES Collection(uuid); -UPDATE cwf_collectionrole SET collection_id = (SELECT collection.uuid FROM collection WHERE cwf_collectionrole.collection_legacy_id = collection.collection_id); -ALTER TABLE cwf_collectionrole DROP COLUMN collection_legacy_id; - -ALTER TABLE cwf_collectionrole RENAME COLUMN group_id to group_legacy_id; -ALTER TABLE cwf_collectionrole ADD group_id RAW(16) REFERENCES epersongroup(uuid); -UPDATE cwf_collectionrole SET group_id = (SELECT epersongroup.uuid FROM epersongroup WHERE cwf_collectionrole.group_legacy_id = epersongroup.eperson_group_id); -ALTER TABLE cwf_collectionrole DROP COLUMN group_legacy_id; - -ALTER TABLE cwf_collectionrole -ADD CONSTRAINT cwf_collectionrole_unique UNIQUE (role_id, collection_id, group_id); - -CREATE INDEX cwf_cr_coll_role_fk_idx ON cwf_collectionrole(collection_id,role_id); -CREATE INDEX cwf_cr_coll_fk_idx ON cwf_collectionrole(collection_id); - - --- cwf_workflowitemrole - -ALTER TABLE cwf_workflowitemrole DROP CONSTRAINT cwf_workflowitemrole_unique; -DROP INDEX cwf_wfir_item_role_fk_idx; -DROP INDEX cwf_wfir_item_fk_idx; - -ALTER TABLE cwf_workflowitemrole RENAME COLUMN group_id to group_legacy_id; -ALTER TABLE cwf_workflowitemrole ADD group_id RAW(16) REFERENCES epersongroup(uuid); -UPDATE cwf_workflowitemrole SET group_id = (SELECT epersongroup.uuid FROM epersongroup WHERE cwf_workflowitemrole.group_legacy_id = epersongroup.eperson_group_id); -ALTER TABLE cwf_workflowitemrole DROP COLUMN group_legacy_id; - -ALTER TABLE cwf_workflowitemrole RENAME COLUMN eperson_id to eperson_legacy_id; -ALTER TABLE cwf_workflowitemrole ADD eperson_id RAW(16) REFERENCES eperson(uuid); -UPDATE cwf_workflowitemrole SET eperson_id = (SELECT eperson.uuid FROM eperson WHERE cwf_workflowitemrole.eperson_legacy_id = eperson.eperson_id); -ALTER TABLE cwf_workflowitemrole DROP COLUMN eperson_legacy_id; - - -ALTER TABLE cwf_workflowitemrole -ADD CONSTRAINT cwf_workflowitemrole_unique UNIQUE (role_id, workflowitem_id, eperson_id, group_id); - -CREATE INDEX cwf_wfir_item_role_fk_idx ON cwf_workflowitemrole(workflowitem_id,role_id); -CREATE INDEX cwf_wfir_item_fk_idx ON cwf_workflowitemrole(workflowitem_id); - --- cwf_pooltask - -DROP INDEX cwf_pt_eperson_fk_idx; -DROP INDEX cwf_pt_workflow_eperson_fk_idx; - -ALTER TABLE cwf_pooltask RENAME COLUMN group_id to group_legacy_id; -ALTER TABLE cwf_pooltask ADD group_id RAW(16) REFERENCES epersongroup(uuid); -UPDATE cwf_pooltask SET group_id = (SELECT epersongroup.uuid FROM epersongroup WHERE cwf_pooltask.group_legacy_id = epersongroup.eperson_group_id); -ALTER TABLE cwf_pooltask DROP COLUMN group_legacy_id; - -ALTER TABLE cwf_pooltask RENAME COLUMN eperson_id to eperson_legacy_id; -ALTER TABLE cwf_pooltask ADD eperson_id RAW(16) REFERENCES eperson(uuid); -UPDATE cwf_pooltask SET eperson_id = (SELECT eperson.uuid FROM eperson WHERE cwf_pooltask.eperson_legacy_id = eperson.eperson_id); -ALTER TABLE cwf_pooltask DROP COLUMN eperson_legacy_id; - -CREATE INDEX cwf_pt_eperson_fk_idx ON cwf_pooltask(eperson_id); -CREATE INDEX cwf_pt_workflow_eperson_fk_idx ON cwf_pooltask(eperson_id,workflowitem_id); - --- cwf_claimtask - -ALTER TABLE cwf_claimtask DROP CONSTRAINT cwf_claimtask_unique; -DROP INDEX cwf_ct_workflow_fk_idx; -DROP INDEX cwf_ct_workflow_eperson_fk_idx; -DROP INDEX cwf_ct_eperson_fk_idx; -DROP INDEX cwf_ct_wfs_fk_idx; -DROP INDEX cwf_ct_wfs_action_fk_idx; -DROP INDEX cwf_ct_wfs_action_e_fk_idx; - -ALTER TABLE cwf_claimtask RENAME COLUMN owner_id to eperson_legacy_id; -ALTER TABLE cwf_claimtask ADD owner_id RAW(16) REFERENCES eperson(uuid); -UPDATE cwf_claimtask SET owner_id = (SELECT eperson.uuid FROM eperson WHERE cwf_claimtask.eperson_legacy_id = eperson.eperson_id); -ALTER TABLE cwf_claimtask DROP COLUMN eperson_legacy_id; - -ALTER TABLE cwf_claimtask -ADD CONSTRAINT cwf_claimtask_unique UNIQUE (step_id, workflowitem_id, workflow_id, owner_id, action_id); - -CREATE INDEX cwf_ct_workflow_fk_idx ON cwf_claimtask(workflowitem_id); -CREATE INDEX cwf_ct_workflow_eperson_fk_idx ON cwf_claimtask(workflowitem_id,owner_id); -CREATE INDEX cwf_ct_eperson_fk_idx ON cwf_claimtask(owner_id); -CREATE INDEX cwf_ct_wfs_fk_idx ON cwf_claimtask(workflowitem_id,step_id); -CREATE INDEX cwf_ct_wfs_action_fk_idx ON cwf_claimtask(workflowitem_id,step_id,action_id); -CREATE INDEX cwf_ct_wfs_action_e_fk_idx ON cwf_claimtask(workflowitem_id,step_id,action_id,owner_id); - --- cwf_in_progress_user - -ALTER TABLE cwf_in_progress_user DROP CONSTRAINT cwf_in_progress_user_unique; -DROP INDEX cwf_ipu_workflow_fk_idx; -DROP INDEX cwf_ipu_eperson_fk_idx; - -ALTER TABLE cwf_in_progress_user RENAME COLUMN user_id to eperson_legacy_id; -ALTER TABLE cwf_in_progress_user ADD user_id RAW(16) REFERENCES eperson(uuid); -UPDATE cwf_in_progress_user SET user_id = (SELECT eperson.uuid FROM eperson WHERE cwf_in_progress_user.eperson_legacy_id = eperson.eperson_id); -ALTER TABLE cwf_in_progress_user DROP COLUMN eperson_legacy_id; -UPDATE cwf_in_progress_user SET finished = '0' WHERE finished IS NULL; - -ALTER TABLE cwf_in_progress_user -ADD CONSTRAINT cwf_in_progress_user_unique UNIQUE (workflowitem_id, user_id); - -CREATE INDEX cwf_ipu_workflow_fk_idx ON cwf_in_progress_user(workflowitem_id); -CREATE INDEX cwf_ipu_eperson_fk_idx ON cwf_in_progress_user(user_id); \ No newline at end of file diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/workflow/oracle/xmlworkflow/V7.0_2018.04.03__upgrade_workflow_policy.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/workflow/oracle/xmlworkflow/V7.0_2018.04.03__upgrade_workflow_policy.sql deleted file mode 100644 index 0402fc994887..000000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/workflow/oracle/xmlworkflow/V7.0_2018.04.03__upgrade_workflow_policy.sql +++ /dev/null @@ -1,27 +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/ --- - --- UPDATE policies for claimtasks --- Item -UPDATE RESOURCEPOLICY SET rptype = 'TYPE_WORKFLOW' WHERE dspace_object in (SELECT cwf_workflowitem.item_id FROM cwf_workflowitem INNER JOIN cwf_claimtask ON cwf_workflowitem.workflowitem_id = cwf_claimtask.workflowitem_id JOIN item ON cwf_workflowitem.item_id = item.uuid) AND eperson_id not in (SELECT item.submitter_id FROM cwf_workflowitem JOIN item ON cwf_workflowitem.item_id = item.uuid); - --- Bundles -UPDATE RESOURCEPOLICY SET rptype = 'TYPE_WORKFLOW' WHERE dspace_object in (SELECT item2bundle.bundle_id FROM cwf_workflowitem INNER JOIN cwf_claimtask ON cwf_workflowitem.workflowitem_id = cwf_claimtask.workflowitem_id INNER JOIN item2bundle ON cwf_workflowitem.item_id = item2bundle.item_id) AND eperson_id not in (SELECT item.submitter_id FROM cwf_workflowitem JOIN item ON cwf_workflowitem.item_id = item.uuid); - --- Bitstreams -UPDATE RESOURCEPOLICY SET rptype = 'TYPE_WORKFLOW' WHERE dspace_object in (SELECT bundle2bitstream.bitstream_id FROM cwf_workflowitem INNER JOIN cwf_claimtask ON cwf_workflowitem.workflowitem_id = cwf_claimtask.workflowitem_id INNER JOIN item2bundle ON cwf_workflowitem.item_id = item2bundle.item_id INNER JOIN bundle2bitstream ON item2bundle.bundle_id = bundle2bitstream.bundle_id) AND eperson_id not in (SELECT item.submitter_id FROM cwf_workflowitem JOIN item ON cwf_workflowitem.item_id = item.uuid); - --- Create policies for pooled tasks --- Item -UPDATE RESOURCEPOLICY SET rptype = 'TYPE_WORKFLOW' WHERE dspace_object in (SELECT cwf_workflowitem.item_id FROM cwf_workflowitem INNER JOIN cwf_pooltask ON cwf_workflowitem.workflowitem_id = cwf_pooltask.workflowitem_id) AND eperson_id not in (SELECT item.submitter_id FROM cwf_workflowitem JOIN item ON cwf_workflowitem.item_id = item.uuid); - --- Bundles -UPDATE RESOURCEPOLICY SET rptype = 'TYPE_WORKFLOW' WHERE dspace_object in (SELECT cwf_workflowitem.item_id FROM cwf_workflowitem INNER JOIN cwf_pooltask ON cwf_workflowitem.workflowitem_id = cwf_pooltask.workflowitem_id INNER JOIN item2bundle ON cwf_workflowitem.item_id = item2bundle.item_id) AND eperson_id not in (SELECT item.submitter_id FROM cwf_workflowitem JOIN item ON cwf_workflowitem.item_id = item.uuid); - --- Bitstreams -UPDATE RESOURCEPOLICY SET rptype = 'TYPE_WORKFLOW' WHERE dspace_object in (SELECT cwf_workflowitem.item_id FROM cwf_workflowitem INNER JOIN cwf_pooltask ON cwf_workflowitem.workflowitem_id = cwf_pooltask.workflowitem_id INNER JOIN item2bundle ON cwf_workflowitem.item_id = item2bundle.item_id INNER JOIN bundle2bitstream ON item2bundle.bundle_id = bundle2bitstream.bundle_id) AND eperson_id not in (SELECT item.submitter_id FROM cwf_workflowitem JOIN item ON cwf_workflowitem.item_id = item.uuid); diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/xmlworkflow/oracle/data_workflow_migration.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/xmlworkflow/oracle/data_workflow_migration.sql deleted file mode 100644 index f582f37c6931..000000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/xmlworkflow/oracle/data_workflow_migration.sql +++ /dev/null @@ -1,377 +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/ --- - ----------------------------------------------------- --- Data Migration for XML/Configurable Workflow --- --- This file will automatically migrate existing --- classic workflows to XML/Configurable workflows. --- NOTE however that the corresponding --- "xml_workflow_migration.sql" script must FIRST be --- called to create the appropriate database tables. --- --- This script is called automatically by the following --- Flyway Java migration class: --- org.dspace.storage.rdbms.migration.V5_0_2014_01_01__XMLWorkflow_Migration ----------------------------------------------------- - --- Convert workflow groups: --- TODO: is 'to_number' ok? do not forget to change role_id values - -INSERT INTO cwf_collectionrole (collectionrole_id, role_id, group_id, collection_id) -SELECT -cwf_collectionrole_seq.nextval as collectionrole_id, -'reviewer' AS role_id, -collection.workflow_step_1 AS group_id, -collection.collection_id AS collection_id -FROM collection -WHERE collection.workflow_step_1 IS NOT NULL; - -INSERT INTO cwf_collectionrole (collectionrole_id, role_id, group_id, collection_id) -SELECT -cwf_collectionrole_seq.nextval as collectionrole_id, -'editor' AS role_id, -collection.workflow_step_2 AS group_id, -collection.collection_id AS collection_id -FROM collection -WHERE collection.workflow_step_2 IS NOT NULL; - -INSERT INTO cwf_collectionrole (collectionrole_id, role_id, group_id, collection_id) -SELECT -cwf_collectionrole_seq.nextval as collectionrole_id, -'finaleditor' AS role_id, -collection.workflow_step_3 AS group_id, -collection.collection_id AS collection_id -FROM collection -WHERE collection.workflow_step_3 IS NOT NULL; - - --- Migrate workflow items -INSERT INTO cwf_workflowitem (workflowitem_id, item_id, collection_id, multiple_titles, published_before, multiple_files) -SELECT -workflow_id AS workflowitem_id, -item_id, -collection_id, -multiple_titles, -published_before, -multiple_files -FROM workflowitem; - - --- Migrate claimed tasks -INSERT INTO cwf_claimtask (claimtask_id,workflowitem_id, workflow_id, step_id, action_id, owner_id) -SELECT -cwf_claimtask_seq.nextval AS claimtask_id, -workflow_id AS workflowitem_id, -'default' AS workflow_id, -'reviewstep' AS step_id, -'reviewaction' AS action_id, -owner AS owner_id -FROM workflowitem WHERE owner IS NOT NULL AND state = 2; - -INSERT INTO cwf_claimtask (claimtask_id,workflowitem_id, workflow_id, step_id, action_id, owner_id) -SELECT -cwf_claimtask_seq.nextval AS claimtask_id, -workflow_id AS workflowitem_id, -'default' AS workflow_id, -'editstep' AS step_id, -'editaction' AS action_id, -owner AS owner_id -FROM workflowitem WHERE owner IS NOT NULL AND state = 4; - -INSERT INTO cwf_claimtask (claimtask_id,workflowitem_id, workflow_id, step_id, action_id, owner_id) -SELECT -cwf_claimtask_seq.nextval AS claimtask_id, -workflow_id AS workflowitem_id, -'default' AS workflow_id, -'finaleditstep' AS step_id, -'finaleditaction' AS action_id, -owner AS owner_id -FROM workflowitem WHERE owner IS NOT NULL AND state = 6; - - --- Migrate pooled tasks -INSERT INTO cwf_pooltask (pooltask_id,workflowitem_id, workflow_id, step_id, action_id, group_id) -SELECT -cwf_pooltask_seq.nextval AS pooltask_id, -workflowitem.workflow_id AS workflowitem_id, -'default' AS workflow_id, -'reviewstep' AS step_id, -'claimaction' AS action_id, -cwf_collectionrole.group_id AS group_id -FROM workflowitem INNER JOIN cwf_collectionrole ON workflowitem.collection_id = cwf_collectionrole.collection_id -WHERE workflowitem.owner IS NULL AND workflowitem.state = 1 AND cwf_collectionrole.role_id = 'reviewer'; - -INSERT INTO cwf_pooltask (pooltask_id,workflowitem_id, workflow_id, step_id, action_id, group_id) -SELECT -cwf_pooltask_seq.nextval AS pooltask_id, -workflowitem.workflow_id AS workflowitem_id, -'default' AS workflow_id, -'editstep' AS step_id, -'claimaction' AS action_id, -cwf_collectionrole.group_id AS group_id -FROM workflowitem INNER JOIN cwf_collectionrole ON workflowitem.collection_id = cwf_collectionrole.collection_id -WHERE workflowitem.owner IS NULL AND workflowitem.state = 3 AND cwf_collectionrole.role_id = 'editor'; - -INSERT INTO cwf_pooltask (pooltask_id,workflowitem_id, workflow_id, step_id, action_id, group_id) -SELECT -cwf_pooltask_seq.nextval AS pooltask_id, -workflowitem.workflow_id AS workflowitem_id, -'default' AS workflow_id, -'finaleditstep' AS step_id, -'claimaction' AS action_id, -cwf_collectionrole.group_id AS group_id -FROM workflowitem INNER JOIN cwf_collectionrole ON workflowitem.collection_id = cwf_collectionrole.collection_id -WHERE workflowitem.owner IS NULL AND workflowitem.state = 5 AND cwf_collectionrole.role_id = 'finaleditor'; - --- Delete resource policies for workflowitems before creating new ones -DELETE FROM resourcepolicy -WHERE resource_type_id = 2 AND resource_id IN - (SELECT item_id FROM workflowitem); - -DELETE FROM resourcepolicy -WHERE resource_type_id = 1 AND resource_id IN - (SELECT item2bundle.bundle_id FROM - (workflowitem INNER JOIN item2bundle ON workflowitem.item_id = item2bundle.item_id)); - -DELETE FROM resourcepolicy -WHERE resource_type_id = 0 AND resource_id IN - (SELECT bundle2bitstream.bitstream_id FROM - ((workflowitem INNER JOIN item2bundle ON workflowitem.item_id = item2bundle.item_id) - INNER JOIN bundle2bitstream ON item2bundle.bundle_id = bundle2bitstream.bundle_id)); --- Create policies for claimtasks --- public static final int BITSTREAM = 0; --- public static final int BUNDLE = 1; --- public static final int ITEM = 2; - --- public static final int READ = 0; --- public static final int WRITE = 1; --- public static final int DELETE = 2; --- public static final int ADD = 3; --- public static final int REMOVE = 4; --- Item --- TODO: getnextID == SELECT sequence.nextval FROM DUAL!! --- Create a temporarty table with action ID's -CREATE TABLE temptable( - action_id INTEGER PRIMARY KEY -); -INSERT ALL - INTO temptable (action_id) VALUES (0) - INTO temptable (action_id) VALUES (1) - INTO temptable (action_id) VALUES (2) - INTO temptable (action_id) VALUES (3) - INTO temptable (action_id) VALUES (4) -SELECT * FROM DUAL; - -INSERT INTO resourcepolicy (policy_id, resource_type_id, resource_id, action_id, eperson_id) -SELECT -resourcepolicy_seq.nextval AS policy_id, -2 AS resource_type_id, -cwf_workflowitem.item_id AS resource_id, -temptable.action_id AS action_id, -cwf_claimtask.owner_id AS eperson_id -FROM (cwf_workflowitem INNER JOIN cwf_claimtask ON cwf_workflowitem.workflowitem_id = cwf_claimtask.workflowitem_id), -temptable; - --- Bundles -INSERT INTO resourcepolicy (policy_id, resource_type_id, resource_id, action_id, eperson_id) -SELECT -resourcepolicy_seq.nextval AS policy_id, -1 AS resource_type_id, -item2bundle.bundle_id AS resource_id, -temptable.action_id AS action_id, -cwf_claimtask.owner_id AS eperson_id -FROM -( - (cwf_workflowitem INNER JOIN cwf_claimtask ON cwf_workflowitem.workflowitem_id = cwf_claimtask.workflowitem_id) - INNER JOIN item2bundle ON cwf_workflowitem.item_id = item2bundle.item_id -), temptable; - - --- Bitstreams -INSERT INTO resourcepolicy (policy_id, resource_type_id, resource_id, action_id, eperson_id) -SELECT -resourcepolicy_seq.nextval AS policy_id, -0 AS resource_type_id, -bundle2bitstream.bitstream_id AS resource_id, -temptable.action_id AS action_id, -cwf_claimtask.owner_id AS eperson_id -FROM -( - ((cwf_workflowitem INNER JOIN cwf_claimtask ON cwf_workflowitem.workflowitem_id = cwf_claimtask.workflowitem_id) - INNER JOIN item2bundle ON cwf_workflowitem.item_id = item2bundle.item_id) - INNER JOIN bundle2bitstream ON item2bundle.bundle_id = bundle2bitstream.bundle_id -), temptable; - - --- Create policies for pooled tasks - -INSERT INTO resourcepolicy (policy_id, resource_type_id, resource_id, action_id, epersongroup_id) -SELECT -resourcepolicy_seq.nextval AS policy_id, -2 AS resource_type_id, -cwf_workflowitem.item_id AS resource_id, -temptable.action_id AS action_id, -cwf_pooltask.group_id AS epersongroup_id -FROM (cwf_workflowitem INNER JOIN cwf_pooltask ON cwf_workflowitem.workflowitem_id = cwf_pooltask.workflowitem_id), -temptable; - --- Bundles -INSERT INTO resourcepolicy (policy_id, resource_type_id, resource_id, action_id, epersongroup_id) -SELECT -resourcepolicy_seq.nextval AS policy_id, -1 AS resource_type_id, -item2bundle.bundle_id AS resource_id, -temptable.action_id AS action_id, -cwf_pooltask.group_id AS epersongroup_id -FROM -( - (cwf_workflowitem INNER JOIN cwf_pooltask ON cwf_workflowitem.workflowitem_id = cwf_pooltask.workflowitem_id) - INNER JOIN item2bundle ON cwf_workflowitem.item_id = item2bundle.item_id -), temptable; - --- Bitstreams -INSERT INTO resourcepolicy (policy_id, resource_type_id, resource_id, action_id, epersongroup_id) -SELECT -resourcepolicy_seq.nextval AS policy_id, -0 AS resource_type_id, -bundle2bitstream.bitstream_id AS resource_id, -temptable.action_id AS action_id, -cwf_pooltask.group_id AS epersongroup_id -FROM -( - ((cwf_workflowitem INNER JOIN cwf_pooltask ON cwf_workflowitem.workflowitem_id = cwf_pooltask.workflowitem_id) - INNER JOIN item2bundle ON cwf_workflowitem.item_id = item2bundle.item_id) - INNER JOIN bundle2bitstream ON item2bundle.bundle_id = bundle2bitstream.bundle_id -), temptable; - --- Drop the temporary table with the action ID's -DROP TABLE temptable; - --- Create policies for submitter --- TODO: only add if unique -INSERT INTO resourcepolicy (policy_id, resource_type_id, resource_id, action_id, eperson_id) -SELECT -resourcepolicy_seq.nextval AS policy_id, -2 AS resource_type_id, -cwf_workflowitem.item_id AS resource_id, -0 AS action_id, -item.submitter_id AS eperson_id -FROM (cwf_workflowitem INNER JOIN item ON cwf_workflowitem.item_id = item.item_id); - -INSERT INTO resourcepolicy (policy_id, resource_type_id, resource_id, action_id, eperson_id) -SELECT -resourcepolicy_seq.nextval AS policy_id, -1 AS resource_type_id, -item2bundle.bundle_id AS resource_id, -0 AS action_id, -item.submitter_id AS eperson_id -FROM ((cwf_workflowitem INNER JOIN item ON cwf_workflowitem.item_id = item.item_id) - INNER JOIN item2bundle ON cwf_workflowitem.item_id = item2bundle.item_id - ); - -INSERT INTO resourcepolicy (policy_id, resource_type_id, resource_id, action_id, eperson_id) -SELECT -resourcepolicy_seq.nextval AS policy_id, -0 AS resource_type_id, -bundle2bitstream.bitstream_id AS resource_id, -0 AS action_id, -item.submitter_id AS eperson_id -FROM (((cwf_workflowitem INNER JOIN item ON cwf_workflowitem.item_id = item.item_id) - INNER JOIN item2bundle ON cwf_workflowitem.item_id = item2bundle.item_id) - INNER JOIN bundle2bitstream ON item2bundle.bundle_id = bundle2bitstream.bundle_id -); - --- TODO: not tested yet -INSERT INTO cwf_in_progress_user (in_progress_user_id, workflowitem_id, user_id, finished) -SELECT - cwf_in_progress_user_seq.nextval AS in_progress_user_id, - cwf_workflowitem.workflowitem_id AS workflowitem_id, - cwf_claimtask.owner_id AS user_id, - 0 as finished -FROM - (cwf_claimtask INNER JOIN cwf_workflowitem ON cwf_workflowitem.workflowitem_id = cwf_claimtask.workflowitem_id); - --- TODO: improve this, important is NVL(curr, 1)!! without this function, empty tables (max = [null]) will only result in sequence deletion -DECLARE - curr NUMBER := 0; -BEGIN - SELECT max(workflowitem_id) INTO curr FROM cwf_workflowitem; - - curr := curr + 1; - - EXECUTE IMMEDIATE 'DROP SEQUENCE cwf_workflowitem_seq'; - - EXECUTE IMMEDIATE 'CREATE SEQUENCE cwf_workflowitem_seq START WITH ' || NVL(curr, 1); -END; -/ - -DECLARE - curr NUMBER := 0; -BEGIN - SELECT max(collectionrole_id) INTO curr FROM cwf_collectionrole; - - curr := curr + 1; - - EXECUTE IMMEDIATE 'DROP SEQUENCE cwf_collectionrole_seq'; - - EXECUTE IMMEDIATE 'CREATE SEQUENCE cwf_collectionrole_seq START WITH ' || NVL(curr, 1); -END; -/ - -DECLARE - curr NUMBER := 0; -BEGIN - SELECT max(workflowitemrole_id) INTO curr FROM cwf_workflowitemrole; - - curr := curr + 1; - - EXECUTE IMMEDIATE 'DROP SEQUENCE cwf_workflowitemrole_seq'; - - EXECUTE IMMEDIATE 'CREATE SEQUENCE cwf_workflowitemrole_seq START WITH ' || NVL(curr, 1); -END; -/ - -DECLARE - curr NUMBER := 0; -BEGIN - SELECT max(pooltask_id) INTO curr FROM cwf_pooltask; - - curr := curr + 1; - - EXECUTE IMMEDIATE 'DROP SEQUENCE cwf_pooltask_seq'; - - EXECUTE IMMEDIATE 'CREATE SEQUENCE cwf_pooltask_seq START WITH ' || NVL(curr, 1); -END; -/ - -DECLARE - curr NUMBER := 0; -BEGIN - SELECT max(claimtask_id) INTO curr FROM cwf_claimtask; - - curr := curr + 1; - - EXECUTE IMMEDIATE 'DROP SEQUENCE cwf_claimtask_seq'; - - EXECUTE IMMEDIATE 'CREATE SEQUENCE cwf_claimtask_seq START WITH ' || NVL(curr, 1); -END; -/ - -DECLARE - curr NUMBER := 0; -BEGIN - SELECT max(in_progress_user_id) INTO curr FROM cwf_in_progress_user; - - curr := curr + 1; - - EXECUTE IMMEDIATE 'DROP SEQUENCE cwf_in_progress_user_seq'; - - EXECUTE IMMEDIATE 'CREATE SEQUENCE cwf_in_progress_user_seq START WITH ' || NVL(curr, 1); -END; -/ diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/xmlworkflow/oracle/v6.0__DS-2701_data_workflow_migration.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/xmlworkflow/oracle/v6.0__DS-2701_data_workflow_migration.sql deleted file mode 100644 index 70eb419d8fbb..000000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/xmlworkflow/oracle/v6.0__DS-2701_data_workflow_migration.sql +++ /dev/null @@ -1,377 +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/ --- - ----------------------------------------------------- --- Data Migration for XML/Configurable Workflow --- --- This file will automatically migrate existing --- classic workflows to XML/Configurable workflows. --- NOTE however that the corresponding --- "xml_workflow_migration.sql" script must FIRST be --- called to create the appropriate database tables. --- --- This script is called automatically by the following --- Flyway Java migration class: --- org.dspace.storage.rdbms.xmlworkflow.V6_0_2015_09_01__DS_2701_Enable_XMLWorkflow_Migration ----------------------------------------------------- - --- Convert workflow groups: --- TODO: is 'to_number' ok? do not forget to change role_id values - -INSERT INTO cwf_collectionrole (collectionrole_id, role_id, group_id, collection_id) -SELECT -cwf_collectionrole_seq.nextval as collectionrole_id, -'reviewer' AS role_id, -collection.workflow_step_1 AS group_id, -collection.uuid AS collection_id -FROM collection -WHERE collection.workflow_step_1 IS NOT NULL; - -INSERT INTO cwf_collectionrole (collectionrole_id, role_id, group_id, collection_id) -SELECT -cwf_collectionrole_seq.nextval as collectionrole_id, -'editor' AS role_id, -collection.workflow_step_2 AS group_id, -collection.uuid AS collection_id -FROM collection -WHERE collection.workflow_step_2 IS NOT NULL; - -INSERT INTO cwf_collectionrole (collectionrole_id, role_id, group_id, collection_id) -SELECT -cwf_collectionrole_seq.nextval as collectionrole_id, -'finaleditor' AS role_id, -collection.workflow_step_3 AS group_id, -collection.uuid AS collection_id -FROM collection -WHERE collection.workflow_step_3 IS NOT NULL; - - --- Migrate workflow items -INSERT INTO cwf_workflowitem (workflowitem_id, item_id, collection_id, multiple_titles, published_before, multiple_files) -SELECT -workflow_id AS workflowitem_id, -item_id, -collection_id, -multiple_titles, -published_before, -multiple_files -FROM workflowitem; - - --- Migrate claimed tasks -INSERT INTO cwf_claimtask (claimtask_id,workflowitem_id, workflow_id, step_id, action_id, owner_id) -SELECT -cwf_claimtask_seq.nextval AS claimtask_id, -workflow_id AS workflowitem_id, -'default' AS workflow_id, -'reviewstep' AS step_id, -'reviewaction' AS action_id, -owner AS owner_id -FROM workflowitem WHERE owner IS NOT NULL AND state = 2; - -INSERT INTO cwf_claimtask (claimtask_id,workflowitem_id, workflow_id, step_id, action_id, owner_id) -SELECT -cwf_claimtask_seq.nextval AS claimtask_id, -workflow_id AS workflowitem_id, -'default' AS workflow_id, -'editstep' AS step_id, -'editaction' AS action_id, -owner AS owner_id -FROM workflowitem WHERE owner IS NOT NULL AND state = 4; - -INSERT INTO cwf_claimtask (claimtask_id,workflowitem_id, workflow_id, step_id, action_id, owner_id) -SELECT -cwf_claimtask_seq.nextval AS claimtask_id, -workflow_id AS workflowitem_id, -'default' AS workflow_id, -'finaleditstep' AS step_id, -'finaleditaction' AS action_id, -owner AS owner_id -FROM workflowitem WHERE owner IS NOT NULL AND state = 6; - - --- Migrate pooled tasks -INSERT INTO cwf_pooltask (pooltask_id,workflowitem_id, workflow_id, step_id, action_id, group_id) -SELECT -cwf_pooltask_seq.nextval AS pooltask_id, -workflowitem.workflow_id AS workflowitem_id, -'default' AS workflow_id, -'reviewstep' AS step_id, -'claimaction' AS action_id, -cwf_collectionrole.group_id AS group_id -FROM workflowitem INNER JOIN cwf_collectionrole ON workflowitem.collection_id = cwf_collectionrole.collection_id -WHERE workflowitem.owner IS NULL AND workflowitem.state = 1 AND cwf_collectionrole.role_id = 'reviewer'; - -INSERT INTO cwf_pooltask (pooltask_id,workflowitem_id, workflow_id, step_id, action_id, group_id) -SELECT -cwf_pooltask_seq.nextval AS pooltask_id, -workflowitem.workflow_id AS workflowitem_id, -'default' AS workflow_id, -'editstep' AS step_id, -'claimaction' AS action_id, -cwf_collectionrole.group_id AS group_id -FROM workflowitem INNER JOIN cwf_collectionrole ON workflowitem.collection_id = cwf_collectionrole.collection_id -WHERE workflowitem.owner IS NULL AND workflowitem.state = 3 AND cwf_collectionrole.role_id = 'editor'; - -INSERT INTO cwf_pooltask (pooltask_id,workflowitem_id, workflow_id, step_id, action_id, group_id) -SELECT -cwf_pooltask_seq.nextval AS pooltask_id, -workflowitem.workflow_id AS workflowitem_id, -'default' AS workflow_id, -'finaleditstep' AS step_id, -'claimaction' AS action_id, -cwf_collectionrole.group_id AS group_id -FROM workflowitem INNER JOIN cwf_collectionrole ON workflowitem.collection_id = cwf_collectionrole.collection_id -WHERE workflowitem.owner IS NULL AND workflowitem.state = 5 AND cwf_collectionrole.role_id = 'finaleditor'; - --- Delete resource policies for workflowitems before creating new ones -DELETE FROM resourcepolicy -WHERE dspace_object IN - (SELECT item_id FROM workflowitem); - -DELETE FROM resourcepolicy -WHERE dspace_object IN - (SELECT item2bundle.bundle_id FROM - (workflowitem INNER JOIN item2bundle ON workflowitem.item_id = item2bundle.item_id)); - -DELETE FROM resourcepolicy -WHERE dspace_object IN - (SELECT bundle2bitstream.bitstream_id FROM - ((workflowitem INNER JOIN item2bundle ON workflowitem.item_id = item2bundle.item_id) - INNER JOIN bundle2bitstream ON item2bundle.bundle_id = bundle2bitstream.bundle_id)); --- Create policies for claimtasks --- public static final int BITSTREAM = 0; --- public static final int BUNDLE = 1; --- public static final int ITEM = 2; - --- public static final int READ = 0; --- public static final int WRITE = 1; --- public static final int DELETE = 2; --- public static final int ADD = 3; --- public static final int REMOVE = 4; --- Item --- TODO: getnextID == SELECT sequence.nextval FROM DUAL!! --- Create a temporarty table with action ID's -CREATE TABLE temptable( - action_id INTEGER PRIMARY KEY -); -INSERT ALL - INTO temptable (action_id) VALUES (0) - INTO temptable (action_id) VALUES (1) - INTO temptable (action_id) VALUES (2) - INTO temptable (action_id) VALUES (3) - INTO temptable (action_id) VALUES (4) -SELECT * FROM DUAL; - -INSERT INTO resourcepolicy (policy_id, resource_type_id, dspace_object, action_id, eperson_id) -SELECT -resourcepolicy_seq.nextval AS policy_id, -2 AS resource_type_id, -cwf_workflowitem.item_id AS dspace_object, -temptable.action_id AS action_id, -cwf_claimtask.owner_id AS eperson_id -FROM (cwf_workflowitem INNER JOIN cwf_claimtask ON cwf_workflowitem.workflowitem_id = cwf_claimtask.workflowitem_id), -temptable; - --- Bundles -INSERT INTO resourcepolicy (policy_id, resource_type_id, dspace_object, action_id, eperson_id) -SELECT -resourcepolicy_seq.nextval AS policy_id, -1 AS resource_type_id, -item2bundle.bundle_id AS dspace_object, -temptable.action_id AS action_id, -cwf_claimtask.owner_id AS eperson_id -FROM -( - (cwf_workflowitem INNER JOIN cwf_claimtask ON cwf_workflowitem.workflowitem_id = cwf_claimtask.workflowitem_id) - INNER JOIN item2bundle ON cwf_workflowitem.item_id = item2bundle.item_id -), temptable; - - --- Bitstreams -INSERT INTO resourcepolicy (policy_id, resource_type_id, dspace_object, action_id, eperson_id) -SELECT -resourcepolicy_seq.nextval AS policy_id, -0 AS resource_type_id, -bundle2bitstream.bitstream_id AS dspace_object, -temptable.action_id AS action_id, -cwf_claimtask.owner_id AS eperson_id -FROM -( - ((cwf_workflowitem INNER JOIN cwf_claimtask ON cwf_workflowitem.workflowitem_id = cwf_claimtask.workflowitem_id) - INNER JOIN item2bundle ON cwf_workflowitem.item_id = item2bundle.item_id) - INNER JOIN bundle2bitstream ON item2bundle.bundle_id = bundle2bitstream.bundle_id -), temptable; - - --- Create policies for pooled tasks - -INSERT INTO resourcepolicy (policy_id, resource_type_id, dspace_object, action_id, epersongroup_id) -SELECT -resourcepolicy_seq.nextval AS policy_id, -2 AS resource_type_id, -cwf_workflowitem.item_id AS dspace_object, -temptable.action_id AS action_id, -cwf_pooltask.group_id AS epersongroup_id -FROM (cwf_workflowitem INNER JOIN cwf_pooltask ON cwf_workflowitem.workflowitem_id = cwf_pooltask.workflowitem_id), -temptable; - --- Bundles -INSERT INTO resourcepolicy (policy_id, resource_type_id, dspace_object, action_id, epersongroup_id) -SELECT -resourcepolicy_seq.nextval AS policy_id, -1 AS resource_type_id, -item2bundle.bundle_id AS dspace_object, -temptable.action_id AS action_id, -cwf_pooltask.group_id AS epersongroup_id -FROM -( - (cwf_workflowitem INNER JOIN cwf_pooltask ON cwf_workflowitem.workflowitem_id = cwf_pooltask.workflowitem_id) - INNER JOIN item2bundle ON cwf_workflowitem.item_id = item2bundle.item_id -), temptable; - --- Bitstreams -INSERT INTO resourcepolicy (policy_id, resource_type_id, dspace_object, action_id, epersongroup_id) -SELECT -resourcepolicy_seq.nextval AS policy_id, -0 AS resource_type_id, -bundle2bitstream.bitstream_id AS dspace_object, -temptable.action_id AS action_id, -cwf_pooltask.group_id AS epersongroup_id -FROM -( - ((cwf_workflowitem INNER JOIN cwf_pooltask ON cwf_workflowitem.workflowitem_id = cwf_pooltask.workflowitem_id) - INNER JOIN item2bundle ON cwf_workflowitem.item_id = item2bundle.item_id) - INNER JOIN bundle2bitstream ON item2bundle.bundle_id = bundle2bitstream.bundle_id -), temptable; - --- Drop the temporary table with the action ID's -DROP TABLE temptable; - --- Create policies for submitter --- TODO: only add if unique -INSERT INTO resourcepolicy (policy_id, resource_type_id, dspace_object, action_id, eperson_id) -SELECT -resourcepolicy_seq.nextval AS policy_id, -2 AS resource_type_id, -cwf_workflowitem.item_id AS dspace_object, -0 AS action_id, -item.submitter_id AS eperson_id -FROM (cwf_workflowitem INNER JOIN item ON cwf_workflowitem.item_id = item.uuid); - -INSERT INTO resourcepolicy (policy_id, resource_type_id, dspace_object, action_id, eperson_id) -SELECT -resourcepolicy_seq.nextval AS policy_id, -1 AS resource_type_id, -item2bundle.bundle_id AS dspace_object, -0 AS action_id, -item.submitter_id AS eperson_id -FROM ((cwf_workflowitem INNER JOIN item ON cwf_workflowitem.item_id = item.uuid) - INNER JOIN item2bundle ON cwf_workflowitem.item_id = item2bundle.item_id - ); - -INSERT INTO resourcepolicy (policy_id, resource_type_id, dspace_object, action_id, eperson_id) -SELECT -resourcepolicy_seq.nextval AS policy_id, -0 AS resource_type_id, -bundle2bitstream.bitstream_id AS dspace_object, -0 AS action_id, -item.submitter_id AS eperson_id -FROM (((cwf_workflowitem INNER JOIN item ON cwf_workflowitem.item_id = item.uuid) - INNER JOIN item2bundle ON cwf_workflowitem.item_id = item2bundle.item_id) - INNER JOIN bundle2bitstream ON item2bundle.bundle_id = bundle2bitstream.bundle_id -); - --- TODO: not tested yet -INSERT INTO cwf_in_progress_user (in_progress_user_id, workflowitem_id, user_id, finished) -SELECT - cwf_in_progress_user_seq.nextval AS in_progress_user_id, - cwf_workflowitem.workflowitem_id AS workflowitem_id, - cwf_claimtask.owner_id AS user_id, - 0 as finished -FROM - (cwf_claimtask INNER JOIN cwf_workflowitem ON cwf_workflowitem.workflowitem_id = cwf_claimtask.workflowitem_id); - --- TODO: improve this, important is NVL(curr, 1)!! without this function, empty tables (max = [null]) will only result in sequence deletion -DECLARE - curr NUMBER := 0; -BEGIN - SELECT max(workflowitem_id) INTO curr FROM cwf_workflowitem; - - curr := curr + 1; - - EXECUTE IMMEDIATE 'DROP SEQUENCE cwf_workflowitem_seq'; - - EXECUTE IMMEDIATE 'CREATE SEQUENCE cwf_workflowitem_seq START WITH ' || NVL(curr, 1); -END; -/ - -DECLARE - curr NUMBER := 0; -BEGIN - SELECT max(collectionrole_id) INTO curr FROM cwf_collectionrole; - - curr := curr + 1; - - EXECUTE IMMEDIATE 'DROP SEQUENCE cwf_collectionrole_seq'; - - EXECUTE IMMEDIATE 'CREATE SEQUENCE cwf_collectionrole_seq START WITH ' || NVL(curr, 1); -END; -/ - -DECLARE - curr NUMBER := 0; -BEGIN - SELECT max(workflowitemrole_id) INTO curr FROM cwf_workflowitemrole; - - curr := curr + 1; - - EXECUTE IMMEDIATE 'DROP SEQUENCE cwf_workflowitemrole_seq'; - - EXECUTE IMMEDIATE 'CREATE SEQUENCE cwf_workflowitemrole_seq START WITH ' || NVL(curr, 1); -END; -/ - -DECLARE - curr NUMBER := 0; -BEGIN - SELECT max(pooltask_id) INTO curr FROM cwf_pooltask; - - curr := curr + 1; - - EXECUTE IMMEDIATE 'DROP SEQUENCE cwf_pooltask_seq'; - - EXECUTE IMMEDIATE 'CREATE SEQUENCE cwf_pooltask_seq START WITH ' || NVL(curr, 1); -END; -/ - -DECLARE - curr NUMBER := 0; -BEGIN - SELECT max(claimtask_id) INTO curr FROM cwf_claimtask; - - curr := curr + 1; - - EXECUTE IMMEDIATE 'DROP SEQUENCE cwf_claimtask_seq'; - - EXECUTE IMMEDIATE 'CREATE SEQUENCE cwf_claimtask_seq START WITH ' || NVL(curr, 1); -END; -/ - -DECLARE - curr NUMBER := 0; -BEGIN - SELECT max(in_progress_user_id) INTO curr FROM cwf_in_progress_user; - - curr := curr + 1; - - EXECUTE IMMEDIATE 'DROP SEQUENCE cwf_in_progress_user_seq'; - - EXECUTE IMMEDIATE 'CREATE SEQUENCE cwf_in_progress_user_seq START WITH ' || NVL(curr, 1); -END; -/ diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/xmlworkflow/oracle/v6.0__DS-2701_xml_workflow_migration.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/xmlworkflow/oracle/v6.0__DS-2701_xml_workflow_migration.sql deleted file mode 100644 index 541af73dfe01..000000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/xmlworkflow/oracle/v6.0__DS-2701_xml_workflow_migration.sql +++ /dev/null @@ -1,124 +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/ --- - ----------------------------------------------------- --- Database Schema Update for XML/Configurable Workflow (for DSpace 6.0) --- --- This file will automatically create/update your --- DSpace Database tables to support XML/Configurable workflows. --- However, it does NOT migrate your existing classic --- workflows. That step is performed by the corresponding --- "data_workflow_migration.sql" script. --- --- This script is called automatically by the following --- Flyway Java migration class: --- org.dspace.storage.rdbms.xmlworkflow.V6_0_2015_09_01__DS_2701_Enable_XMLWorkflow_Migration ----------------------------------------------------- - -CREATE SEQUENCE cwf_workflowitem_seq; -CREATE SEQUENCE cwf_collectionrole_seq; -CREATE SEQUENCE cwf_workflowitemrole_seq; -CREATE SEQUENCE cwf_claimtask_seq; -CREATE SEQUENCE cwf_in_progress_user_seq; -CREATE SEQUENCE cwf_pooltask_seq; - - -CREATE TABLE cwf_workflowitem -( - workflowitem_id INTEGER PRIMARY KEY, - item_id RAW(16) REFERENCES item(uuid) UNIQUE, - collection_id RAW(16) REFERENCES collection(uuid), - -- - -- Answers to questions on first page of submit UI - multiple_titles NUMBER(1), - published_before NUMBER(1), - multiple_files NUMBER(1) - -- Note: stage reached not applicable here - people involved in workflow - -- can always jump around submission UI -); - - -CREATE INDEX cwf_workflowitem_coll_fk_idx ON cwf_workflowitem(collection_id); - - -CREATE TABLE cwf_collectionrole ( -collectionrole_id INTEGER PRIMARY KEY, -role_id VARCHAR2(256), -collection_id RAW(16) REFERENCES collection(uuid), -group_id RAW(16) REFERENCES epersongroup(uuid) -); -ALTER TABLE cwf_collectionrole -ADD CONSTRAINT cwf_collectionrole_unique UNIQUE (role_id, collection_id, group_id); - -CREATE INDEX cwf_cr_coll_role_fk_idx ON cwf_collectionrole(collection_id,role_id); -CREATE INDEX cwf_cr_coll_fk_idx ON cwf_collectionrole(collection_id); - - -CREATE TABLE cwf_workflowitemrole ( - workflowitemrole_id INTEGER PRIMARY KEY, - role_id VARCHAR2(256), - workflowitem_id integer REFERENCES cwf_workflowitem(workflowitem_id), - eperson_id RAW(16) REFERENCES eperson(uuid), - group_id RAW(16) REFERENCES epersongroup(uuid) -); -ALTER TABLE cwf_workflowitemrole -ADD CONSTRAINT cwf_workflowitemrole_unique UNIQUE (role_id, workflowitem_id, eperson_id, group_id); - -CREATE INDEX cwf_wfir_item_role_fk_idx ON cwf_workflowitemrole(workflowitem_id,role_id); -CREATE INDEX cwf_wfir_item_fk_idx ON cwf_workflowitemrole(workflowitem_id); - - -CREATE TABLE cwf_pooltask ( - pooltask_id INTEGER PRIMARY KEY, - workflowitem_id INTEGER REFERENCES cwf_workflowitem(workflowitem_id), - workflow_id VARCHAR2(256), - step_id VARCHAR2(256), - action_id VARCHAR2(256), - eperson_id RAW(16) REFERENCES EPerson(uuid), - group_id RAW(16) REFERENCES epersongroup(uuid) -); - -CREATE INDEX cwf_pt_eperson_fk_idx ON cwf_pooltask(eperson_id); -CREATE INDEX cwf_pt_workflow_fk_idx ON cwf_pooltask(workflowitem_id); -CREATE INDEX cwf_pt_workflow_eperson_fk_idx ON cwf_pooltask(eperson_id,workflowitem_id); - - - -CREATE TABLE cwf_claimtask ( - claimtask_id INTEGER PRIMARY KEY, - workflowitem_id integer REFERENCES cwf_workflowitem(workflowitem_id), - workflow_id VARCHAR2(256), - step_id VARCHAR2(256), - action_id VARCHAR2(256), - owner_id RAW(16) REFERENCES eperson(uuid) -); - -ALTER TABLE cwf_claimtask -ADD CONSTRAINT cwf_claimtask_unique UNIQUE (step_id, workflowitem_id, workflow_id, owner_id, action_id); - -CREATE INDEX cwf_ct_workflow_fk_idx ON cwf_claimtask(workflowitem_id); -CREATE INDEX cwf_ct_workflow_eperson_fk_idx ON cwf_claimtask(workflowitem_id,owner_id); -CREATE INDEX cwf_ct_eperson_fk_idx ON cwf_claimtask(owner_id); -CREATE INDEX cwf_ct_wfs_fk_idx ON cwf_claimtask(workflowitem_id,step_id); -CREATE INDEX cwf_ct_wfs_action_fk_idx ON cwf_claimtask(workflowitem_id,step_id,action_id); -CREATE INDEX cwf_ct_wfs_action_e_fk_idx ON cwf_claimtask(workflowitem_id,step_id,action_id,owner_id); - - -CREATE TABLE cwf_in_progress_user ( - in_progress_user_id INTEGER PRIMARY KEY, - workflowitem_id integer REFERENCES cwf_workflowitem(workflowitem_id), - user_id RAW(16) REFERENCES eperson(uuid), - finished NUMBER(1) DEFAULT 0 -); - -ALTER TABLE cwf_in_progress_user -ADD CONSTRAINT cwf_in_progress_user_unique UNIQUE (workflowitem_id, user_id); - -CREATE INDEX cwf_ipu_workflow_fk_idx ON cwf_in_progress_user(workflowitem_id); -CREATE INDEX cwf_ipu_eperson_fk_idx ON cwf_in_progress_user(user_id); - diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/xmlworkflow/oracle/xml_workflow_migration.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/xmlworkflow/oracle/xml_workflow_migration.sql deleted file mode 100644 index f8f0e564e824..000000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/xmlworkflow/oracle/xml_workflow_migration.sql +++ /dev/null @@ -1,124 +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/ --- - ----------------------------------------------------- --- Database Schema Update for XML/Configurable Workflow --- --- This file will automatically create/update your --- DSpace Database tables to support XML/Configurable workflows. --- However, it does NOT migrate your existing classic --- workflows. That step is performed by the corresponding --- "data_workflow_migration.sql" script. --- --- This script is called automatically by the following --- Flyway Java migration class: --- org.dspace.storage.rdbms.migration.V5_0_2014_01_01__XMLWorkflow_Migration ----------------------------------------------------- - -CREATE SEQUENCE cwf_workflowitem_seq; -CREATE SEQUENCE cwf_collectionrole_seq; -CREATE SEQUENCE cwf_workflowitemrole_seq; -CREATE SEQUENCE cwf_claimtask_seq; -CREATE SEQUENCE cwf_in_progress_user_seq; -CREATE SEQUENCE cwf_pooltask_seq; - - -CREATE TABLE cwf_workflowitem -( - workflowitem_id INTEGER PRIMARY KEY, - item_id INTEGER REFERENCES item(item_id) UNIQUE, - collection_id INTEGER REFERENCES collection(collection_id), - -- - -- Answers to questions on first page of submit UI - multiple_titles NUMBER(1), - published_before NUMBER(1), - multiple_files NUMBER(1) - -- Note: stage reached not applicable here - people involved in workflow - -- can always jump around submission UI -); - - -CREATE INDEX cwf_workflowitem_coll_fk_idx ON cwf_workflowitem(collection_id); - - -CREATE TABLE cwf_collectionrole ( -collectionrole_id INTEGER PRIMARY KEY, -role_id VARCHAR2(256), -collection_id integer REFERENCES collection(collection_id), -group_id integer REFERENCES epersongroup(eperson_group_id) -); -ALTER TABLE cwf_collectionrole -ADD CONSTRAINT cwf_collectionrole_unique UNIQUE (role_id, collection_id, group_id); - -CREATE INDEX cwf_cr_coll_role_fk_idx ON cwf_collectionrole(collection_id,role_id); -CREATE INDEX cwf_cr_coll_fk_idx ON cwf_collectionrole(collection_id); - - -CREATE TABLE cwf_workflowitemrole ( - workflowitemrole_id INTEGER PRIMARY KEY, - role_id VARCHAR2(256), - workflowitem_id integer REFERENCES cwf_workflowitem(workflowitem_id), - eperson_id integer REFERENCES eperson(eperson_id), - group_id integer REFERENCES epersongroup(eperson_group_id) -); -ALTER TABLE cwf_workflowitemrole -ADD CONSTRAINT cwf_workflowitemrole_unique UNIQUE (role_id, workflowitem_id, eperson_id, group_id); - -CREATE INDEX cwf_wfir_item_role_fk_idx ON cwf_workflowitemrole(workflowitem_id,role_id); -CREATE INDEX cwf_wfir_item_fk_idx ON cwf_workflowitemrole(workflowitem_id); - - -CREATE TABLE cwf_pooltask ( - pooltask_id INTEGER PRIMARY KEY, - workflowitem_id INTEGER REFERENCES cwf_workflowitem(workflowitem_id), - workflow_id VARCHAR2(256), - step_id VARCHAR2(256), - action_id VARCHAR2(256), - eperson_id INTEGER REFERENCES EPerson(eperson_id), - group_id INTEGER REFERENCES epersongroup(eperson_group_id) -); - -CREATE INDEX cwf_pt_eperson_fk_idx ON cwf_pooltask(eperson_id); -CREATE INDEX cwf_pt_workflow_fk_idx ON cwf_pooltask(workflowitem_id); -CREATE INDEX cwf_pt_workflow_eperson_fk_idx ON cwf_pooltask(eperson_id,workflowitem_id); - - - -CREATE TABLE cwf_claimtask ( - claimtask_id INTEGER PRIMARY KEY, - workflowitem_id integer REFERENCES cwf_workflowitem(workflowitem_id), - workflow_id VARCHAR2(256), - step_id VARCHAR2(256), - action_id VARCHAR2(256), - owner_id integer REFERENCES eperson(eperson_id) -); - -ALTER TABLE cwf_claimtask -ADD CONSTRAINT cwf_claimtask_unique UNIQUE (step_id, workflowitem_id, workflow_id, owner_id, action_id); - -CREATE INDEX cwf_ct_workflow_fk_idx ON cwf_claimtask(workflowitem_id); -CREATE INDEX cwf_ct_workflow_eperson_fk_idx ON cwf_claimtask(workflowitem_id,owner_id); -CREATE INDEX cwf_ct_eperson_fk_idx ON cwf_claimtask(owner_id); -CREATE INDEX cwf_ct_wfs_fk_idx ON cwf_claimtask(workflowitem_id,step_id); -CREATE INDEX cwf_ct_wfs_action_fk_idx ON cwf_claimtask(workflowitem_id,step_id,action_id); -CREATE INDEX cwf_ct_wfs_action_e_fk_idx ON cwf_claimtask(workflowitem_id,step_id,action_id,owner_id); - - -CREATE TABLE cwf_in_progress_user ( - in_progress_user_id INTEGER PRIMARY KEY, - workflowitem_id integer REFERENCES cwf_workflowitem(workflowitem_id), - user_id integer REFERENCES eperson(eperson_id), - finished NUMBER(1) DEFAULT 0 -); - -ALTER TABLE cwf_in_progress_user -ADD CONSTRAINT cwf_in_progress_user_unique UNIQUE (workflowitem_id, user_id); - -CREATE INDEX cwf_ipu_workflow_fk_idx ON cwf_in_progress_user(workflowitem_id); -CREATE INDEX cwf_ipu_eperson_fk_idx ON cwf_in_progress_user(user_id); - From 7a209d228aef5689132656a35dca7849b99813b9 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 3 May 2023 13:37:36 -0500 Subject: [PATCH 121/510] Remove oracle specific Java code / comments. --- .../org/dspace/eperson/GroupServiceImpl.java | 2 - .../dspace/storage/rdbms/DatabaseUtils.java | 50 ++----------------- .../rdbms/migration/MigrationUtils.java | 16 ------ ...Drop_constraint_for_DSpace_1_4_schema.java | 5 +- ...Drop_constraint_for_DSpace_1_6_schema.java | 5 +- ...adata_For_All_Objects_drop_constraint.java | 5 +- ...4_11_04__Enable_XMLWorkflow_Migration.java | 2 - ..._DS_2701_Enable_XMLWorkflow_Migration.java | 2 - .../postgres/upgradeToFlyway4x.sql | 2 +- 9 files changed, 12 insertions(+), 77 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/eperson/GroupServiceImpl.java b/dspace-api/src/main/java/org/dspace/eperson/GroupServiceImpl.java index f3dc6ca36a65..607e57af0b2c 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/GroupServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/GroupServiceImpl.java @@ -353,8 +353,6 @@ public Set allMemberGroupsSet(Context context, EPerson ePerson) throws SQ List groupCache = group2GroupCacheDAO.findByChildren(context, groups); // now we have all owning groups, also grab all parents of owning groups - // yes, I know this could have been done as one big query and a union, - // but doing the Oracle port taught me to keep to simple SQL! for (Group2GroupCache group2GroupCache : groupCache) { groups.add(group2GroupCache.getParent()); } diff --git a/dspace-api/src/main/java/org/dspace/storage/rdbms/DatabaseUtils.java b/dspace-api/src/main/java/org/dspace/storage/rdbms/DatabaseUtils.java index 1464fb44ecda..89010a73087f 100644 --- a/dspace-api/src/main/java/org/dspace/storage/rdbms/DatabaseUtils.java +++ b/dspace-api/src/main/java/org/dspace/storage/rdbms/DatabaseUtils.java @@ -75,7 +75,6 @@ public class DatabaseUtils { // Types of databases supported by DSpace. See getDbType() public static final String DBMS_POSTGRES = "postgres"; - public static final String DBMS_ORACLE = "oracle"; public static final String DBMS_H2 = "h2"; // Name of the table that Flyway uses for its migration history @@ -369,9 +368,7 @@ public static void main(String[] argv) { .println("\nWARNING: ALL DATA AND TABLES IN YOUR DATABASE WILL BE PERMANENTLY DELETED.\n"); System.out.println("There is NO turning back from this action. Backup your DB before " + "continuing."); - if (dbType.equals(DBMS_ORACLE)) { - System.out.println("\nORACLE WARNING: your RECYCLEBIN will also be PURGED.\n"); - } else if (dbType.equals(DBMS_POSTGRES)) { + if (dbType.equals(DBMS_POSTGRES)) { System.out.println( "\nPOSTGRES WARNING: the '" + PostgresUtils.PGCRYPTO + "' extension will be dropped " + "if it is in the same schema as the DSpace database.\n"); @@ -467,11 +464,10 @@ private static void printDBInfo(Connection connection) throws SQLException { DatabaseMetaData meta = connection.getMetaData(); String dbType = getDbType(connection); System.out.println("\nDatabase Type: " + dbType); - if (dbType.equals(DBMS_ORACLE)) { - System.out.println("===================================="); - System.out.println("WARNING: Oracle support is deprecated!"); - System.out.println("See https://github.com/DSpace/DSpace/issues/8214"); - System.out.println("====================================="); + if (!dbType.equals(DBMS_POSTGRES) && !dbType.equals(DBMS_H2)) { + System.err.println("===================================="); + System.err.println("ERROR: Database type " + dbType + " is UNSUPPORTED!"); + System.err.println("====================================="); } System.out.println("Database URL: " + meta.getURL()); System.out.println("Database Schema: " + getSchemaName(connection)); @@ -606,10 +602,6 @@ private synchronized static FluentConfiguration setupFlyway(DataSource datasourc String dbType = getDbType(connection); connection.close(); - if (dbType.equals(DBMS_ORACLE)) { - log.warn("ORACLE SUPPORT IS DEPRECATED! See https://github.com/DSpace/DSpace/issues/8214"); - } - // Determine location(s) where Flyway will load all DB migrations ArrayList scriptLocations = new ArrayList<>(); @@ -946,26 +938,6 @@ private static synchronized void cleanDatabase(Flyway flyway, DataSource dataSou // First, run Flyway's clean command on database. // For MOST database types, this takes care of everything flyway.clean(); - - try (Connection connection = dataSource.getConnection()) { - // Get info about which database type we are using - String dbType = getDbType(connection); - - // If this is Oracle, the only way to entirely clean the database - // is to also purge the "Recyclebin". See: - // http://docs.oracle.com/cd/B19306_01/server.102/b14200/statements_9018.htm - if (dbType.equals(DBMS_ORACLE)) { - PreparedStatement statement = null; - try { - statement = connection.prepareStatement("PURGE RECYCLEBIN"); - statement.executeQuery(); - } finally { - if (statement != null && !statement.isClosed()) { - statement.close(); - } - } - } - } } catch (FlywayException fe) { // If any FlywayException (Runtime) is thrown, change it to a SQLException throw new SQLException("Flyway clean error occurred", fe); @@ -1214,11 +1186,6 @@ public static boolean sequenceExists(Connection connection, String sequenceName) // We need to filter by schema in PostgreSQL schemaFilter = true; break; - case DBMS_ORACLE: - // Oracle specific query for a sequence owned by our current DSpace user - // NOTE: No need to filter by schema for Oracle, as Schema = User - sequenceSQL = "SELECT COUNT(1) FROM user_sequences WHERE sequence_name=?"; - break; case DBMS_H2: // In H2, sequences are listed in the "information_schema.sequences" table // SEE: http://www.h2database.com/html/grammar.html#information_schema @@ -1322,11 +1289,6 @@ public static String getSchemaName(Connection connection) // For PostgreSQL, the default schema is named "public" // See: http://www.postgresql.org/docs/9.0/static/ddl-schemas.html schema = "public"; - } else if (dbType.equals(DBMS_ORACLE)) { - // For Oracle, default schema is actually the user account - // See: http://stackoverflow.com/a/13341390 - DatabaseMetaData meta = connection.getMetaData(); - schema = meta.getUserName(); } else { // For H2 (in memory), there is no such thing as a schema schema = null; @@ -1552,8 +1514,6 @@ public static String getDbType(Connection connection) String dbms_lc = prodName.toLowerCase(Locale.ROOT); if (dbms_lc.contains("postgresql")) { return DBMS_POSTGRES; - } else if (dbms_lc.contains("oracle")) { - return DBMS_ORACLE; } else if (dbms_lc.contains("h2")) { // Used for unit testing only return DBMS_H2; diff --git a/dspace-api/src/main/java/org/dspace/storage/rdbms/migration/MigrationUtils.java b/dspace-api/src/main/java/org/dspace/storage/rdbms/migration/MigrationUtils.java index 842fc15e1657..f0c4e4e17990 100644 --- a/dspace-api/src/main/java/org/dspace/storage/rdbms/migration/MigrationUtils.java +++ b/dspace-api/src/main/java/org/dspace/storage/rdbms/migration/MigrationUtils.java @@ -78,13 +78,6 @@ protected static Integer dropDBConstraint(Connection connection, String tableNam constraintName += "_" + StringUtils.lowerCase(constraintSuffix); cascade = true; break; - case "oracle": - // In Oracle, constraints are listed in the USER_CONS_COLUMNS table - constraintNameSQL = "SELECT CONSTRAINT_NAME " + - "FROM USER_CONS_COLUMNS " + - "WHERE TABLE_NAME = ? AND COLUMN_NAME = ?"; - cascade = true; - break; case "h2": // In H2, column constraints are listed in the "INFORMATION_SCHEMA.KEY_COLUMN_USAGE" table constraintNameSQL = "SELECT DISTINCT CONSTRAINT_NAME " + @@ -160,9 +153,6 @@ protected static Integer dropDBTable(Connection connection, String tableName) case "postgresql": dropTableSQL = "DROP TABLE IF EXISTS " + tableName + " CASCADE"; break; - case "oracle": - dropTableSQL = "DROP TABLE " + tableName + " CASCADE CONSTRAINTS"; - break; case "h2": dropTableSQL = "DROP TABLE IF EXISTS " + tableName + " CASCADE"; break; @@ -208,9 +198,6 @@ protected static Integer dropDBSequence(Connection connection, String sequenceNa case "postgresql": dropSequenceSQL = "DROP SEQUENCE IF EXISTS " + sequenceName; break; - case "oracle": - dropSequenceSQL = "DROP SEQUENCE " + sequenceName; - break; case "h2": dropSequenceSQL = "DROP SEQUENCE IF EXISTS " + sequenceName; break; @@ -256,9 +243,6 @@ protected static Integer dropDBView(Connection connection, String viewName) case "postgresql": dropViewSQL = "DROP VIEW IF EXISTS " + viewName + " CASCADE"; break; - case "oracle": - dropViewSQL = "DROP VIEW " + viewName + " CASCADE CONSTRAINTS"; - break; case "h2": dropViewSQL = "DROP VIEW IF EXISTS " + viewName + " CASCADE"; break; diff --git a/dspace-api/src/main/java/org/dspace/storage/rdbms/migration/V1_3_9__Drop_constraint_for_DSpace_1_4_schema.java b/dspace-api/src/main/java/org/dspace/storage/rdbms/migration/V1_3_9__Drop_constraint_for_DSpace_1_4_schema.java index 56c5b474d9fc..758e745ddc86 100644 --- a/dspace-api/src/main/java/org/dspace/storage/rdbms/migration/V1_3_9__Drop_constraint_for_DSpace_1_4_schema.java +++ b/dspace-api/src/main/java/org/dspace/storage/rdbms/migration/V1_3_9__Drop_constraint_for_DSpace_1_4_schema.java @@ -19,10 +19,9 @@ * of the "community" table. This is necessary for the upgrade from 1.3 to 1.4 *

* This class was created because the names of database constraints differs based - * on the type of database (Postgres vs. Oracle vs. H2). As such, it becomes difficult + * on the type of database (Postgres vs. H2). As such, it becomes difficult * to write simple SQL which will work for multiple database types (especially - * since unit tests require H2 and the syntax for H2 is different from either - * Oracle or Postgres). + * since unit tests require H2 and the syntax for H2 is different from Postgres). *

* NOTE: This migration class is very simple because it is meant to be used * in conjuction with the corresponding SQL script: diff --git a/dspace-api/src/main/java/org/dspace/storage/rdbms/migration/V1_5_9__Drop_constraint_for_DSpace_1_6_schema.java b/dspace-api/src/main/java/org/dspace/storage/rdbms/migration/V1_5_9__Drop_constraint_for_DSpace_1_6_schema.java index 6d82055e530e..37100a17f926 100644 --- a/dspace-api/src/main/java/org/dspace/storage/rdbms/migration/V1_5_9__Drop_constraint_for_DSpace_1_6_schema.java +++ b/dspace-api/src/main/java/org/dspace/storage/rdbms/migration/V1_5_9__Drop_constraint_for_DSpace_1_6_schema.java @@ -19,10 +19,9 @@ * from 1.5 to 1.6 *

* This class was created because the names of database constraints differs based - * on the type of database (Postgres vs. Oracle vs. H2). As such, it becomes difficult + * on the type of database (Postgres vs. H2). As such, it becomes difficult * to write simple SQL which will work for multiple database types (especially - * since unit tests require H2 and the syntax for H2 is different from either - * Oracle or Postgres). + * since unit tests require H2 and the syntax for H2 is different from Postgres). *

* NOTE: This migration class is very simple because it is meant to be used * in conjuction with the corresponding SQL script: diff --git a/dspace-api/src/main/java/org/dspace/storage/rdbms/migration/V5_0_2014_09_25__DS_1582_Metadata_For_All_Objects_drop_constraint.java b/dspace-api/src/main/java/org/dspace/storage/rdbms/migration/V5_0_2014_09_25__DS_1582_Metadata_For_All_Objects_drop_constraint.java index ea72d99b6e29..8e2be91127c8 100644 --- a/dspace-api/src/main/java/org/dspace/storage/rdbms/migration/V5_0_2014_09_25__DS_1582_Metadata_For_All_Objects_drop_constraint.java +++ b/dspace-api/src/main/java/org/dspace/storage/rdbms/migration/V5_0_2014_09_25__DS_1582_Metadata_For_All_Objects_drop_constraint.java @@ -20,10 +20,9 @@ * this column must be renamed to "resource_id". *

* This class was created because the names of database constraints differs based - * on the type of database (Postgres vs. Oracle vs. H2). As such, it becomes difficult + * on the type of database (Postgres vs. H2). As such, it becomes difficult * to write simple SQL which will work for multiple database types (especially - * since unit tests require H2 and the syntax for H2 is different from either - * Oracle or Postgres). + * since unit tests require H2 and the syntax for H2 is different from Postgres). *

* NOTE: This migration class is very simple because it is meant to be used * in conjuction with the corresponding SQL script: diff --git a/dspace-api/src/main/java/org/dspace/storage/rdbms/xmlworkflow/V5_0_2014_11_04__Enable_XMLWorkflow_Migration.java b/dspace-api/src/main/java/org/dspace/storage/rdbms/xmlworkflow/V5_0_2014_11_04__Enable_XMLWorkflow_Migration.java index b3306a9fc93c..0361e6805356 100644 --- a/dspace-api/src/main/java/org/dspace/storage/rdbms/xmlworkflow/V5_0_2014_11_04__Enable_XMLWorkflow_Migration.java +++ b/dspace-api/src/main/java/org/dspace/storage/rdbms/xmlworkflow/V5_0_2014_11_04__Enable_XMLWorkflow_Migration.java @@ -67,8 +67,6 @@ public void migrate(Context context) String dbFileLocation = null; if (dbtype.toLowerCase().contains("postgres")) { dbFileLocation = "postgres"; - } else if (dbtype.toLowerCase().contains("oracle")) { - dbFileLocation = "oracle"; } else if (dbtype.toLowerCase().contains("h2")) { dbFileLocation = "h2"; } diff --git a/dspace-api/src/main/java/org/dspace/storage/rdbms/xmlworkflow/V6_0_2015_09_01__DS_2701_Enable_XMLWorkflow_Migration.java b/dspace-api/src/main/java/org/dspace/storage/rdbms/xmlworkflow/V6_0_2015_09_01__DS_2701_Enable_XMLWorkflow_Migration.java index 9aa0f4877c39..4c1cf3365395 100644 --- a/dspace-api/src/main/java/org/dspace/storage/rdbms/xmlworkflow/V6_0_2015_09_01__DS_2701_Enable_XMLWorkflow_Migration.java +++ b/dspace-api/src/main/java/org/dspace/storage/rdbms/xmlworkflow/V6_0_2015_09_01__DS_2701_Enable_XMLWorkflow_Migration.java @@ -46,8 +46,6 @@ public void migrate(Context context) throws Exception { String dbFileLocation = null; if (dbtype.toLowerCase().contains("postgres")) { dbFileLocation = "postgres"; - } else if (dbtype.toLowerCase().contains("oracle")) { - dbFileLocation = "oracle"; } else if (dbtype.toLowerCase().contains("h2")) { dbFileLocation = "h2"; } diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/flywayupgrade/postgres/upgradeToFlyway4x.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/flywayupgrade/postgres/upgradeToFlyway4x.sql index 7548fa4c6acb..edebe6e087fb 100644 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/flywayupgrade/postgres/upgradeToFlyway4x.sql +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/flywayupgrade/postgres/upgradeToFlyway4x.sql @@ -15,7 +15,7 @@ -- ----------------- -- This is the PostgreSQL upgrade script from Flyway v4.2.0, copied/borrowed from: --- https://github.com/flyway/flyway/blob/flyway-4.2.0/flyway-core/src/main/resources/org/flywaydb/core/internal/dbsupport/oracle/upgradeMetaDataTable.sql +-- https://github.com/flyway/flyway/blob/flyway-4.2.0/flyway-core/src/main/resources/org/flywaydb/core/internal/dbsupport/postgresql/upgradeMetaDataTable.sql -- -- The variables in this script are replaced in FlywayUpgradeUtils.upgradeFlywayTable() ------------------ From dac7ed2f09e0d8c9ae4931910944ef32deccc924 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 3 May 2023 14:47:16 -0500 Subject: [PATCH 122/510] Migrate to org.hibernate.type.TextType from our custom LobType --- .../org/dspace/authorize/ResourcePolicy.java | 2 +- .../org/dspace/content/MetadataValue.java | 2 +- .../java/org/dspace/orcid/OrcidHistory.java | 6 +- .../java/org/dspace/orcid/OrcidQueue.java | 4 +- .../main/java/org/dspace/scripts/Process.java | 2 +- .../rdbms/hibernate/DatabaseAwareLobType.java | 57 ------------------- 6 files changed, 8 insertions(+), 65 deletions(-) delete mode 100644 dspace-api/src/main/java/org/dspace/storage/rdbms/hibernate/DatabaseAwareLobType.java diff --git a/dspace-api/src/main/java/org/dspace/authorize/ResourcePolicy.java b/dspace-api/src/main/java/org/dspace/authorize/ResourcePolicy.java index 38b6aef45bc2..c781400bae45 100644 --- a/dspace-api/src/main/java/org/dspace/authorize/ResourcePolicy.java +++ b/dspace-api/src/main/java/org/dspace/authorize/ResourcePolicy.java @@ -100,7 +100,7 @@ public class ResourcePolicy implements ReloadableEntity { private String rptype; @Lob - @Type(type = "org.dspace.storage.rdbms.hibernate.DatabaseAwareLobType") + @Type(type = "org.hibernate.type.TextType") @Column(name = "rpdescription") private String rpdescription; diff --git a/dspace-api/src/main/java/org/dspace/content/MetadataValue.java b/dspace-api/src/main/java/org/dspace/content/MetadataValue.java index c3deaacd804c..31479e620618 100644 --- a/dspace-api/src/main/java/org/dspace/content/MetadataValue.java +++ b/dspace-api/src/main/java/org/dspace/content/MetadataValue.java @@ -60,7 +60,7 @@ public class MetadataValue implements ReloadableEntity { * The value of the field */ @Lob - @Type(type = "org.dspace.storage.rdbms.hibernate.DatabaseAwareLobType") + @Type(type = "org.hibernate.type.TextType") @Column(name = "text_value") private String value; diff --git a/dspace-api/src/main/java/org/dspace/orcid/OrcidHistory.java b/dspace-api/src/main/java/org/dspace/orcid/OrcidHistory.java index a567c6e7a7fe..07a79384c77c 100644 --- a/dspace-api/src/main/java/org/dspace/orcid/OrcidHistory.java +++ b/dspace-api/src/main/java/org/dspace/orcid/OrcidHistory.java @@ -80,7 +80,7 @@ public class OrcidHistory implements ReloadableEntity { * A description of the synchronized resource. */ @Lob - @Type(type = "org.dspace.storage.rdbms.hibernate.DatabaseAwareLobType") + @Type(type = "org.hibernate.type.TextType") @Column(name = "description") private String description; @@ -89,7 +89,7 @@ public class OrcidHistory implements ReloadableEntity { * the owner itself. */ @Lob - @Type(type = "org.dspace.storage.rdbms.hibernate.DatabaseAwareLobType") + @Type(type = "org.hibernate.type.TextType") @Column(name = "metadata") private String metadata; @@ -104,7 +104,7 @@ public class OrcidHistory implements ReloadableEntity { * The response message incoming from ORCID. */ @Lob - @Type(type = "org.dspace.storage.rdbms.hibernate.DatabaseAwareLobType") + @Type(type = "org.hibernate.type.TextType") @Column(name = "response_message") private String responseMessage; diff --git a/dspace-api/src/main/java/org/dspace/orcid/OrcidQueue.java b/dspace-api/src/main/java/org/dspace/orcid/OrcidQueue.java index 9261f14eea24..65b66cd20c3e 100644 --- a/dspace-api/src/main/java/org/dspace/orcid/OrcidQueue.java +++ b/dspace-api/src/main/java/org/dspace/orcid/OrcidQueue.java @@ -65,7 +65,7 @@ public class OrcidQueue implements ReloadableEntity { * A description of the resource to be synchronized. */ @Lob - @Type(type = "org.dspace.storage.rdbms.hibernate.DatabaseAwareLobType") + @Type(type = "org.hibernate.type.TextType") @Column(name = "description") private String description; @@ -89,7 +89,7 @@ public class OrcidQueue implements ReloadableEntity { */ @Lob @Column(name = "metadata") - @Type(type = "org.dspace.storage.rdbms.hibernate.DatabaseAwareLobType") + @Type(type = "org.hibernate.type.TextType") private String metadata; /** diff --git a/dspace-api/src/main/java/org/dspace/scripts/Process.java b/dspace-api/src/main/java/org/dspace/scripts/Process.java index 190d214a3c8d..eab3ba460c09 100644 --- a/dspace-api/src/main/java/org/dspace/scripts/Process.java +++ b/dspace-api/src/main/java/org/dspace/scripts/Process.java @@ -71,7 +71,7 @@ public class Process implements ReloadableEntity { private ProcessStatus processStatus; @Lob - @Type(type = "org.dspace.storage.rdbms.hibernate.DatabaseAwareLobType") + @Type(type = "org.hibernate.type.TextType") @Column(name = "parameters") private String parameters; diff --git a/dspace-api/src/main/java/org/dspace/storage/rdbms/hibernate/DatabaseAwareLobType.java b/dspace-api/src/main/java/org/dspace/storage/rdbms/hibernate/DatabaseAwareLobType.java deleted file mode 100644 index 95939f9902aa..000000000000 --- a/dspace-api/src/main/java/org/dspace/storage/rdbms/hibernate/DatabaseAwareLobType.java +++ /dev/null @@ -1,57 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.storage.rdbms.hibernate; - -import org.apache.commons.lang.StringUtils; -import org.dspace.services.ConfigurationService; -import org.dspace.services.factory.DSpaceServicesFactory; -import org.hibernate.type.AbstractSingleColumnStandardBasicType; -import org.hibernate.type.descriptor.java.StringTypeDescriptor; -import org.hibernate.type.descriptor.sql.ClobTypeDescriptor; -import org.hibernate.type.descriptor.sql.LongVarcharTypeDescriptor; -import org.hibernate.type.descriptor.sql.SqlTypeDescriptor; - -/** - * A Hibernate @Type used to properly support the CLOB in both Postgres and Oracle. - * PostgreSQL doesn't have a CLOB type, instead it's a TEXT field. - * Normally, you'd use org.hibernate.type.TextType to support TEXT, but that won't work for Oracle. - * https://github.com/hibernate/hibernate-orm/blob/5.6/hibernate-core/src/main/java/org/hibernate/type/TextType.java - * - * This Type checks if we are using PostgreSQL. - * If so, it configures Hibernate to map CLOB to LongVarChar (same as org.hibernate.type.TextType) - * If not, it uses default CLOB (which works for other databases). - */ -public class DatabaseAwareLobType extends AbstractSingleColumnStandardBasicType { - - public static final DatabaseAwareLobType INSTANCE = new DatabaseAwareLobType(); - - public DatabaseAwareLobType() { - super( getDbDescriptor(), StringTypeDescriptor.INSTANCE ); - } - - public static SqlTypeDescriptor getDbDescriptor() { - if ( isPostgres() ) { - return LongVarcharTypeDescriptor.INSTANCE; - } else { - return ClobTypeDescriptor.DEFAULT; - } - } - - private static boolean isPostgres() { - ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); - String dbDialect = configurationService.getProperty("db.dialect"); - - return StringUtils.containsIgnoreCase(dbDialect, "PostgreSQL"); - } - - @Override - public String getName() { - return "database_aware_lob"; - } -} - From 66eb8a548fe55698cd53766fb86f605f23534323 Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Thu, 4 May 2023 20:11:47 +0200 Subject: [PATCH 123/510] Browse-by support for controlled vocabularies https://github.com/DSpace/RestContract/pull/225 --- .../authority/ChoiceAuthorityServiceImpl.java | 54 +++++++++++++ .../DSpaceControlledVocabularyIndex.java | 45 +++++++++++ .../service/ChoiceAuthorityService.java | 4 + .../DiscoveryConfigurationService.java | 12 +++ .../rest/converter/BrowseIndexConverter.java | 6 +- .../HierarchicalBrowseConverter.java | 42 ++++++++++ .../rest/link/BrowseEntryHalLinkFactory.java | 4 +- .../app/rest/model/BrowseIndexRest.java | 77 +++++++++++++++---- .../model/hateoas/BrowseIndexResource.java | 34 +++++++- .../repository/BrowseEntryLinkRepository.java | 5 +- .../repository/BrowseIndexRestRepository.java | 30 +++++++- .../repository/BrowseItemLinkRepository.java | 5 +- .../repository/VocabularyRestRepository.java | 2 +- 13 files changed, 288 insertions(+), 32 deletions(-) create mode 100644 dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabularyIndex.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/HierarchicalBrowseConverter.java 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 f25e2c4646b2..ec8f8769be52 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 @@ -15,6 +15,7 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Set; +import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; @@ -30,6 +31,8 @@ import org.dspace.content.authority.service.ChoiceAuthorityService; import org.dspace.core.Utils; import org.dspace.core.service.PluginService; +import org.dspace.discovery.configuration.DiscoveryConfigurationService; +import org.dspace.discovery.configuration.DiscoverySearchFilterFacet; import org.dspace.services.ConfigurationService; import org.springframework.beans.factory.annotation.Autowired; @@ -80,6 +83,9 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService protected Map>> authoritiesFormDefinitions = new HashMap>>(); + // Map of vocabulary authorities to and their index info equivalent + protected Map vocabularyIndexMap = new HashMap<>(); + // the item submission reader private SubmissionConfigReader itemSubmissionConfigReader; @@ -87,6 +93,8 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService protected ConfigurationService configurationService; @Autowired(required = true) protected PluginService pluginService; + @Autowired + private DiscoveryConfigurationService searchConfigurationService; final static String CHOICES_PLUGIN_PREFIX = "choices.plugin."; final static String CHOICES_PRESENTATION_PREFIX = "choices.presentation."; @@ -540,4 +548,50 @@ public Choice getParentChoice(String authorityName, String vocabularyId, String HierarchicalAuthority ma = (HierarchicalAuthority) getChoiceAuthorityByAuthorityName(authorityName); return ma.getParentChoice(authorityName, vocabularyId, locale); } + + @Override + public DSpaceControlledVocabularyIndex getVocabularyIndex(String nameVocab) { + if (this.vocabularyIndexMap.containsKey(nameVocab)) { + return this.vocabularyIndexMap.get(nameVocab); + } else { + init(); + ChoiceAuthority source = this.getChoiceAuthorityByAuthorityName(nameVocab); + if (source != null && source instanceof DSpaceControlledVocabulary) { + Set metadataFields = new HashSet<>(); + Map> formsToFields = this.authoritiesFormDefinitions.get(nameVocab); + for (Map.Entry> formToField : formsToFields.entrySet()) { + metadataFields.addAll(formToField.getValue().stream().map(value -> + StringUtils.replace(value, "_", ".")) + .collect(Collectors.toList())); + } + DiscoverySearchFilterFacet matchingFacet = null; + for (DiscoverySearchFilterFacet facetConfig : searchConfigurationService.getAllFacetsConfig()) { + boolean coversAllFieldsFromVocab = true; + for (String fieldFromVocab: metadataFields) { + boolean coversFieldFromVocab = false; + for (String facetMdField: facetConfig.getMetadataFields()) { + if (facetMdField.startsWith(fieldFromVocab)) { + coversFieldFromVocab = true; + break; + } + } + if (!coversFieldFromVocab) { + coversAllFieldsFromVocab = false; + break; + } + } + if (coversAllFieldsFromVocab) { + matchingFacet = facetConfig; + break; + } + } + DSpaceControlledVocabularyIndex vocabularyIndex = + new DSpaceControlledVocabularyIndex((DSpaceControlledVocabulary) source, metadataFields, + matchingFacet); + this.vocabularyIndexMap.put(nameVocab, vocabularyIndex); + return vocabularyIndex; + } + return null; + } + } } diff --git a/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabularyIndex.java b/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabularyIndex.java new file mode 100644 index 000000000000..6f350fc71ec9 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabularyIndex.java @@ -0,0 +1,45 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.content.authority; + +import java.util.Set; + +import org.dspace.discovery.configuration.DiscoverySearchFilterFacet; + +/** + * Helper class to transform a {@link org.dspace.content.authority.DSpaceControlledVocabulary} into a + * {@code BrowseIndexRest} + * cached by {@link org.dspace.content.authority.service.ChoiceAuthorityService#getVocabularyIndex(String)} + * + * @author Marie Verdonck (Atmire) on 04/05/2023 + */ +public class DSpaceControlledVocabularyIndex { + + protected DSpaceControlledVocabulary vocabulary; + protected Set metadataFields; + protected DiscoverySearchFilterFacet facetConfig; + + public DSpaceControlledVocabularyIndex(DSpaceControlledVocabulary controlledVocabulary, Set metadataFields, + DiscoverySearchFilterFacet facetConfig) { + this.vocabulary = controlledVocabulary; + this.metadataFields = metadataFields; + this.facetConfig = facetConfig; + } + + public DSpaceControlledVocabulary getVocabulary() { + return vocabulary; + } + + public Set getMetadataFields() { + return this.metadataFields; + } + + public DiscoverySearchFilterFacet getFacetConfig() { + return this.facetConfig; + } +} diff --git a/dspace-api/src/main/java/org/dspace/content/authority/service/ChoiceAuthorityService.java b/dspace-api/src/main/java/org/dspace/content/authority/service/ChoiceAuthorityService.java index eb34de29c14b..a9fd24e947b3 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/service/ChoiceAuthorityService.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/service/ChoiceAuthorityService.java @@ -15,6 +15,7 @@ import org.dspace.content.authority.Choice; import org.dspace.content.authority.ChoiceAuthority; import org.dspace.content.authority.Choices; +import org.dspace.content.authority.DSpaceControlledVocabularyIndex; /** * Broker for ChoiceAuthority plugins, and for other information configured @@ -220,4 +221,7 @@ public Choices getBestMatch(String fieldKey, String query, Collection collection * @return the parent Choice object if any */ public Choice getParentChoice(String authorityName, String vocabularyId, String locale); + + public DSpaceControlledVocabularyIndex getVocabularyIndex(String nameVocab); + } 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 636e7ccd2ae4..f4fd3ca0ef5c 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 @@ -92,6 +92,18 @@ public List getIndexAlwaysConfigurations() { return configs; } + /** + * @return All configurations for {@link org.dspace.discovery.configuration.DiscoverySearchFilterFacet} + */ + public List getAllFacetsConfig() { + List configs = new ArrayList<>(); + for (String key : map.keySet()) { + DiscoveryConfiguration config = map.get(key); + configs.addAll(config.getSidebarFacets()); + } + return configs; + } + public static void main(String[] args) { System.out.println(DSpaceServicesFactory.getInstance().getServiceManager().getServicesNames().size()); DiscoveryConfigurationService mainService = DSpaceServicesFactory.getInstance().getServiceManager() diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/BrowseIndexConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/BrowseIndexConverter.java index 6ee836e5fc9b..1e2899b396ab 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/BrowseIndexConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/BrowseIndexConverter.java @@ -7,6 +7,9 @@ */ package org.dspace.app.rest.converter; +import static org.dspace.app.rest.model.BrowseIndexRest.BROWSE_TYPE_FLAT; +import static org.dspace.app.rest.model.BrowseIndexRest.BROWSE_TYPE_VALUE_LIST; + import java.util.ArrayList; import java.util.List; @@ -33,14 +36,15 @@ public BrowseIndexRest convert(BrowseIndex obj, Projection projection) { bir.setId(obj.getName()); bir.setDataType(obj.getDataType()); bir.setOrder(obj.getDefaultOrder()); - bir.setMetadataBrowse(obj.isMetadataIndex()); List metadataList = new ArrayList(); if (obj.isMetadataIndex()) { for (String s : obj.getMetadata().split(",")) { metadataList.add(s.trim()); } + bir.setBrowseType(BROWSE_TYPE_VALUE_LIST); } else { metadataList.add(obj.getSortOption().getMetadata()); + bir.setBrowseType(BROWSE_TYPE_FLAT); } bir.setMetadataList(metadataList); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/HierarchicalBrowseConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/HierarchicalBrowseConverter.java new file mode 100644 index 000000000000..7b0cea9d8fb2 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/HierarchicalBrowseConverter.java @@ -0,0 +1,42 @@ +/** + * 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.converter; + +import java.util.ArrayList; + +import org.dspace.app.rest.model.BrowseIndexRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.content.authority.DSpaceControlledVocabularyIndex; +import org.springframework.stereotype.Component; + +/** + * This is the converter from a {@link org.dspace.content.authority.DSpaceControlledVocabularyIndex} to a + * {@link org.dspace.app.rest.model.BrowseIndexRest#BROWSE_TYPE_HIERARCHICAL} {@link org.dspace.app.rest.model.BrowseIndexRest} + * + * @author Marie Verdonck (Atmire) on 04/05/2023 + */ +@Component +public class HierarchicalBrowseConverter implements DSpaceConverter { + + @Override + public BrowseIndexRest convert(DSpaceControlledVocabularyIndex obj, Projection projection) { + BrowseIndexRest bir = new BrowseIndexRest(); + bir.setProjection(projection); + bir.setId(obj.getVocabulary().getPluginInstanceName()); + bir.setBrowseType(BrowseIndexRest.BROWSE_TYPE_HIERARCHICAL); + bir.setFacetType(obj.getFacetConfig().getIndexFieldName()); + bir.setVocabulary(obj.getVocabulary().getPluginInstanceName()); + bir.setMetadataList(new ArrayList<>(obj.getMetadataFields())); + return bir; + } + + @Override + public Class getModelClass() { + return DSpaceControlledVocabularyIndex.class; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/BrowseEntryHalLinkFactory.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/BrowseEntryHalLinkFactory.java index ee70dbf43132..9e515984fe03 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/BrowseEntryHalLinkFactory.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/BrowseEntryHalLinkFactory.java @@ -37,11 +37,11 @@ protected void addLinks(final BrowseEntryResource halResource, final Pageable pa UriComponentsBuilder baseLink = uriBuilder( getMethodOn(bix.getCategory(), bix.getType()).findRel(null, null, bix.getCategory(), English.plural(bix.getType()), bix.getId(), - BrowseIndexRest.ITEMS, null, null)); + BrowseIndexRest.LINK_ITEMS, null, null)); addFilterParams(baseLink, data); - list.add(buildLink(BrowseIndexRest.ITEMS, + list.add(buildLink(BrowseIndexRest.LINK_ITEMS, baseLink.build().encode().toUriString())); } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BrowseIndexRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BrowseIndexRest.java index 9fee6cbdbad2..f7978f00fdf5 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BrowseIndexRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BrowseIndexRest.java @@ -10,6 +10,7 @@ import java.util.List; import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import org.dspace.app.rest.RestResourceController; @@ -20,11 +21,11 @@ */ @LinksRest(links = { @LinkRest( - name = BrowseIndexRest.ITEMS, + name = BrowseIndexRest.LINK_ITEMS, method = "listBrowseItems" ), @LinkRest( - name = BrowseIndexRest.ENTRIES, + name = BrowseIndexRest.LINK_ENTRIES, method = "listBrowseEntries" ) }) @@ -35,20 +36,38 @@ public class BrowseIndexRest extends BaseObjectRest { public static final String CATEGORY = RestAddressableModel.DISCOVER; - public static final String ITEMS = "items"; - public static final String ENTRIES = "entries"; - - boolean metadataBrowse; - + public static final String LINK_ITEMS = "items"; + public static final String LINK_ENTRIES = "entries"; + public static final String LINK_VOCABULARY = "vocabulary"; + + // if the browse index has two levels, the 1st level shows the list of entries like author names, subjects, types, + // etc. the second level is the actual list of items linked to a specific entry + public static final String BROWSE_TYPE_VALUE_LIST = "valueList"; + // if the browse index has one level: the full list of items + public static final String BROWSE_TYPE_FLAT = "flatBrowse"; + // if the browse index should display the vocabulary tree. The 1st level shows the tree. + // The second level is the actual list of items linked to a specific entry + public static final String BROWSE_TYPE_HIERARCHICAL = "hierarchicalBrowse"; + + // Shared fields + String browseType; @JsonProperty(value = "metadata") List metadataList; + // Single browse index fields + @JsonInclude(JsonInclude.Include.NON_NULL) String dataType; - + @JsonInclude(JsonInclude.Include.NON_NULL) List sortOptions; - + @JsonInclude(JsonInclude.Include.NON_NULL) String order; + // Hierarchical browse fields + @JsonInclude(JsonInclude.Include.NON_NULL) + String facetType; + @JsonInclude(JsonInclude.Include.NON_NULL) + String vocabulary; + @JsonIgnore @Override public String getCategory() { @@ -60,14 +79,6 @@ public String getType() { return NAME; } - public boolean isMetadataBrowse() { - return metadataBrowse; - } - - public void setMetadataBrowse(boolean metadataBrowse) { - this.metadataBrowse = metadataBrowse; - } - public List getMetadataList() { return metadataList; } @@ -100,6 +111,38 @@ public void setSortOptions(List sortOptions) { this.sortOptions = sortOptions; } + /** + * - valueList => if the browse index has two levels, the 1st level shows the list of entries like author names, + * subjects, types, etc. the second level is the actual list of items linked to a specific entry + * - flatBrowse if the browse index has one level: the full list of items + * - hierarchicalBrowse if the browse index should display the vocabulary tree. The 1st level shows the tree. + * The second level is the actual list of items linked to a specific entry + */ + public void setBrowseType(String browseType) { + this.browseType = browseType; + } + + public String getBrowseType() { + return browseType; + } + + public void setFacetType(String facetType) { + this.facetType = facetType; + } + + public String getFacetType() { + return facetType; + } + + public void setVocabulary(String vocabulary) { + this.vocabulary = vocabulary; + } + + + public String getVocabulary() { + return vocabulary; + } + @Override public Class getController() { return RestResourceController.class; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/BrowseIndexResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/BrowseIndexResource.java index f6c821595f55..61158704ea5a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/BrowseIndexResource.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/BrowseIndexResource.java @@ -7,9 +7,20 @@ */ package org.dspace.app.rest.model.hateoas; +import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo; +import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn; + +import org.atteo.evo.inflector.English; +import org.dspace.app.rest.RestResourceController; import org.dspace.app.rest.model.BrowseIndexRest; +import org.dspace.app.rest.model.VocabularyRest; import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource; import org.dspace.app.rest.utils.Utils; +import org.dspace.content.authority.ChoiceAuthority; +import org.dspace.content.authority.factory.ContentAuthorityServiceFactory; +import org.dspace.content.authority.service.ChoiceAuthorityService; +import org.springframework.hateoas.Link; +import org.springframework.web.util.UriComponentsBuilder; /** * Browse Index Rest HAL Resource. The HAL Resource wraps the REST Resource @@ -19,15 +30,32 @@ */ @RelNameDSpaceResource(BrowseIndexRest.NAME) public class BrowseIndexResource extends DSpaceResource { + + public BrowseIndexResource(BrowseIndexRest bix, Utils utils) { super(bix, utils); // TODO: the following code will force the embedding of items and // entries in the browseIndex we need to find a way to populate the rels // array from the request/projection right now it is always null // super(bix, utils, "items", "entries"); - if (bix.isMetadataBrowse()) { - add(utils.linkToSubResource(bix, BrowseIndexRest.ENTRIES)); + if (bix.getBrowseType().equals(BrowseIndexRest.BROWSE_TYPE_VALUE_LIST)) { + add(utils.linkToSubResource(bix, BrowseIndexRest.LINK_ENTRIES)); + add(utils.linkToSubResource(bix, BrowseIndexRest.LINK_ITEMS)); + } + if (bix.getBrowseType().equals(BrowseIndexRest.BROWSE_TYPE_FLAT)) { + add(utils.linkToSubResource(bix, BrowseIndexRest.LINK_ITEMS)); + } + if (bix.getBrowseType().equals(BrowseIndexRest.BROWSE_TYPE_HIERARCHICAL)) { + ChoiceAuthorityService choiceAuthorityService = + ContentAuthorityServiceFactory.getInstance().getChoiceAuthorityService(); + ChoiceAuthority source = choiceAuthorityService.getChoiceAuthorityByAuthorityName(bix.getVocabulary()); + UriComponentsBuilder baseLink = linkTo( + methodOn(RestResourceController.class, VocabularyRest.AUTHENTICATION).findRel(null, + null, VocabularyRest.CATEGORY, + English.plural(VocabularyRest.NAME), source.getPluginInstanceName(), + "", null, null)).toUriComponentsBuilder(); + + add(Link.of(baseLink.build().encode().toUriString(), BrowseIndexRest.LINK_VOCABULARY)); } - add(utils.linkToSubResource(bix, BrowseIndexRest.ITEMS)); } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BrowseEntryLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BrowseEntryLinkRepository.java index 93224f78cd53..f608595c3dda 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BrowseEntryLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BrowseEntryLinkRepository.java @@ -40,7 +40,7 @@ * * @author Andrea Bollini (andrea.bollini at 4science.it) */ -@Component(BrowseIndexRest.CATEGORY + "." + BrowseIndexRest.NAME + "." + BrowseIndexRest.ENTRIES) +@Component(BrowseIndexRest.CATEGORY + "." + BrowseIndexRest.NAME + "." + BrowseIndexRest.LINK_ENTRIES) public class BrowseEntryLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { @@ -127,7 +127,8 @@ public Page listBrowseEntries(HttpServletRequest request, Strin @Override public boolean isEmbeddableRelation(Object data, String name) { BrowseIndexRest bir = (BrowseIndexRest) data; - if (bir.isMetadataBrowse() && "entries".equals(name)) { + if (bir.getBrowseType().equals(BrowseIndexRest.BROWSE_TYPE_VALUE_LIST) && + name.equals(BrowseIndexRest.LINK_ENTRIES)) { return true; } return false; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BrowseIndexRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BrowseIndexRestRepository.java index 01277ff29b19..c87cbc6c037e 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BrowseIndexRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BrowseIndexRestRepository.java @@ -13,7 +13,10 @@ import org.dspace.app.rest.model.BrowseIndexRest; import org.dspace.browse.BrowseException; import org.dspace.browse.BrowseIndex; +import org.dspace.content.authority.DSpaceControlledVocabularyIndex; +import org.dspace.content.authority.service.ChoiceAuthorityService; import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.security.access.prepost.PreAuthorize; @@ -27,20 +30,39 @@ @Component(BrowseIndexRest.CATEGORY + "." + BrowseIndexRest.NAME) public class BrowseIndexRestRepository extends DSpaceRestRepository { + @Autowired + private ChoiceAuthorityService choiceAuthorityService; + @Override @PreAuthorize("permitAll()") public BrowseIndexRest findOne(Context context, String name) { - BrowseIndexRest bi = null; + BrowseIndexRest bi = createFromMatchingBrowseIndex(name); + if (bi == null) { + bi = createFromMatchingVocabulary(name); + } + + return bi; + } + + private BrowseIndexRest createFromMatchingVocabulary(String name) { + DSpaceControlledVocabularyIndex vocabularyIndex = choiceAuthorityService.getVocabularyIndex(name); + if (vocabularyIndex != null) { + return converter.toRest(vocabularyIndex, utils.obtainProjection()); + } + return null; + } + + private BrowseIndexRest createFromMatchingBrowseIndex(String name) { BrowseIndex bix; try { - bix = BrowseIndex.getBrowseIndex(name); + bix = BrowseIndex.getBrowseIndex(name); } catch (BrowseException e) { throw new RuntimeException(e.getMessage(), e); } if (bix != null) { - bi = converter.toRest(bix, utils.obtainProjection()); + return converter.toRest(bix, utils.obtainProjection()); } - return bi; + return null; } @Override diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BrowseItemLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BrowseItemLinkRepository.java index 74aa9f38bfec..baa79bc80ae7 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BrowseItemLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BrowseItemLinkRepository.java @@ -42,7 +42,7 @@ * * @author Andrea Bollini (andrea.bollini at 4science.it) */ -@Component(BrowseIndexRest.CATEGORY + "." + BrowseIndexRest.NAME + "." + BrowseIndexRest.ITEMS) +@Component(BrowseIndexRest.CATEGORY + "." + BrowseIndexRest.NAME + "." + BrowseIndexRest.LINK_ITEMS) public class BrowseItemLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { @@ -155,7 +155,8 @@ public Page listBrowseItems(HttpServletRequest request, String browseN @Override public boolean isEmbeddableRelation(Object data, String name) { BrowseIndexRest bir = (BrowseIndexRest) data; - if (!bir.isMetadataBrowse() && "items".equals(name)) { + if (bir.getBrowseType().equals(BrowseIndexRest.BROWSE_TYPE_FLAT) && + name.equals(BrowseIndexRest.LINK_ITEMS)) { return true; } return false; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyRestRepository.java index dcdf71186bcb..fcc37d13160d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyRestRepository.java @@ -53,7 +53,7 @@ public class VocabularyRestRepository extends DSpaceRestRepository Date: Fri, 5 May 2023 12:35:40 +0200 Subject: [PATCH 124/510] 94299: Remove bitstreams in bulk via patch --- .../src/main/resources/Messages.properties | 2 + .../rest/BitstreamCategoryRestController.java | 63 ++++++++++++++++++ .../DSpaceApiExceptionControllerAdvice.java | 1 + .../RESTBitstreamNotFoundException.java | 51 +++++++++++++++ .../repository/BitstreamRestRepository.java | 19 ++++++ .../operation/BitstreamRemoveOperation.java | 65 +++++++++++++++++++ 6 files changed, 201 insertions(+) create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamCategoryRestController.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/exception/RESTBitstreamNotFoundException.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/BitstreamRemoveOperation.java diff --git a/dspace-api/src/main/resources/Messages.properties b/dspace-api/src/main/resources/Messages.properties index b537819c06a6..78e2774013a9 100644 --- a/dspace-api/src/main/resources/Messages.properties +++ b/dspace-api/src/main/resources/Messages.properties @@ -120,3 +120,5 @@ org.dspace.app.rest.exception.RESTEmptyWorkflowGroupException.message = Refused org.dspace.app.rest.exception.EPersonNameNotProvidedException.message = The eperson.firstname and eperson.lastname values need to be filled in org.dspace.app.rest.exception.GroupNameNotProvidedException.message = Cannot create group, no group name is provided org.dspace.app.rest.exception.GroupHasPendingWorkflowTasksException.message = Cannot delete group, the associated workflow role still has pending tasks +org.dspace.app.rest.exception.RESTBitstreamNotFoundException.message = Bitstream with uuid {0} could not be found in \ + the repository diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamCategoryRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamCategoryRestController.java new file mode 100644 index 000000000000..13929e5a9a73 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamCategoryRestController.java @@ -0,0 +1,63 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest; + +import static org.dspace.app.rest.utils.ContextUtil.obtainContext; + +import java.sql.SQLException; +import javax.servlet.http.HttpServletRequest; + +import com.fasterxml.jackson.databind.JsonNode; +import org.dspace.app.rest.model.BitstreamRest; +import org.dspace.app.rest.repository.BitstreamRestRepository; +import org.dspace.authorize.AuthorizeException; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.hateoas.RepresentationModel; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +/** + * REST controller for handling bulk updates to Bitstream resources. + *

+ * This controller is responsible for handling requests to the bitstream category, which allows for updating + * multiple bitstream resources in a single operation. + *

+ * + * @author Jens Vannerum (jens.vannerum@atmire.com) + */ +@RestController +@RequestMapping("/api/" + BitstreamRest.CATEGORY + "/" + BitstreamRest.PLURAL_NAME) +public class BitstreamCategoryRestController { + @Autowired + BitstreamRestRepository bitstreamRestRepository; + + /** + * Handles PATCH requests to the bitstream category for bulk updates of bitstream resources. + * + * @param request the HTTP request object. + * @param jsonNode the JSON representation of the bulk update operation, containing the updates to be applied. + * @return a ResponseEntity representing the HTTP response to be sent back to the client, in this case, a + * HTTP 204 No Content response since currently only a delete operation is supported. + * @throws SQLException if an error occurs while accessing the database. + * @throws AuthorizeException if the user is not authorized to perform the requested operation. + */ + @PreAuthorize("hasAuthority('ADMIN')") + @RequestMapping(method = RequestMethod.PATCH) + public ResponseEntity> patch(HttpServletRequest request, + @RequestBody(required = true) JsonNode jsonNode) + throws SQLException, AuthorizeException { + Context context = obtainContext(request); + bitstreamRestRepository.patchBitstreamsInBulk(context, jsonNode); + return ResponseEntity.noContent().build(); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/exception/DSpaceApiExceptionControllerAdvice.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/exception/DSpaceApiExceptionControllerAdvice.java index 6ded477813f1..3f55536666a0 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/exception/DSpaceApiExceptionControllerAdvice.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/exception/DSpaceApiExceptionControllerAdvice.java @@ -163,6 +163,7 @@ protected void handleOrcidValidationException(HttpServletRequest request, HttpSe EPersonNameNotProvidedException.class, GroupNameNotProvidedException.class, GroupHasPendingWorkflowTasksException.class, + RESTBitstreamNotFoundException.class }) protected void handleCustomUnprocessableEntityException(HttpServletRequest request, HttpServletResponse response, TranslatableException ex) throws IOException { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/exception/RESTBitstreamNotFoundException.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/exception/RESTBitstreamNotFoundException.java new file mode 100644 index 000000000000..a0b48e3c0dfc --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/exception/RESTBitstreamNotFoundException.java @@ -0,0 +1,51 @@ +/** + * 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.exception; + +import java.text.MessageFormat; + +import org.dspace.core.Context; +import org.dspace.core.I18nUtil; + +/** + *

Extend {@link UnprocessableEntityException} to provide a specific error message + * in the REST response. The error message is added to the response in + * {@link DSpaceApiExceptionControllerAdvice#handleCustomUnprocessableEntityException}, + * hence it should not contain sensitive or security-compromising info.

+ * + * @author Jens Vannerum (jens.vannerum@atmire.com) + */ +public class RESTBitstreamNotFoundException extends UnprocessableEntityException implements TranslatableException { + + public static String uuid; + + /** + * @param formatStr string with placeholders, ideally obtained using {@link I18nUtil} + * @return message with bitstream id substituted + */ + private static String formatMessage(String formatStr) { + MessageFormat fmt = new MessageFormat(formatStr); + return fmt.format(new String[]{uuid}); + } + + public static final String MESSAGE_KEY = "org.dspace.app.rest.exception.RESTBitstreamNotFoundException.message"; + + public RESTBitstreamNotFoundException(String uuid) { + super(formatMessage(I18nUtil.getMessage(MESSAGE_KEY))); + RESTBitstreamNotFoundException.uuid = uuid; + } + + public String getMessageKey() { + return MESSAGE_KEY; + } + + public String getLocalizedMessage(Context context) { + return formatMessage(I18nUtil.getMessage(MESSAGE_KEY, context)); + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BitstreamRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BitstreamRestRepository.java index 3696b3866808..8ef06ecbadc2 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BitstreamRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BitstreamRestRepository.java @@ -17,9 +17,12 @@ import java.util.UUID; import javax.servlet.http.HttpServletRequest; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.commons.lang3.StringUtils; import org.dspace.app.rest.Parameter; import org.dspace.app.rest.SearchRestMethod; +import org.dspace.app.rest.converter.JsonPatchConverter; import org.dspace.app.rest.exception.DSpaceBadRequestException; import org.dspace.app.rest.exception.RepositoryMethodNotImplementedException; import org.dspace.app.rest.exception.UnprocessableEntityException; @@ -292,4 +295,20 @@ public BundleRest performBitstreamMove(Context context, Bitstream bitstream, Bun return converter.toRest(targetBundle, utils.obtainProjection()); } + + /** + * Method that will transform the provided PATCH json body into a list of operations. + * The operations will be handled by a supporting class resolved by the + * {@link org.dspace.app.rest.repository.patch.ResourcePatch#patch} method. + * + * @param context The context + * @param jsonNode the json body provided from the request body + */ + public void patchBitstreamsInBulk(Context context, JsonNode jsonNode) throws SQLException { + ObjectMapper mapper = new ObjectMapper(); + JsonPatchConverter patchConverter = new JsonPatchConverter(mapper); + Patch patch = patchConverter.convert(jsonNode); + resourcePatch.patch(obtainContext(), null, patch.getOperations()); + context.commit(); + } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/BitstreamRemoveOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/BitstreamRemoveOperation.java new file mode 100644 index 000000000000..5d37e04cea6c --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/BitstreamRemoveOperation.java @@ -0,0 +1,65 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.repository.patch.operation; + +import java.io.IOException; +import java.sql.SQLException; +import java.util.UUID; + +import org.dspace.app.rest.exception.RESTBitstreamNotFoundException; +import org.dspace.app.rest.model.patch.Operation; +import org.dspace.authorize.AuthorizeException; +import org.dspace.content.Bitstream; +import org.dspace.content.service.BitstreamService; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * A PATCH operation for removing bitstreams in bulk from the repository. + * + * Example: + * curl -X PATCH http://${dspace.server.url}/api/core/bitstreams -H "Content-Type: application/json" + * -d '[ + * {"op": "remove", "path": "/bitstreams/${bitstream1UUID}"}, + * {"op": "remove", "path": "/bitstreams/${bitstream2UUID}"}, + * {"op": "remove", "path": "/bitstreams/${bitstream3UUID}"} + * ]' + * + * + * @author Jens Vannerum (jens.vannerum@atmire.com) + */ +@Component +public class BitstreamRemoveOperation extends PatchOperation { + @Autowired + BitstreamService bitstreamService; + private static final String OPERATION_PATH_BITSTREAM_REMOVE = "/bitstreams/"; + + @Override + public Bitstream perform(Context context, Bitstream resource, Operation operation) throws SQLException { + String bitstreamIDtoDelete = operation.getPath().replace(OPERATION_PATH_BITSTREAM_REMOVE, ""); + Bitstream bitstreamToDelete = bitstreamService.find(context, UUID.fromString(bitstreamIDtoDelete)); + if (bitstreamToDelete == null) { + throw new RESTBitstreamNotFoundException(bitstreamIDtoDelete); + } + + try { + bitstreamService.delete(context, bitstreamToDelete); + bitstreamService.update(context, bitstreamToDelete); + } catch (AuthorizeException | IOException e) { + throw new RuntimeException(e.getMessage(), e); + } + return null; + } + + @Override + public boolean supports(Object objectToMatch, Operation operation) { + return objectToMatch == null && operation.getOp().trim().equalsIgnoreCase(OPERATION_REMOVE) && + operation.getPath().trim().startsWith(OPERATION_PATH_BITSTREAM_REMOVE); + } +} From 09b56c2d99b770d376a3e49cb7f01b3ca0a4f5eb Mon Sep 17 00:00:00 2001 From: Jens Vannerum Date: Fri, 5 May 2023 13:05:34 +0200 Subject: [PATCH 125/510] 94299: Configurable limit on amount of patch operations --- .../app/rest/repository/BitstreamRestRepository.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BitstreamRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BitstreamRestRepository.java index 8ef06ecbadc2..586525bbd2fe 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BitstreamRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BitstreamRestRepository.java @@ -43,6 +43,7 @@ import org.dspace.content.service.CommunityService; import org.dspace.core.Context; import org.dspace.handle.service.HandleService; +import org.dspace.services.ConfigurationService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -77,6 +78,9 @@ public class BitstreamRestRepository extends DSpaceObjectRestRepository operationsLimit) { + throw new DSpaceBadRequestException("The number of operations in the patch is over the limit of " + + operationsLimit); + } resourcePatch.patch(obtainContext(), null, patch.getOperations()); context.commit(); } From 80706592aae50681dde850391770e6fe09ee6eca Mon Sep 17 00:00:00 2001 From: Jens Vannerum Date: Fri, 5 May 2023 13:07:47 +0200 Subject: [PATCH 126/510] Revert "94299 Multiple Bitstream deletion endpoint" This reverts commit 51d8874a --- .../app/rest/RestResourceController.java | 33 - .../repository/BitstreamRestRepository.java | 44 - .../rest/repository/DSpaceRestRepository.java | 18 - .../app/rest/BitstreamRestRepositoryIT.java | 955 ------------------ 4 files changed, 1050 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/RestResourceController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/RestResourceController.java index 24468660f016..b82b4830753c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/RestResourceController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/RestResourceController.java @@ -7,7 +7,6 @@ */ package org.dspace.app.rest; -import static org.dspace.app.rest.utils.ContextUtil.obtainContext; import static org.dspace.app.rest.utils.RegexUtils.REGEX_REQUESTMAPPING_IDENTIFIER_AS_DIGIT; import static org.dspace.app.rest.utils.RegexUtils.REGEX_REQUESTMAPPING_IDENTIFIER_AS_HEX32; import static org.dspace.app.rest.utils.RegexUtils.REGEX_REQUESTMAPPING_IDENTIFIER_AS_STRING_VERSION_STRONG; @@ -56,8 +55,6 @@ import org.dspace.app.rest.utils.RestRepositoryUtils; import org.dspace.app.rest.utils.Utils; import org.dspace.authorize.AuthorizeException; -import org.dspace.content.DSpaceObject; -import org.dspace.core.Context; import org.dspace.util.UUIDUtils; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; @@ -1053,13 +1050,6 @@ public ResponseEntity> delete(HttpServletRequest request, return deleteInternal(apiCategory, model, uuid); } - @RequestMapping(method = RequestMethod.DELETE, consumes = {"text/uri-list"}) - public ResponseEntity> delete(HttpServletRequest request, @PathVariable String apiCategory, - @PathVariable String model) - throws HttpRequestMethodNotSupportedException { - return deleteUriListInternal(request, apiCategory, model); - } - /** * Internal method to delete resource. * @@ -1077,29 +1067,6 @@ private ResponseEntity> deleteI return ControllerUtils.toEmptyResponse(HttpStatus.NO_CONTENT); } - public ResponseEntity> deleteUriListInternal( - HttpServletRequest request, - String apiCategory, - String model) - throws HttpRequestMethodNotSupportedException { - checkModelPluralForm(apiCategory, model); - DSpaceRestRepository repository = utils.getResourceRepository(apiCategory, model); - Context context = obtainContext(request); - List dsoStringList = utils.getStringListFromRequest(request); - List dsoList = utils.constructDSpaceObjectList(context, dsoStringList); - if (dsoStringList.size() != dsoList.size()) { - throw new ResourceNotFoundException("One or more bitstreams could not be found."); - } - try { - repository.delete(dsoList); - } catch (ClassCastException e) { - log.error("Something went wrong whilst creating the object for apiCategory: " + apiCategory + - " and model: " + model, e); - return ControllerUtils.toEmptyResponse(HttpStatus.INTERNAL_SERVER_ERROR); - } - return ControllerUtils.toEmptyResponse(HttpStatus.NO_CONTENT); - } - /** * Execute a PUT request for an entity with id of type UUID; * diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BitstreamRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BitstreamRestRepository.java index 586525bbd2fe..454b6f8453d6 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BitstreamRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BitstreamRestRepository.java @@ -10,8 +10,6 @@ import java.io.IOException; import java.io.InputStream; import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.UUID; @@ -156,48 +154,6 @@ protected void delete(Context context, UUID id) throws AuthorizeException { } } - @Override - protected void deleteList(Context context, List dsoList) - throws SQLException, AuthorizeException { - // check if list is empty - if (dsoList.isEmpty()) { - throw new ResourceNotFoundException("No bitstreams given."); - } - // check if every DSO is a Bitstream - if (dsoList.stream().anyMatch(dso -> !(dso instanceof Bitstream))) { - throw new UnprocessableEntityException("Not all given items are bitstreams."); - } - // check that they're all part of the same Item - List parents = new ArrayList<>(); - for (DSpaceObject dso : dsoList) { - Bitstream bit = bs.find(context, dso.getID()); - DSpaceObject bitstreamParent = bs.getParentObject(context, bit); - if (bit == null) { - throw new ResourceNotFoundException("The bitstream with uuid " + dso.getID() + " could not be found"); - } - // we have to check if the bitstream has already been deleted - if (bit.isDeleted()) { - throw new UnprocessableEntityException("The bitstream with uuid " + bit.getID() - + " was already deleted"); - } else { - parents.add(bitstreamParent); - } - } - if (parents.stream().distinct().count() > 1) { - throw new UnprocessableEntityException("Not all given items are part of the same Item."); - } - // delete all Bitstreams - Iterator iterator = dsoList.iterator(); - while (iterator.hasNext()) { - Bitstream bit = (Bitstream) iterator.next(); - try { - bs.delete(context, bit); - } catch (SQLException | IOException e) { - throw new RuntimeException(e.getMessage(), e); - } - } - } - /** * Find the bitstream for the provided handle and sequence or filename. * When a bitstream can be found with the sequence ID it will be returned if the user has "METADATA_READ" access. diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/DSpaceRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/DSpaceRestRepository.java index 219b7c4123b2..01f127eca5ac 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/DSpaceRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/DSpaceRestRepository.java @@ -26,7 +26,6 @@ import org.dspace.app.rest.model.RestAddressableModel; import org.dspace.app.rest.model.patch.Patch; import org.dspace.authorize.AuthorizeException; -import org.dspace.content.DSpaceObject; import org.dspace.content.service.MetadataFieldService; import org.dspace.core.Context; import org.springframework.beans.factory.BeanNameAware; @@ -257,23 +256,6 @@ public void deleteAll() { } - public void delete(List dsoList) { - Context context = obtainContext(); - try { - getThisRepository().deleteList(context, dsoList); - context.commit(); - } catch (AuthorizeException e) { - throw new RESTAuthorizationException(e); - } catch (SQLException ex) { - throw new RuntimeException(ex.getMessage(), ex); - } - } - - protected void deleteList(Context context, List list) - throws AuthorizeException, SQLException, RepositoryMethodNotImplementedException { - throw new RepositoryMethodNotImplementedException("No implementation found; Method not allowed!", ""); - } - @Override /** * This method cannot be implemented we required all the find method to be paginated diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java index 391d9e419330..f9c1e469fcfe 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java @@ -13,7 +13,6 @@ import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; -import static org.springframework.data.rest.webmvc.RestMediaTypes.TEXT_URI_LIST; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; @@ -1202,960 +1201,6 @@ public void deleteDeleted() throws Exception { .andExpect(status().isNotFound()); } - @Test - public void deleteListOneBitstream() throws Exception { - - //We turn off the authorization system in order to create the structure as defined below - context.turnOffAuthorisationSystem(); - - //** GIVEN ** - //1. A community-collection structure with one parent community with sub-community and one collection. - parentCommunity = CommunityBuilder.createCommunity(context) - .withName("Parent Community") - .build(); - Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) - .withName("Sub Community") - .build(); - Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); - - //2. One public items that is readable by Anonymous - Item publicItem1 = ItemBuilder.createItem(context, col1) - .withTitle("Test") - .withIssueDate("2010-10-17") - .withAuthor("Smith, Donald") - .withSubject("ExtraEntry") - .build(); - - String bitstreamContent = "ThisIsSomeDummyText"; - //Add a bitstream to an item - Bitstream bitstream = null; - try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { - bitstream = BitstreamBuilder. - createBitstream(context, publicItem1, is) - .withName("Bitstream") - .withDescription("Description") - .withMimeType("text/plain") - .build(); - } - - context.restoreAuthSystemState(); - - String token = getAuthToken(admin.getEmail(), password); - - // Delete - getClient(token).perform(delete("/api/core/bitstreams") - .contentType(TEXT_URI_LIST) - .content("http://localhost:8080/server/api/core/bitstreams/" + bitstream.getID())) - .andExpect(status().is(204)); - - // Verify 404 after delete - getClient(token).perform(get("/api/core/bitstreams/" + bitstream.getID())) - .andExpect(status().isNotFound()); - } - - @Test - public void deleteListOneOfMultipleBitstreams() throws Exception { - - //We turn off the authorization system in order to create the structure as defined below - context.turnOffAuthorisationSystem(); - - //** GIVEN ** - //1. A community-collection structure with one parent community with sub-community and one collection. - parentCommunity = CommunityBuilder.createCommunity(context) - .withName("Parent Community") - .build(); - Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) - .withName("Sub Community") - .build(); - Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); - - //2. One public items that is readable by Anonymous - Item publicItem1 = ItemBuilder.createItem(context, col1) - .withTitle("Test") - .withIssueDate("2010-10-17") - .withAuthor("Smith, Donald") - .withSubject("ExtraEntry") - .build(); - - // Add 3 bitstreams to the item - String bitstreamContent1 = "ThisIsSomeDummyText1"; - Bitstream bitstream1 = null; - try (InputStream is = IOUtils.toInputStream(bitstreamContent1, CharEncoding.UTF_8)) { - bitstream1 = BitstreamBuilder. - createBitstream(context, publicItem1, is) - .withName("Bitstream1") - .withDescription("Description1") - .withMimeType("text/plain") - .build(); - } - - String bitstreamContent2 = "ThisIsSomeDummyText2"; - Bitstream bitstream2 = null; - try (InputStream is = IOUtils.toInputStream(bitstreamContent2, CharEncoding.UTF_8)) { - bitstream2 = BitstreamBuilder. - createBitstream(context, publicItem1, is) - .withName("Bitstream2") - .withDescription("Description2") - .withMimeType("text/plain") - .build(); - } - - String bitstreamContent3 = "ThisIsSomeDummyText3"; - Bitstream bitstream3 = null; - try (InputStream is = IOUtils.toInputStream(bitstreamContent3, CharEncoding.UTF_8)) { - bitstream3 = BitstreamBuilder. - createBitstream(context, publicItem1, is) - .withName("Bitstream3") - .withDescription("Description3") - .withMimeType("text/plain") - .build(); - } - - context.restoreAuthSystemState(); - - String token = getAuthToken(admin.getEmail(), password); - - // Delete bitstream1 - getClient(token).perform(delete("/api/core/bitstreams") - .contentType(TEXT_URI_LIST) - .content("http://localhost:8080/server/api/core/bitstreams/" + bitstream1.getID())) - .andExpect(status().is(204)); - - // Verify 404 after delete for bitstream1 - getClient(token).perform(get("/api/core/bitstreams/" + bitstream1.getID())) - .andExpect(status().isNotFound()); - - // check that bitstream2 still exists - getClient().perform(get("/api/core/bitstreams/" + bitstream2.getID())) - .andExpect(status().isOk()) - .andExpect(jsonPath("$", HalMatcher.matchNoEmbeds())); - - // check that bitstream3 still exists - getClient().perform(get("/api/core/bitstreams/" + bitstream3.getID())) - .andExpect(status().isOk()) - .andExpect(jsonPath("$", HalMatcher.matchNoEmbeds())) - ; - } - - @Test - public void deleteListAllBitstreams() throws Exception { - - //We turn off the authorization system in order to create the structure as defined below - context.turnOffAuthorisationSystem(); - - //** GIVEN ** - //1. A community-collection structure with one parent community with sub-community and one collection. - parentCommunity = CommunityBuilder.createCommunity(context) - .withName("Parent Community") - .build(); - Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) - .withName("Sub Community") - .build(); - Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); - - //2. One public items that is readable by Anonymous - Item publicItem1 = ItemBuilder.createItem(context, col1) - .withTitle("Test") - .withIssueDate("2010-10-17") - .withAuthor("Smith, Donald") - .withSubject("ExtraEntry") - .build(); - - // Add 3 bitstreams to the item - String bitstreamContent1 = "ThisIsSomeDummyText1"; - Bitstream bitstream1 = null; - try (InputStream is = IOUtils.toInputStream(bitstreamContent1, CharEncoding.UTF_8)) { - bitstream1 = BitstreamBuilder. - createBitstream(context, publicItem1, is) - .withName("Bitstream1") - .withDescription("Description1") - .withMimeType("text/plain") - .build(); - } - - String bitstreamContent2 = "ThisIsSomeDummyText2"; - Bitstream bitstream2 = null; - try (InputStream is = IOUtils.toInputStream(bitstreamContent2, CharEncoding.UTF_8)) { - bitstream2 = BitstreamBuilder. - createBitstream(context, publicItem1, is) - .withName("Bitstream2") - .withDescription("Description2") - .withMimeType("text/plain") - .build(); - } - - String bitstreamContent3 = "ThisIsSomeDummyText3"; - Bitstream bitstream3 = null; - try (InputStream is = IOUtils.toInputStream(bitstreamContent3, CharEncoding.UTF_8)) { - bitstream3 = BitstreamBuilder. - createBitstream(context, publicItem1, is) - .withName("Bitstream3") - .withDescription("Description3") - .withMimeType("text/plain") - .build(); - } - - context.restoreAuthSystemState(); - - String token = getAuthToken(admin.getEmail(), password); - - // Delete all bitstreams - getClient(token).perform(delete("/api/core/bitstreams") - .contentType(TEXT_URI_LIST) - .content("http://localhost:8080/server/api/core/bitstreams/" + bitstream1.getID() - + " \n http://localhost:8080/server/api/core/bitstreams/" + bitstream2.getID() - + " \n http://localhost:8080/server/api/core/bitstreams/" + bitstream3.getID())) - .andExpect(status().is(204)); - - // Verify 404 after delete for bitstream1 - getClient(token).perform(get("/api/core/bitstreams/" + bitstream1.getID())) - .andExpect(status().isNotFound()); - - // Verify 404 after delete for bitstream2 - getClient(token).perform(get("/api/core/bitstreams/" + bitstream2.getID())) - .andExpect(status().isNotFound()); - - // Verify 404 after delete for bitstream3 - getClient(token).perform(get("/api/core/bitstreams/" + bitstream3.getID())) - .andExpect(status().isNotFound()); - } - - @Test - public void deleteListForbidden() throws Exception { - - //We turn off the authorization system in order to create the structure as defined below - context.turnOffAuthorisationSystem(); - - //** GIVEN ** - //1. A community-collection structure with one parent community with sub-community and one collection. - parentCommunity = CommunityBuilder.createCommunity(context) - .withName("Parent Community") - .build(); - Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) - .withName("Sub Community") - .build(); - Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); - - //2. One public items that is readable by Anonymous - Item publicItem1 = ItemBuilder.createItem(context, col1) - .withTitle("Test") - .withIssueDate("2010-10-17") - .withAuthor("Smith, Donald") - .withSubject("ExtraEntry") - .build(); - - // Add 3 bitstreams to the item - String bitstreamContent1 = "ThisIsSomeDummyText1"; - Bitstream bitstream1 = null; - try (InputStream is = IOUtils.toInputStream(bitstreamContent1, CharEncoding.UTF_8)) { - bitstream1 = BitstreamBuilder. - createBitstream(context, publicItem1, is) - .withName("Bitstream1") - .withDescription("Description1") - .withMimeType("text/plain") - .build(); - } - - String bitstreamContent2 = "ThisIsSomeDummyText2"; - Bitstream bitstream2 = null; - try (InputStream is = IOUtils.toInputStream(bitstreamContent2, CharEncoding.UTF_8)) { - bitstream2 = BitstreamBuilder. - createBitstream(context, publicItem1, is) - .withName("Bitstream2") - .withDescription("Description2") - .withMimeType("text/plain") - .build(); - } - - String bitstreamContent3 = "ThisIsSomeDummyText3"; - Bitstream bitstream3 = null; - try (InputStream is = IOUtils.toInputStream(bitstreamContent3, CharEncoding.UTF_8)) { - bitstream3 = BitstreamBuilder. - createBitstream(context, publicItem1, is) - .withName("Bitstream3") - .withDescription("Description3") - .withMimeType("text/plain") - .build(); - } - - context.restoreAuthSystemState(); - - String token = getAuthToken(eperson.getEmail(), password); - - // Delete using an unauthorized user - getClient(token).perform(delete("/api/core/bitstreams") - .contentType(TEXT_URI_LIST) - .content("http://localhost:8080/server/api/core/bitstreams/" + bitstream1.getID() - + " \n http://localhost:8080/server/api/core/bitstreams/" + bitstream2.getID() - + " \n http://localhost:8080/server/api/core/bitstreams/" + bitstream3.getID())) - .andExpect(status().isForbidden()); - - // Verify the bitstreams are still here - getClient().perform(get("/api/core/bitstreams/" + bitstream1.getID())) - .andExpect(status().isOk()); - - getClient().perform(get("/api/core/bitstreams/" + bitstream2.getID())) - .andExpect(status().isOk()); - - getClient().perform(get("/api/core/bitstreams/" + bitstream3.getID())) - .andExpect(status().isOk()); - } - - @Test - public void deleteListUnauthorized() throws Exception { - - //We turn off the authorization system in order to create the structure as defined below - context.turnOffAuthorisationSystem(); - - //** GIVEN ** - //1. A community-collection structure with one parent community with sub-community and one collection. - parentCommunity = CommunityBuilder.createCommunity(context) - .withName("Parent Community") - .build(); - Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) - .withName("Sub Community") - .build(); - Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); - - //2. One public items that is readable by Anonymous - Item publicItem1 = ItemBuilder.createItem(context, col1) - .withTitle("Test") - .withIssueDate("2010-10-17") - .withAuthor("Smith, Donald") - .withSubject("ExtraEntry") - .build(); - - // Add 3 bitstreams to the item - String bitstreamContent1 = "ThisIsSomeDummyText1"; - Bitstream bitstream1 = null; - try (InputStream is = IOUtils.toInputStream(bitstreamContent1, CharEncoding.UTF_8)) { - bitstream1 = BitstreamBuilder. - createBitstream(context, publicItem1, is) - .withName("Bitstream1") - .withDescription("Description1") - .withMimeType("text/plain") - .build(); - } - - String bitstreamContent2 = "ThisIsSomeDummyText2"; - Bitstream bitstream2 = null; - try (InputStream is = IOUtils.toInputStream(bitstreamContent2, CharEncoding.UTF_8)) { - bitstream2 = BitstreamBuilder. - createBitstream(context, publicItem1, is) - .withName("Bitstream2") - .withDescription("Description2") - .withMimeType("text/plain") - .build(); - } - - String bitstreamContent3 = "ThisIsSomeDummyText3"; - Bitstream bitstream3 = null; - try (InputStream is = IOUtils.toInputStream(bitstreamContent3, CharEncoding.UTF_8)) { - bitstream3 = BitstreamBuilder. - createBitstream(context, publicItem1, is) - .withName("Bitstream3") - .withDescription("Description3") - .withMimeType("text/plain") - .build(); - } - - context.restoreAuthSystemState(); - - // Delete as anonymous - getClient().perform(delete("/api/core/bitstreams") - .contentType(TEXT_URI_LIST) - .content("http://localhost:8080/server/api/core/bitstreams/" + bitstream1.getID() - + " \n http://localhost:8080/server/api/core/bitstreams/" + bitstream2.getID() - + " \n http://localhost:8080/server/api/core/bitstreams/" + bitstream3.getID())) - .andExpect(status().isUnauthorized()); - - // Verify the bitstreams are still here - getClient().perform(get("/api/core/bitstreams/" + bitstream1.getID())) - .andExpect(status().isOk()); - - getClient().perform(get("/api/core/bitstreams/" + bitstream2.getID())) - .andExpect(status().isOk()); - - getClient().perform(get("/api/core/bitstreams/" + bitstream3.getID())) - .andExpect(status().isOk()); - } - - @Test - public void deleteListEmpty() throws Exception { - - //We turn off the authorization system in order to create the structure as defined below - context.turnOffAuthorisationSystem(); - - //** GIVEN ** - //1. A community-collection structure with one parent community with sub-community and one collection. - parentCommunity = CommunityBuilder.createCommunity(context) - .withName("Parent Community") - .build(); - Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) - .withName("Sub Community") - .build(); - Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); - - //2. One public items that is readable by Anonymous - Item publicItem1 = ItemBuilder.createItem(context, col1) - .withTitle("Test") - .withIssueDate("2010-10-17") - .withAuthor("Smith, Donald") - .withSubject("ExtraEntry") - .build(); - - // Add 3 bitstreams to the item - String bitstreamContent1 = "ThisIsSomeDummyText1"; - Bitstream bitstream1 = null; - try (InputStream is = IOUtils.toInputStream(bitstreamContent1, CharEncoding.UTF_8)) { - bitstream1 = BitstreamBuilder. - createBitstream(context, publicItem1, is) - .withName("Bitstream1") - .withDescription("Description1") - .withMimeType("text/plain") - .build(); - } - - String bitstreamContent2 = "ThisIsSomeDummyText2"; - Bitstream bitstream2 = null; - try (InputStream is = IOUtils.toInputStream(bitstreamContent2, CharEncoding.UTF_8)) { - bitstream2 = BitstreamBuilder. - createBitstream(context, publicItem1, is) - .withName("Bitstream2") - .withDescription("Description2") - .withMimeType("text/plain") - .build(); - } - - String bitstreamContent3 = "ThisIsSomeDummyText3"; - Bitstream bitstream3 = null; - try (InputStream is = IOUtils.toInputStream(bitstreamContent3, CharEncoding.UTF_8)) { - bitstream3 = BitstreamBuilder. - createBitstream(context, publicItem1, is) - .withName("Bitstream3") - .withDescription("Description3") - .withMimeType("text/plain") - .build(); - } - - context.restoreAuthSystemState(); - - String token = getAuthToken(admin.getEmail(), password); - - // Delete with empty list throws 404 - getClient(token).perform(delete("/api/core/bitstreams") - .contentType(TEXT_URI_LIST) - .content("")) - .andExpect(status().isNotFound()); - - // Verify the bitstreams are still here - getClient(token).perform(get("/api/core/bitstreams/" + bitstream1.getID())) - .andExpect(status().isOk()); - - getClient(token).perform(get("/api/core/bitstreams/" + bitstream2.getID())) - .andExpect(status().isOk()); - - getClient(token).perform(get("/api/core/bitstreams/" + bitstream3.getID())) - .andExpect(status().isOk()); - } - - @Test - public void deleteListNotBitstream() throws Exception { - - //We turn off the authorization system in order to create the structure as defined below - context.turnOffAuthorisationSystem(); - - //** GIVEN ** - //1. A community-collection structure with one parent community with sub-community and one collection. - parentCommunity = CommunityBuilder.createCommunity(context) - .withName("Parent Community") - .build(); - Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) - .withName("Sub Community") - .build(); - Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); - - //2. One public items that is readable by Anonymous - Item publicItem1 = ItemBuilder.createItem(context, col1) - .withTitle("Test") - .withIssueDate("2010-10-17") - .withAuthor("Smith, Donald") - .withSubject("ExtraEntry") - .build(); - - // Add 3 bitstreams to the item - String bitstreamContent1 = "ThisIsSomeDummyText1"; - Bitstream bitstream1 = null; - try (InputStream is = IOUtils.toInputStream(bitstreamContent1, CharEncoding.UTF_8)) { - bitstream1 = BitstreamBuilder. - createBitstream(context, publicItem1, is) - .withName("Bitstream1") - .withDescription("Description1") - .withMimeType("text/plain") - .build(); - } - - String bitstreamContent2 = "ThisIsSomeDummyText2"; - Bitstream bitstream2 = null; - try (InputStream is = IOUtils.toInputStream(bitstreamContent2, CharEncoding.UTF_8)) { - bitstream2 = BitstreamBuilder. - createBitstream(context, publicItem1, is) - .withName("Bitstream2") - .withDescription("Description2") - .withMimeType("text/plain") - .build(); - } - - String bitstreamContent3 = "ThisIsSomeDummyText3"; - Bitstream bitstream3 = null; - try (InputStream is = IOUtils.toInputStream(bitstreamContent3, CharEncoding.UTF_8)) { - bitstream3 = BitstreamBuilder. - createBitstream(context, publicItem1, is) - .withName("Bitstream3") - .withDescription("Description3") - .withMimeType("text/plain") - .build(); - } - - context.restoreAuthSystemState(); - - String token = getAuthToken(admin.getEmail(), password); - - // Delete with list containing non-Bitstream throws 422 - getClient(token).perform(delete("/api/core/bitstreams") - .contentType(TEXT_URI_LIST) - .content("http://localhost:8080/server/api/core/bitstreams/" + bitstream1.getID() - + " \n http://localhost:8080/server/api/core/bitstreams/" + bitstream2.getID() - + " \n http://localhost:8080/server/api/core/bitstreams/" + bitstream3.getID() - + " \n http://localhost:8080/server/api/core/items/" + publicItem1.getID())) - .andExpect(status().is(422)); - - // Verify the bitstreams are still here - getClient(token).perform(get("/api/core/bitstreams/" + bitstream1.getID())) - .andExpect(status().isOk()); - - getClient(token).perform(get("/api/core/bitstreams/" + bitstream2.getID())) - .andExpect(status().isOk()); - - getClient(token).perform(get("/api/core/bitstreams/" + bitstream3.getID())) - .andExpect(status().isOk()); - } - - @Test - public void deleteListDifferentItems() throws Exception { - - //We turn off the authorization system in order to create the structure as defined below - context.turnOffAuthorisationSystem(); - - //** GIVEN ** - //1. A community-collection structure with one parent community with sub-community and one collection. - parentCommunity = CommunityBuilder.createCommunity(context) - .withName("Parent Community") - .build(); - Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) - .withName("Sub Community") - .build(); - Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); - - //2. Two public items that is readable by Anonymous - Item publicItem1 = ItemBuilder.createItem(context, col1) - .withTitle("Test") - .withIssueDate("2010-10-17") - .withAuthor("Smith, Donald") - .withSubject("ExtraEntry") - .build(); - - Item publicItem2 = ItemBuilder.createItem(context, col1) - .withTitle("Test") - .withIssueDate("2010-10-17") - .withAuthor("Smith, Donald") - .withSubject("ExtraEntry") - .build(); - - // Add 1 bitstream to each item - String bitstreamContent1 = "ThisIsSomeDummyText1"; - Bitstream bitstream1 = null; - try (InputStream is = IOUtils.toInputStream(bitstreamContent1, CharEncoding.UTF_8)) { - bitstream1 = BitstreamBuilder. - createBitstream(context, publicItem1, is) - .withName("Bitstream1") - .withDescription("Description1") - .withMimeType("text/plain") - .build(); - } - - String bitstreamContent2 = "ThisIsSomeDummyText2"; - Bitstream bitstream2 = null; - try (InputStream is = IOUtils.toInputStream(bitstreamContent2, CharEncoding.UTF_8)) { - bitstream2 = BitstreamBuilder. - createBitstream(context, publicItem2, is) - .withName("Bitstream2") - .withDescription("Description2") - .withMimeType("text/plain") - .build(); - } - - context.restoreAuthSystemState(); - - String token = getAuthToken(admin.getEmail(), password); - - // Delete with list containing Bitstreams from different items throws 422 - getClient(token).perform(delete("/api/core/bitstreams") - .contentType(TEXT_URI_LIST) - .content("http://localhost:8080/server/api/core/bitstreams/" + bitstream1.getID() - + " \n http://localhost:8080/server/api/core/bitstreams/" + bitstream2.getID())) - .andExpect(status().is(422)); - - // Verify the bitstreams are still here - getClient(token).perform(get("/api/core/bitstreams/" + bitstream1.getID())) - .andExpect(status().isOk()); - - getClient(token).perform(get("/api/core/bitstreams/" + bitstream2.getID())) - .andExpect(status().isOk()); - - } - - @Test - public void deleteListLogo() throws Exception { - // We turn off the authorization system in order to create the structure as defined below - context.turnOffAuthorisationSystem(); - - // ** GIVEN ** - // 1. A community with a logo - parentCommunity = CommunityBuilder.createCommunity(context).withName("Community").withLogo("logo_community") - .build(); - - // 2. A collection with a logo - Collection col = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection") - .withLogo("logo_collection").build(); - - context.restoreAuthSystemState(); - - String token = getAuthToken(admin.getEmail(), password); - - // trying to DELETE parentCommunity logo and collection logo should work - // we have to delete them separately otherwise it will throw 422 as they belong to different items - getClient(token).perform(delete("/api/core/bitstreams") - .contentType(TEXT_URI_LIST) - .content("http://localhost:8080/server/api/core/bitstreams/" + parentCommunity.getLogo().getID())) - .andExpect(status().is(204)); - - getClient(token).perform(delete("/api/core/bitstreams") - .contentType(TEXT_URI_LIST) - .content("http://localhost:8080/server/api/core/bitstreams/" + col.getLogo().getID())) - .andExpect(status().is(204)); - - // Verify 404 after delete for parentCommunity logo - getClient(token).perform(get("/api/core/bitstreams/" + parentCommunity.getLogo().getID())) - .andExpect(status().isNotFound()); - - // Verify 404 after delete for collection logo - getClient(token).perform(get("/api/core/bitstreams/" + col.getLogo().getID())) - .andExpect(status().isNotFound()); - } - - @Test - public void deleteListMissing() throws Exception { - String token = getAuthToken(admin.getEmail(), password); - - // Delete - getClient(token).perform(delete("/api/core/bitstreams") - .contentType(TEXT_URI_LIST) - .content("http://localhost:8080/server/api/core/bitstreams/1c11f3f1-ba1f-4f36-908a-3f1ea9a557eb")) - .andExpect(status().isNotFound()); - - // Verify 404 after failed delete - getClient(token).perform(delete("/api/core/bitstreams") - .contentType(TEXT_URI_LIST) - .content("http://localhost:8080/server/api/core/bitstreams/1c11f3f1-ba1f-4f36-908a-3f1ea9a557eb")) - .andExpect(status().isNotFound()); - } - - @Test - public void deleteListOneMissing() throws Exception { - - //We turn off the authorization system in order to create the structure as defined below - context.turnOffAuthorisationSystem(); - - //** GIVEN ** - //1. A community-collection structure with one parent community with sub-community and one collection. - parentCommunity = CommunityBuilder.createCommunity(context) - .withName("Parent Community") - .build(); - Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) - .withName("Sub Community") - .build(); - Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); - - //2. One public items that is readable by Anonymous - Item publicItem1 = ItemBuilder.createItem(context, col1) - .withTitle("Test") - .withIssueDate("2010-10-17") - .withAuthor("Smith, Donald") - .withSubject("ExtraEntry") - .build(); - - // Add 3 bitstreams to the item - String bitstreamContent1 = "ThisIsSomeDummyText1"; - Bitstream bitstream1 = null; - try (InputStream is = IOUtils.toInputStream(bitstreamContent1, CharEncoding.UTF_8)) { - bitstream1 = BitstreamBuilder. - createBitstream(context, publicItem1, is) - .withName("Bitstream1") - .withDescription("Description1") - .withMimeType("text/plain") - .build(); - } - - String bitstreamContent2 = "ThisIsSomeDummyText2"; - Bitstream bitstream2 = null; - try (InputStream is = IOUtils.toInputStream(bitstreamContent2, CharEncoding.UTF_8)) { - bitstream2 = BitstreamBuilder. - createBitstream(context, publicItem1, is) - .withName("Bitstream2") - .withDescription("Description2") - .withMimeType("text/plain") - .build(); - } - - String bitstreamContent3 = "ThisIsSomeDummyText3"; - Bitstream bitstream3 = null; - try (InputStream is = IOUtils.toInputStream(bitstreamContent3, CharEncoding.UTF_8)) { - bitstream3 = BitstreamBuilder. - createBitstream(context, publicItem1, is) - .withName("Bitstream3") - .withDescription("Description3") - .withMimeType("text/plain") - .build(); - } - - context.restoreAuthSystemState(); - - String token = getAuthToken(admin.getEmail(), password); - - // Delete all bitstreams and a missing bitstream returns 404 - getClient(token).perform(delete("/api/core/bitstreams") - .contentType(TEXT_URI_LIST) - .content("http://localhost:8080/server/api/core/bitstreams/" + bitstream1.getID() - + " \n http://localhost:8080/server/api/core/bitstreams/" + bitstream2.getID() - + " \n http://localhost:8080/server/api/core/bitstreams/" + bitstream3.getID() - + " \n http://localhost:8080/server/api/core/bitstreams/1c11f3f1-ba1f-4f36-908a-3f1ea9a557eb")) - .andExpect(status().isNotFound()); - - // Verify the bitstreams are still here - getClient(token).perform(get("/api/core/bitstreams/" + bitstream1.getID())) - .andExpect(status().isOk()); - - getClient(token).perform(get("/api/core/bitstreams/" + bitstream2.getID())) - .andExpect(status().isOk()); - - getClient(token).perform(get("/api/core/bitstreams/" + bitstream3.getID())) - .andExpect(status().isOk()); - } - - @Test - public void deleteListOneMissingDifferentItems() throws Exception { - - //We turn off the authorization system in order to create the structure as defined below - context.turnOffAuthorisationSystem(); - - //** GIVEN ** - //1. A community-collection structure with one parent community with sub-community and one collection. - parentCommunity = CommunityBuilder.createCommunity(context) - .withName("Parent Community") - .build(); - Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) - .withName("Sub Community") - .build(); - Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); - - //2. Two public items that is readable by Anonymous - Item publicItem1 = ItemBuilder.createItem(context, col1) - .withTitle("Test") - .withIssueDate("2010-10-17") - .withAuthor("Smith, Donald") - .withSubject("ExtraEntry") - .build(); - - Item publicItem2 = ItemBuilder.createItem(context, col1) - .withTitle("Test") - .withIssueDate("2010-10-17") - .withAuthor("Smith, Donald") - .withSubject("ExtraEntry") - .build(); - - // Add 1 bitstream to each item - String bitstreamContent1 = "ThisIsSomeDummyText1"; - Bitstream bitstream1 = null; - try (InputStream is = IOUtils.toInputStream(bitstreamContent1, CharEncoding.UTF_8)) { - bitstream1 = BitstreamBuilder. - createBitstream(context, publicItem1, is) - .withName("Bitstream1") - .withDescription("Description1") - .withMimeType("text/plain") - .build(); - } - - String bitstreamContent2 = "ThisIsSomeDummyText2"; - Bitstream bitstream2 = null; - try (InputStream is = IOUtils.toInputStream(bitstreamContent2, CharEncoding.UTF_8)) { - bitstream2 = BitstreamBuilder. - createBitstream(context, publicItem2, is) - .withName("Bitstream2") - .withDescription("Description2") - .withMimeType("text/plain") - .build(); - } - - context.restoreAuthSystemState(); - - String token = getAuthToken(admin.getEmail(), password); - - // Delete all bitstreams and a missing bitstream returns 404 - getClient(token).perform(delete("/api/core/bitstreams") - .contentType(TEXT_URI_LIST) - .content("http://localhost:8080/server/api/core/bitstreams/" + bitstream1.getID() - + " \n http://localhost:8080/server/api/core/bitstreams/" + bitstream2.getID() - + " \n http://localhost:8080/server/api/core/bitstreams/1c11f3f1-ba1f-4f36-908a-3f1ea9a557eb")) - .andExpect(status().isNotFound()); - - // Verify the bitstreams are still here - getClient(token).perform(get("/api/core/bitstreams/" + bitstream1.getID())) - .andExpect(status().isOk()); - - getClient(token).perform(get("/api/core/bitstreams/" + bitstream2.getID())) - .andExpect(status().isOk()); - - } - - @Test - public void deleteListDeleted() throws Exception { - //We turn off the authorization system in order to create the structure as defined below - context.turnOffAuthorisationSystem(); - - //** GIVEN ** - //1. A community-collection structure with one parent community with sub-community and one collection. - parentCommunity = CommunityBuilder.createCommunity(context) - .withName("Parent Community") - .build(); - Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) - .withName("Sub Community") - .build(); - Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); - - //2. One public items that is readable by Anonymous - Item publicItem1 = ItemBuilder.createItem(context, col1) - .withTitle("Test") - .withIssueDate("2010-10-17") - .withAuthor("Smith, Donald") - .withSubject("ExtraEntry") - .build(); - - String bitstreamContent = "ThisIsSomeDummyText"; - //Add a bitstream to an item - Bitstream bitstream = null; - try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { - bitstream = BitstreamBuilder. - createBitstream(context, publicItem1, is) - .withName("Bitstream") - .withDescription("Description") - .withMimeType("text/plain") - .build(); - } - - context.restoreAuthSystemState(); - - String token = getAuthToken(admin.getEmail(), password); - - // Delete - getClient(token).perform(delete("/api/core/bitstreams") - .contentType(TEXT_URI_LIST) - .content("http://localhost:8080/server/api/core/bitstreams/" + bitstream.getID())) - .andExpect(status().is(204)); - - // Verify 404 when trying to delete a non-existing, already deleted, bitstream - getClient(token).perform(delete("/api/core/bitstreams") - .contentType(TEXT_URI_LIST) - .content("http://localhost:8080/server/api/core/bitstreams/" + bitstream.getID())) - .andExpect(status().is(422)); - } - - @Test - public void deleteListOneDeleted() throws Exception { - //We turn off the authorization system in order to create the structure as defined below - context.turnOffAuthorisationSystem(); - - //** GIVEN ** - //1. A community-collection structure with one parent community with sub-community and one collection. - parentCommunity = CommunityBuilder.createCommunity(context) - .withName("Parent Community") - .build(); - Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) - .withName("Sub Community") - .build(); - Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); - - //2. One public items that is readable by Anonymous - Item publicItem1 = ItemBuilder.createItem(context, col1) - .withTitle("Test") - .withIssueDate("2010-10-17") - .withAuthor("Smith, Donald") - .withSubject("ExtraEntry") - .build(); - - // Add 3 bitstreams to the item - String bitstreamContent1 = "ThisIsSomeDummyText1"; - Bitstream bitstream1 = null; - try (InputStream is = IOUtils.toInputStream(bitstreamContent1, CharEncoding.UTF_8)) { - bitstream1 = BitstreamBuilder. - createBitstream(context, publicItem1, is) - .withName("Bitstream1") - .withDescription("Description1") - .withMimeType("text/plain") - .build(); - } - - String bitstreamContent2 = "ThisIsSomeDummyText2"; - Bitstream bitstream2 = null; - try (InputStream is = IOUtils.toInputStream(bitstreamContent2, CharEncoding.UTF_8)) { - bitstream2 = BitstreamBuilder. - createBitstream(context, publicItem1, is) - .withName("Bitstream2") - .withDescription("Description2") - .withMimeType("text/plain") - .build(); - } - - String bitstreamContent3 = "ThisIsSomeDummyText3"; - Bitstream bitstream3 = null; - try (InputStream is = IOUtils.toInputStream(bitstreamContent3, CharEncoding.UTF_8)) { - bitstream3 = BitstreamBuilder. - createBitstream(context, publicItem1, is) - .withName("Bitstream3") - .withDescription("Description3") - .withMimeType("text/plain") - .build(); - } - - context.restoreAuthSystemState(); - - String token = getAuthToken(admin.getEmail(), password); - - // Delete bitstream1 - getClient(token).perform(delete("/api/core/bitstreams") - .contentType(TEXT_URI_LIST) - .content("http://localhost:8080/server/api/core/bitstreams/" + bitstream1.getID())) - .andExpect(status().is(204)); - - // Verify 404 when trying to delete a non-existing, already deleted, bitstream - getClient(token).perform(delete("/api/core/bitstreams") - .contentType(TEXT_URI_LIST) - .content("http://localhost:8080/server/api/core/bitstreams/" + bitstream1.getID() - + " \n http://localhost:8080/server/api/core/bitstreams/" + bitstream2.getID() - + " \n http://localhost:8080/server/api/core/bitstreams/" + bitstream3.getID())) - .andExpect(status().is(422)); - } - @Test public void patchBitstreamMetadataAuthorized() throws Exception { runPatchMetadataTests(admin, 200); From 648b27befbe992515319a79d78d01b8327c338d4 Mon Sep 17 00:00:00 2001 From: Nona Luypaert Date: Fri, 5 May 2023 14:34:53 +0200 Subject: [PATCH 127/510] 101549: Make BrowseIndexRestRepository#findAll also return hierarchicalBrowses --- .../java/org/dspace/browse/BrowseIndex.java | 8 ++-- .../DSpaceControlledVocabularyIndex.java | 4 +- .../rest/converter/BrowseIndexConverter.java | 24 ++++++++--- .../HierarchicalBrowseConverter.java | 42 ------------------- .../repository/BrowseIndexRestRepository.java | 6 ++- 5 files changed, 32 insertions(+), 52 deletions(-) delete mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/HierarchicalBrowseConverter.java diff --git a/dspace-api/src/main/java/org/dspace/browse/BrowseIndex.java b/dspace-api/src/main/java/org/dspace/browse/BrowseIndex.java index 8d065c21ce36..6c38c8dd664b 100644 --- a/dspace-api/src/main/java/org/dspace/browse/BrowseIndex.java +++ b/dspace-api/src/main/java/org/dspace/browse/BrowseIndex.java @@ -22,11 +22,13 @@ * This class holds all the information about a specifically configured * BrowseIndex. It is responsible for parsing the configuration, understanding * about what sort options are available, and what the names of the database - * tables that hold all the information are actually called. + * tables that hold all the information are actually called. Hierarchical browse + * indexes also contain information about the vocabulary they're using, see: + * {@link org.dspace.content.authority.DSpaceControlledVocabularyIndex} * * @author Richard Jones */ -public final class BrowseIndex { +public class BrowseIndex { /** the configuration number, as specified in the config */ /** * used for single metadata browse tables for generating the table name @@ -102,7 +104,7 @@ private BrowseIndex() { * * @param baseName The base of the table name */ - private BrowseIndex(String baseName) { + protected BrowseIndex(String baseName) { try { number = -1; tableBaseName = baseName; diff --git a/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabularyIndex.java b/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabularyIndex.java index 6f350fc71ec9..bf8194dbd53b 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabularyIndex.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabularyIndex.java @@ -9,6 +9,7 @@ import java.util.Set; +import org.dspace.browse.BrowseIndex; import org.dspace.discovery.configuration.DiscoverySearchFilterFacet; /** @@ -18,7 +19,7 @@ * * @author Marie Verdonck (Atmire) on 04/05/2023 */ -public class DSpaceControlledVocabularyIndex { +public class DSpaceControlledVocabularyIndex extends BrowseIndex { protected DSpaceControlledVocabulary vocabulary; protected Set metadataFields; @@ -26,6 +27,7 @@ public class DSpaceControlledVocabularyIndex { public DSpaceControlledVocabularyIndex(DSpaceControlledVocabulary controlledVocabulary, Set metadataFields, DiscoverySearchFilterFacet facetConfig) { + super(controlledVocabulary.vocabularyName); this.vocabulary = controlledVocabulary; this.metadataFields = metadataFields; this.facetConfig = facetConfig; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/BrowseIndexConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/BrowseIndexConverter.java index 1e2899b396ab..2595968d4d02 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/BrowseIndexConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/BrowseIndexConverter.java @@ -8,6 +8,7 @@ package org.dspace.app.rest.converter; import static org.dspace.app.rest.model.BrowseIndexRest.BROWSE_TYPE_FLAT; +import static org.dspace.app.rest.model.BrowseIndexRest.BROWSE_TYPE_HIERARCHICAL; import static org.dspace.app.rest.model.BrowseIndexRest.BROWSE_TYPE_VALUE_LIST; import java.util.ArrayList; @@ -16,6 +17,7 @@ import org.dspace.app.rest.model.BrowseIndexRest; import org.dspace.app.rest.projection.Projection; import org.dspace.browse.BrowseIndex; +import org.dspace.content.authority.DSpaceControlledVocabularyIndex; import org.dspace.sort.SortException; import org.dspace.sort.SortOption; import org.springframework.stereotype.Component; @@ -33,19 +35,29 @@ public class BrowseIndexConverter implements DSpaceConverter metadataList = new ArrayList(); - if (obj.isMetadataIndex()) { + String id = obj.getName(); + if (obj instanceof DSpaceControlledVocabularyIndex) { + DSpaceControlledVocabularyIndex vocObj = (DSpaceControlledVocabularyIndex) obj; + metadataList = new ArrayList<>(vocObj.getMetadataFields()); + id = vocObj.getVocabulary().getPluginInstanceName(); + bir.setFacetType(vocObj.getFacetConfig().getIndexFieldName()); + bir.setVocabulary(vocObj.getVocabulary().getPluginInstanceName()); + bir.setBrowseType(BROWSE_TYPE_HIERARCHICAL); + } else if (obj.isMetadataIndex()) { for (String s : obj.getMetadata().split(",")) { metadataList.add(s.trim()); } + bir.setDataType(obj.getDataType()); + bir.setOrder(obj.getDefaultOrder()); bir.setBrowseType(BROWSE_TYPE_VALUE_LIST); } else { metadataList.add(obj.getSortOption().getMetadata()); + bir.setDataType(obj.getDataType()); + bir.setOrder(obj.getDefaultOrder()); bir.setBrowseType(BROWSE_TYPE_FLAT); } + bir.setId(id); bir.setMetadataList(metadataList); List sortOptionsList = new ArrayList(); @@ -56,7 +68,9 @@ public BrowseIndexRest convert(BrowseIndex obj, Projection projection) { } catch (SortException e) { throw new RuntimeException(e.getMessage(), e); } - bir.setSortOptions(sortOptionsList); + if (!bir.getBrowseType().equals(BROWSE_TYPE_HIERARCHICAL)) { + bir.setSortOptions(sortOptionsList); + } return bir; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/HierarchicalBrowseConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/HierarchicalBrowseConverter.java deleted file mode 100644 index 7b0cea9d8fb2..000000000000 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/HierarchicalBrowseConverter.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.dspace.app.rest.converter; - -import java.util.ArrayList; - -import org.dspace.app.rest.model.BrowseIndexRest; -import org.dspace.app.rest.projection.Projection; -import org.dspace.content.authority.DSpaceControlledVocabularyIndex; -import org.springframework.stereotype.Component; - -/** - * This is the converter from a {@link org.dspace.content.authority.DSpaceControlledVocabularyIndex} to a - * {@link org.dspace.app.rest.model.BrowseIndexRest#BROWSE_TYPE_HIERARCHICAL} {@link org.dspace.app.rest.model.BrowseIndexRest} - * - * @author Marie Verdonck (Atmire) on 04/05/2023 - */ -@Component -public class HierarchicalBrowseConverter implements DSpaceConverter { - - @Override - public BrowseIndexRest convert(DSpaceControlledVocabularyIndex obj, Projection projection) { - BrowseIndexRest bir = new BrowseIndexRest(); - bir.setProjection(projection); - bir.setId(obj.getVocabulary().getPluginInstanceName()); - bir.setBrowseType(BrowseIndexRest.BROWSE_TYPE_HIERARCHICAL); - bir.setFacetType(obj.getFacetConfig().getIndexFieldName()); - bir.setVocabulary(obj.getVocabulary().getPluginInstanceName()); - bir.setMetadataList(new ArrayList<>(obj.getMetadataFields())); - return bir; - } - - @Override - public Class getModelClass() { - return DSpaceControlledVocabularyIndex.class; - } -} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BrowseIndexRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BrowseIndexRestRepository.java index c87cbc6c037e..b166bffda785 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BrowseIndexRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BrowseIndexRestRepository.java @@ -7,6 +7,7 @@ */ package org.dspace.app.rest.repository; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -68,7 +69,10 @@ private BrowseIndexRest createFromMatchingBrowseIndex(String name) { @Override public Page findAll(Context context, Pageable pageable) { try { - List indexes = Arrays.asList(BrowseIndex.getBrowseIndices()); + List indexes = new ArrayList<>(Arrays.asList(BrowseIndex.getBrowseIndices())); + choiceAuthorityService.getChoiceAuthoritiesNames() + .stream().filter(name -> choiceAuthorityService.getVocabularyIndex(name) != null) + .forEach(name -> indexes.add(choiceAuthorityService.getVocabularyIndex(name))); return converter.toRestPage(indexes, pageable, indexes.size(), utils.obtainProjection()); } catch (BrowseException e) { throw new RuntimeException(e.getMessage(), e); From 999fb46e8dfafe82313d6b19441ec34075a2a4c8 Mon Sep 17 00:00:00 2001 From: Jens Vannerum Date: Fri, 5 May 2023 15:10:12 +0200 Subject: [PATCH 128/510] 94299: Add IT --- .../operation/BitstreamRemoveOperation.java | 2 +- .../app/rest/BitstreamRestRepositoryIT.java | 229 ++++++++++++++++++ 2 files changed, 230 insertions(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/BitstreamRemoveOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/BitstreamRemoveOperation.java index 5d37e04cea6c..93c495a30233 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/BitstreamRemoveOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/BitstreamRemoveOperation.java @@ -38,7 +38,7 @@ public class BitstreamRemoveOperation extends PatchOperation { @Autowired BitstreamService bitstreamService; - private static final String OPERATION_PATH_BITSTREAM_REMOVE = "/bitstreams/"; + public static final String OPERATION_PATH_BITSTREAM_REMOVE = "/bitstreams/"; @Override public Bitstream perform(Context context, Bitstream resource, Operation operation) throws SQLException { diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java index f9c1e469fcfe..3b01b4eac2ab 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java @@ -7,22 +7,29 @@ */ package org.dspace.app.rest; +import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND; +import static javax.servlet.http.HttpServletResponse.SC_OK; import static org.dspace.app.rest.matcher.MetadataMatcher.matchMetadata; import static org.dspace.app.rest.matcher.MetadataMatcher.matchMetadataDoesNotExist; +import static org.dspace.app.rest.repository.patch.operation.BitstreamRemoveOperation.OPERATION_PATH_BITSTREAM_REMOVE; import static org.dspace.core.Constants.WRITE; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; +import static org.junit.Assert.assertEquals; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import java.io.InputStream; +import java.util.ArrayList; import java.util.Comparator; import java.util.List; import java.util.UUID; +import javax.ws.rs.core.MediaType; import org.apache.commons.codec.CharEncoding; import org.apache.commons.io.IOUtils; @@ -30,6 +37,8 @@ import org.dspace.app.rest.matcher.BitstreamMatcher; import org.dspace.app.rest.matcher.BundleMatcher; import org.dspace.app.rest.matcher.HalMatcher; +import org.dspace.app.rest.model.patch.Operation; +import org.dspace.app.rest.model.patch.RemoveOperation; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.app.rest.test.MetadataPatchSuite; import org.dspace.authorize.service.ResourcePolicyService; @@ -52,10 +61,13 @@ import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; import org.dspace.eperson.service.GroupService; +import org.dspace.services.factory.DSpaceServicesFactory; import org.hamcrest.Matchers; +import org.junit.Assert; import org.junit.Ignore; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.web.servlet.MvcResult; public class BitstreamRestRepositoryIT extends AbstractControllerIntegrationTest { @@ -2279,6 +2291,223 @@ public void findByHandleAndFileNameForPublicItemWithEmbargoOnFile() throws Excep )); } + @Test + public void deleteBitstreamsInBulk() throws Exception { + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Collection collection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection") + .build(); + Item publicItem1 = ItemBuilder.createItem(context, collection) + .withTitle("Test item 1") + .build(); + Item publicItem2 = ItemBuilder.createItem(context, collection) + .withTitle("Test item 2") + .build(); + + String bitstreamContent = "This is an archived bitstream"; + Bitstream bitstream1 = null; + Bitstream bitstream2 = null; + Bitstream bitstream3 = null; + Bitstream bitstream4 = null; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + bitstream1 = BitstreamBuilder.createBitstream(context, publicItem1, is) + .withName("Bitstream 1") + .withMimeType("text/plain") + .build(); + bitstream2 = BitstreamBuilder.createBitstream(context, publicItem1, is) + .withName("Bitstream 2") + .withMimeType("text/plain") + .build(); + bitstream3 = BitstreamBuilder.createBitstream(context, publicItem2, is) + .withName("Bitstream 3") + .withMimeType("text/plain") + .build(); + bitstream4 = BitstreamBuilder.createBitstream(context, publicItem2, is) + .withName("Bitstream 4") + .withMimeType("text/plain") + .build(); + } + context.restoreAuthSystemState(); + + // Add three out of four bitstreams to the list of bitstreams to be deleted + List ops = new ArrayList<>(); + RemoveOperation removeOp1 = new RemoveOperation(OPERATION_PATH_BITSTREAM_REMOVE + bitstream1.getID()); + ops.add(removeOp1); + RemoveOperation removeOp2 = new RemoveOperation(OPERATION_PATH_BITSTREAM_REMOVE + bitstream2.getID()); + ops.add(removeOp2); + RemoveOperation removeOp3 = new RemoveOperation(OPERATION_PATH_BITSTREAM_REMOVE + bitstream3.getID()); + ops.add(removeOp3); + String patchBody = getPatchContent(ops); + String token = getAuthToken(admin.getEmail(), password); + + Assert.assertTrue(bitstreamExists(token, bitstream1, bitstream2, bitstream3, bitstream4)); + + getClient(token).perform(patch("/api/core/bitstreams") + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isNoContent()); + + // Verify that only the three bitstreams were deleted and the fourth one still exists + Assert.assertTrue(bitstreamNotFound(token, bitstream1, bitstream2, bitstream3)); + Assert.assertTrue(bitstreamExists(token, bitstream4)); + } + + @Test + public void deleteBitstreamsInBulk_invalidUUID() throws Exception { + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Collection collection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection") + .build(); + Item publicItem1 = ItemBuilder.createItem(context, collection) + .withTitle("Test item 1") + .build(); + Item publicItem2 = ItemBuilder.createItem(context, collection) + .withTitle("Test item 2") + .build(); + + String bitstreamContent = "This is an archived bitstream"; + Bitstream bitstream1 = null; + Bitstream bitstream2 = null; + Bitstream bitstream3 = null; + Bitstream bitstream4 = null; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + bitstream1 = BitstreamBuilder.createBitstream(context, publicItem1, is) + .withName("Bitstream 1") + .withMimeType("text/plain") + .build(); + bitstream2 = BitstreamBuilder.createBitstream(context, publicItem1, is) + .withName("Bitstream 2") + .withMimeType("text/plain") + .build(); + bitstream3 = BitstreamBuilder.createBitstream(context, publicItem2, is) + .withName("Bitstream 3") + .withMimeType("text/plain") + .build(); + bitstream4 = BitstreamBuilder.createBitstream(context, publicItem2, is) + .withName("Bitstream 4") + .withMimeType("text/plain") + .build(); + } + context.restoreAuthSystemState(); + + // Add three out of four bitstreams to the list of bitstreams to be deleted + // For the third bitstream, use an invalid UUID + List ops = new ArrayList<>(); + RemoveOperation removeOp1 = new RemoveOperation(OPERATION_PATH_BITSTREAM_REMOVE + bitstream1.getID()); + ops.add(removeOp1); + RemoveOperation removeOp2 = new RemoveOperation(OPERATION_PATH_BITSTREAM_REMOVE + bitstream2.getID()); + ops.add(removeOp2); + UUID randomUUID = UUID.randomUUID(); + RemoveOperation removeOp3 = new RemoveOperation(OPERATION_PATH_BITSTREAM_REMOVE + randomUUID); + ops.add(removeOp3); + String patchBody = getPatchContent(ops); + String token = getAuthToken(admin.getEmail(), password); + + Assert.assertTrue(bitstreamExists(token, bitstream1, bitstream2, bitstream3, bitstream4)); + + MvcResult result = getClient(token).perform(patch("/api/core/bitstreams") + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isUnprocessableEntity()) + .andReturn(); + + // Verify our custom error message is returned when an invalid UUID is used + assertEquals("Bitstream with uuid " + randomUUID + " could not be found in the repository", + result.getResponse().getErrorMessage()); + + // Verify that no bitstreams were deleted since the request was invalid + Assert.assertTrue(bitstreamExists(token, bitstream1, bitstream2, bitstream3, bitstream4)); + } + + @Test + public void deleteBitstreamsInBulk_invalidRequestSize() throws Exception { + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Collection collection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection") + .build(); + Item publicItem1 = ItemBuilder.createItem(context, collection) + .withTitle("Test item 1") + .build(); + Item publicItem2 = ItemBuilder.createItem(context, collection) + .withTitle("Test item 2") + .build(); + + String bitstreamContent = "This is an archived bitstream"; + Bitstream bitstream1 = null; + Bitstream bitstream2 = null; + Bitstream bitstream3 = null; + Bitstream bitstream4 = null; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + bitstream1 = BitstreamBuilder.createBitstream(context, publicItem1, is) + .withName("Bitstream 1") + .withMimeType("text/plain") + .build(); + bitstream2 = BitstreamBuilder.createBitstream(context, publicItem1, is) + .withName("Bitstream 2") + .withMimeType("text/plain") + .build(); + bitstream3 = BitstreamBuilder.createBitstream(context, publicItem2, is) + .withName("Bitstream 3") + .withMimeType("text/plain") + .build(); + bitstream4 = BitstreamBuilder.createBitstream(context, publicItem2, is) + .withName("Bitstream 4") + .withMimeType("text/plain") + .build(); + } + context.restoreAuthSystemState(); + // Add three out of four bitstreams to the list of bitstreams to be deleted + // But set the patch.operations.limit property to 2, so that the request is invalid + List ops = new ArrayList<>(); + RemoveOperation removeOp1 = new RemoveOperation(OPERATION_PATH_BITSTREAM_REMOVE + bitstream1.getID()); + ops.add(removeOp1); + RemoveOperation removeOp2 = new RemoveOperation(OPERATION_PATH_BITSTREAM_REMOVE + bitstream2.getID()); + ops.add(removeOp2); + RemoveOperation removeOp3 = new RemoveOperation(OPERATION_PATH_BITSTREAM_REMOVE + bitstream3.getID()); + ops.add(removeOp3); + String patchBody = getPatchContent(ops); + String token = getAuthToken(admin.getEmail(), password); + + Assert.assertTrue(bitstreamExists(token, bitstream1, bitstream2, bitstream3, bitstream4)); + DSpaceServicesFactory.getInstance().getConfigurationService().setProperty("patch.operations.limit", 2); + + getClient(token).perform(patch("/api/core/bitstreams") + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isBadRequest()); + + // Verify that no bitstreams were deleted since the request was invalid + Assert.assertTrue(bitstreamExists(token, bitstream1, bitstream2, bitstream3, bitstream4)); + } + + public boolean bitstreamExists(String token, Bitstream ...bitstreams) throws Exception { + for (Bitstream bitstream : bitstreams) { + if (getClient(token).perform(get("/api/core/bitstreams/" + bitstream.getID())) + .andReturn().getResponse().getStatus() != SC_OK) { + return false; + } + } + return true; + } + + public boolean bitstreamNotFound(String token, Bitstream ...bitstreams) throws Exception { + for (Bitstream bitstream : bitstreams) { + if (getClient(token).perform(get("/api/core/bitstreams/" + bitstream.getID())) + .andReturn().getResponse().getStatus() != SC_NOT_FOUND) { + return false; + } + } + return true; + } } From acb700c88774b1aea471b4bf08037a8dcfaa8be5 Mon Sep 17 00:00:00 2001 From: Nona Luypaert Date: Fri, 5 May 2023 15:55:49 +0200 Subject: [PATCH 129/510] 101549: Fix BrowseIndexMatcher and BrowsesResourceControllerIT --- .../app/rest/BrowsesResourceControllerIT.java | 26 +++++++++++--- .../app/rest/matcher/BrowseIndexMatcher.java | 34 ++++++++++++++++--- 2 files changed, 50 insertions(+), 10 deletions(-) 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 baf459408dbd..a5f4af102cd0 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 @@ -63,22 +63,23 @@ public void findAll() throws Exception { //We expect the content type to be "application/hal+json;charset=UTF-8" .andExpect(content().contentType(contentType)) - //Our default Discovery config has 4 browse indexes so we expect this to be reflected in the page + //Our default Discovery config has 5 browse indexes, so we expect this to be reflected in the page // object .andExpect(jsonPath("$.page.size", is(20))) - .andExpect(jsonPath("$.page.totalElements", is(4))) + .andExpect(jsonPath("$.page.totalElements", is(5))) .andExpect(jsonPath("$.page.totalPages", is(1))) .andExpect(jsonPath("$.page.number", is(0))) - //The array of browse index should have a size 4 - .andExpect(jsonPath("$._embedded.browses", hasSize(4))) + //The array of browse index should have a size 5 + .andExpect(jsonPath("$._embedded.browses", hasSize(5))) //Check that all (and only) the default browse indexes are present .andExpect(jsonPath("$._embedded.browses", containsInAnyOrder( BrowseIndexMatcher.dateIssuedBrowseIndex("asc"), BrowseIndexMatcher.contributorBrowseIndex("asc"), BrowseIndexMatcher.titleBrowseIndex("asc"), - BrowseIndexMatcher.subjectBrowseIndex("asc") + BrowseIndexMatcher.subjectBrowseIndex("asc"), + BrowseIndexMatcher.hierarchicalBrowseIndex("srsc") ))) ; } @@ -125,6 +126,21 @@ public void findBrowseByContributor() throws Exception { ; } + @Test + public void findBrowseByVocabulary() throws Exception { + //Use srsc as this vocabulary is included by default + //When we call the root endpoint + getClient().perform(get("/api/discover/browses/srsc")) + //The status has to be 200 OK + .andExpect(status().isOk()) + //We expect the content type to be "application/hal+json;charset=UTF-8" + .andExpect(content().contentType(contentType)) + + //Check that the JSON root matches the expected browse index + .andExpect(jsonPath("$", BrowseIndexMatcher.hierarchicalBrowseIndex("srsc"))) + ; + } + @Test public void findBrowseBySubject() throws Exception { //When we call the root endpoint diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/BrowseIndexMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/BrowseIndexMatcher.java index 82d611facf80..80f27b6bbbeb 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/BrowseIndexMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/BrowseIndexMatcher.java @@ -8,6 +8,9 @@ package org.dspace.app.rest.matcher; import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; +import static org.dspace.app.rest.model.BrowseIndexRest.BROWSE_TYPE_FLAT; +import static org.dspace.app.rest.model.BrowseIndexRest.BROWSE_TYPE_HIERARCHICAL; +import static org.dspace.app.rest.model.BrowseIndexRest.BROWSE_TYPE_VALUE_LIST; import static org.dspace.app.rest.test.AbstractControllerIntegrationTest.REST_SERVER_URL; import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.contains; @@ -16,7 +19,6 @@ import static org.hamcrest.text.IsEqualIgnoringCase.equalToIgnoringCase; import org.hamcrest.Matcher; -import org.hamcrest.Matchers; /** * Utility class to construct a Matcher for a browse index @@ -31,7 +33,8 @@ private BrowseIndexMatcher() { } public static Matcher subjectBrowseIndex(final String order) { return allOf( hasJsonPath("$.metadata", contains("dc.subject.*")), - hasJsonPath("$.metadataBrowse", Matchers.is(true)), + hasJsonPath("$.browseType", equalToIgnoringCase(BROWSE_TYPE_VALUE_LIST)), + hasJsonPath("$.type", equalToIgnoringCase("browse")), hasJsonPath("$.dataType", equalToIgnoringCase("text")), hasJsonPath("$.order", equalToIgnoringCase(order)), hasJsonPath("$.sortOptions[*].name", containsInAnyOrder("title", "dateissued", "dateaccessioned")), @@ -44,7 +47,8 @@ public static Matcher subjectBrowseIndex(final String order) { public static Matcher titleBrowseIndex(final String order) { return allOf( hasJsonPath("$.metadata", contains("dc.title")), - hasJsonPath("$.metadataBrowse", Matchers.is(false)), + hasJsonPath("$.browseType", equalToIgnoringCase(BROWSE_TYPE_FLAT)), + hasJsonPath("$.type", equalToIgnoringCase("browse")), hasJsonPath("$.dataType", equalToIgnoringCase("title")), hasJsonPath("$.order", equalToIgnoringCase(order)), hasJsonPath("$.sortOptions[*].name", containsInAnyOrder("title", "dateissued", "dateaccessioned")), @@ -56,7 +60,8 @@ public static Matcher titleBrowseIndex(final String order) { public static Matcher contributorBrowseIndex(final String order) { return allOf( hasJsonPath("$.metadata", contains("dc.contributor.*", "dc.creator")), - hasJsonPath("$.metadataBrowse", Matchers.is(true)), + hasJsonPath("$.browseType", equalToIgnoringCase(BROWSE_TYPE_VALUE_LIST)), + hasJsonPath("$.type", equalToIgnoringCase("browse")), hasJsonPath("$.dataType", equalToIgnoringCase("text")), hasJsonPath("$.order", equalToIgnoringCase(order)), hasJsonPath("$.sortOptions[*].name", containsInAnyOrder("title", "dateissued", "dateaccessioned")), @@ -69,7 +74,8 @@ public static Matcher contributorBrowseIndex(final String order) public static Matcher dateIssuedBrowseIndex(final String order) { return allOf( hasJsonPath("$.metadata", contains("dc.date.issued")), - hasJsonPath("$.metadataBrowse", Matchers.is(false)), + hasJsonPath("$.browseType", equalToIgnoringCase(BROWSE_TYPE_FLAT)), + hasJsonPath("$.type", equalToIgnoringCase("browse")), hasJsonPath("$.dataType", equalToIgnoringCase("date")), hasJsonPath("$.order", equalToIgnoringCase(order)), hasJsonPath("$.sortOptions[*].name", containsInAnyOrder("title", "dateissued", "dateaccessioned")), @@ -77,4 +83,22 @@ public static Matcher dateIssuedBrowseIndex(final String order) hasJsonPath("$._links.items.href", is(REST_SERVER_URL + "discover/browses/dateissued/items")) ); } + + public static Matcher hierarchicalBrowseIndex(final String vocabulary) { + return allOf( + hasJsonPath("$.metadata", contains("dc.subject")), + hasJsonPath("$.browseType", equalToIgnoringCase(BROWSE_TYPE_HIERARCHICAL)), + hasJsonPath("$.type", equalToIgnoringCase("browse")), + hasJsonPath("$.facetType", equalToIgnoringCase("subject")), + hasJsonPath("$.vocabulary", equalToIgnoringCase(vocabulary)), + hasJsonPath("$._links.vocabulary.href", + is(REST_SERVER_URL + String.format("submission/vocabularies/%s/", vocabulary))), + hasJsonPath("$._links.items.href", + is(REST_SERVER_URL + String.format("discover/browses/%s/items", vocabulary))), + hasJsonPath("$._links.entries.href", + is(REST_SERVER_URL + String.format("discover/browses/%s/entries", vocabulary))), + hasJsonPath("$._links.self.href", + is(REST_SERVER_URL + String.format("discover/browses/%s", vocabulary))) + ); + } } From ab240d7f0ec07d9454ae925b2a03154c5cb2b80a Mon Sep 17 00:00:00 2001 From: Nona Luypaert Date: Fri, 5 May 2023 17:47:24 +0200 Subject: [PATCH 130/510] 101549: Fix BrowsesResourceControllerIT --- .../org/dspace/app/rest/BrowsesResourceControllerIT.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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 301ffeab4177..d1791ab872bb 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 @@ -8,6 +8,7 @@ package org.dspace.app.rest; import static org.dspace.app.rest.matcher.MetadataMatcher.matchMetadata; +import static org.dspace.app.rest.model.BrowseIndexRest.BROWSE_TYPE_VALUE_LIST; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.containsString; @@ -2158,7 +2159,7 @@ public void findOneLinked() throws Exception { // The browse definition ID should be "author" .andExpect(jsonPath("$.id", is("author"))) // It should be configured as a metadata browse - .andExpect(jsonPath("$.metadataBrowse", is(true))) + .andExpect(jsonPath("$.browseType", is(BROWSE_TYPE_VALUE_LIST))) ; } @@ -2175,7 +2176,7 @@ public void findOneLinkedPassingTwoFields() throws Exception { // The browse definition ID should be "author" .andExpect(jsonPath("$.id", is("author"))) // It should be configured as a metadata browse - .andExpect(jsonPath("$.metadataBrowse", is(true))); + .andExpect(jsonPath("$.browseType", is(BROWSE_TYPE_VALUE_LIST))); } @Test From 5088447111dd10f8627fe61d5602e6e331a93ff1 Mon Sep 17 00:00:00 2001 From: Michael W Spalti Date: Sat, 6 May 2023 11:30:51 -0700 Subject: [PATCH 131/510] Updated solr query params. --- .../dspace/app/iiif/service/WordHighlightSolrSearch.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/dspace-iiif/src/main/java/org/dspace/app/iiif/service/WordHighlightSolrSearch.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/WordHighlightSolrSearch.java index da50f335825a..9e6022548dbe 100644 --- a/dspace-iiif/src/main/java/org/dspace/app/iiif/service/WordHighlightSolrSearch.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/WordHighlightSolrSearch.java @@ -118,7 +118,8 @@ private String adjustQuery(String query) { } /** - * Constructs a solr search URL. + * Constructs a solr search URL. Compatible with solr-ocrhighlighting-0.7.2. + * https://github.com/dbmdz/solr-ocrhighlighting/releases/tag/0.7.2 * * @param query the search terms * @param manifestId the id of the manifest in which to search @@ -132,8 +133,9 @@ private SolrQuery getSolrQuery(String query, String manifestId) { solrQuery.set("hl.ocr.fl", "ocr_text"); solrQuery.set("hl.ocr.contextBlock", "line"); solrQuery.set("hl.ocr.contextSize", "2"); - solrQuery.set("hl.snippets", "10"); - solrQuery.set("hl.ocr.trackPages", "off"); + solrQuery.set("hl.snippets", "8192"); + solrQuery.set("hl.ocr.maxPassages", "8192"); + solrQuery.set("hl.ocr.trackPages", "on"); solrQuery.set("hl.ocr.limitBlock","page"); solrQuery.set("hl.ocr.absoluteHighlights", "true"); From 7971887b9a8603aa0039f8e5f3595520c0e65c3a Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Fri, 21 Apr 2023 06:54:13 +0200 Subject: [PATCH 132/510] DURACOM-136 allow script execution by user other than admins --- .../ProcessCleanerConfiguration.java | 17 -- .../MetadataDeletionScriptConfiguration.java | 17 -- .../MetadataExportScriptConfiguration.java | 17 -- ...tadataExportSearchScriptConfiguration.java | 6 - .../MetadataImportScriptConfiguration.java | 16 -- .../harvest/HarvestScriptConfiguration.java | 14 - .../ItemExportScriptConfiguration.java | 17 -- .../ItemImportScriptConfiguration.java | 16 -- .../MediaFilterScriptConfiguration.java | 19 -- ...rDatabaseResyncCliScriptConfiguration.java | 6 - .../authorize/AuthorizeServiceImpl.java | 14 + .../authorize/service/AuthorizeService.java | 9 + .../org/dspace/content/dao/ProcessDAO.java | 23 ++ .../content/dao/impl/ProcessDAOImpl.java | 28 ++ .../curate/CurationScriptConfiguration.java | 43 ++- .../IndexDiscoveryScriptConfiguration.java | 17 -- .../OrcidBulkPushScriptConfiguration.java | 17 -- .../dspace/scripts/ProcessServiceImpl.java | 10 + .../org/dspace/scripts/ScriptServiceImpl.java | 2 +- .../configuration/ScriptConfiguration.java | 22 +- .../scripts/service/ProcessService.java | 22 ++ ...iledOpenUrlTrackerScriptConfiguration.java | 17 -- ...nFormsMigrationCliScriptConfiguration.java | 17 -- ...sionFormsMigrationScriptConfiguration.java | 36 ++- ...riptionEmailNotificationConfiguration.java | 16 -- .../builder/AbstractDSpaceObjectBuilder.java | 4 +- .../java/org/dspace/builder/ItemBuilder.java | 4 +- .../org/dspace/builder/ProcessBuilder.java | 3 + ...MockDSpaceRunnableScriptConfiguration.java | 17 -- .../app/rest/ScriptProcessesController.java | 15 +- .../repository/ProcessRestRepository.java | 16 ++ .../rest/repository/ScriptRestRepository.java | 30 +- .../app/rest/ProcessRestRepositoryIT.java | 26 ++ .../app/rest/ScriptRestRepositoryIT.java | 136 ++++++++- ...TypeConversionTestScriptConfiguration.java | 5 - .../org/dspace/curate/CurationScriptIT.java | 267 ++++++++++++++++++ ...MockDSpaceRunnableScriptConfiguration.java | 17 -- 37 files changed, 659 insertions(+), 319 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/administer/ProcessCleanerConfiguration.java b/dspace-api/src/main/java/org/dspace/administer/ProcessCleanerConfiguration.java index 8d189038d9d1..91dcfb5dfec5 100644 --- a/dspace-api/src/main/java/org/dspace/administer/ProcessCleanerConfiguration.java +++ b/dspace-api/src/main/java/org/dspace/administer/ProcessCleanerConfiguration.java @@ -7,33 +7,16 @@ */ package org.dspace.administer; -import java.sql.SQLException; - import org.apache.commons.cli.Options; -import org.dspace.authorize.service.AuthorizeService; -import org.dspace.core.Context; import org.dspace.scripts.configuration.ScriptConfiguration; -import org.springframework.beans.factory.annotation.Autowired; /** * The {@link ScriptConfiguration} for the {@link ProcessCleaner} script. */ public class ProcessCleanerConfiguration extends ScriptConfiguration { - @Autowired - private AuthorizeService authorizeService; - private Class dspaceRunnableClass; - @Override - public boolean isAllowedToExecute(Context context) { - try { - return authorizeService.isAdmin(context); - } catch (SQLException e) { - throw new RuntimeException("SQLException occurred when checking if the current user is an admin", e); - } - } - @Override public Options getOptions() { if (options == null) { diff --git a/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataDeletionScriptConfiguration.java b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataDeletionScriptConfiguration.java index 9ccd53944a24..fb228e7041b8 100644 --- a/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataDeletionScriptConfiguration.java +++ b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataDeletionScriptConfiguration.java @@ -7,33 +7,16 @@ */ package org.dspace.app.bulkedit; -import java.sql.SQLException; - import org.apache.commons.cli.Options; -import org.dspace.authorize.service.AuthorizeService; -import org.dspace.core.Context; import org.dspace.scripts.configuration.ScriptConfiguration; -import org.springframework.beans.factory.annotation.Autowired; /** * The {@link ScriptConfiguration} for the {@link MetadataDeletion} script. */ public class MetadataDeletionScriptConfiguration extends ScriptConfiguration { - @Autowired - private AuthorizeService authorizeService; - private Class dspaceRunnableClass; - @Override - public boolean isAllowedToExecute(Context context) { - try { - return authorizeService.isAdmin(context); - } catch (SQLException e) { - throw new RuntimeException("SQLException occurred when checking if the current user is an admin", e); - } - } - @Override public Options getOptions() { if (options == null) { diff --git a/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataExportScriptConfiguration.java b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataExportScriptConfiguration.java index 31556afc8d3d..aa76c09c0a5b 100644 --- a/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataExportScriptConfiguration.java +++ b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataExportScriptConfiguration.java @@ -7,22 +7,14 @@ */ package org.dspace.app.bulkedit; -import java.sql.SQLException; - import org.apache.commons.cli.Options; -import org.dspace.authorize.service.AuthorizeService; -import org.dspace.core.Context; import org.dspace.scripts.configuration.ScriptConfiguration; -import org.springframework.beans.factory.annotation.Autowired; /** * The {@link ScriptConfiguration} for the {@link MetadataExport} script */ public class MetadataExportScriptConfiguration extends ScriptConfiguration { - @Autowired - private AuthorizeService authorizeService; - private Class dspaceRunnableClass; @Override @@ -39,15 +31,6 @@ public void setDspaceRunnableClass(Class dspaceRunnableClass) { this.dspaceRunnableClass = dspaceRunnableClass; } - @Override - public boolean isAllowedToExecute(Context context) { - try { - return authorizeService.isAdmin(context); - } catch (SQLException e) { - throw new RuntimeException("SQLException occurred when checking if the current user is an admin", e); - } - } - @Override public Options getOptions() { if (options == null) { diff --git a/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataExportSearchScriptConfiguration.java b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataExportSearchScriptConfiguration.java index 4e350562bc26..4f2a225d3ac6 100644 --- a/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataExportSearchScriptConfiguration.java +++ b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataExportSearchScriptConfiguration.java @@ -9,7 +9,6 @@ package org.dspace.app.bulkedit; import org.apache.commons.cli.Options; -import org.dspace.core.Context; import org.dspace.scripts.configuration.ScriptConfiguration; /** @@ -29,11 +28,6 @@ public void setDspaceRunnableClass(Class dspaceRunnableClass) { this.dspaceRunnableclass = dspaceRunnableClass; } - @Override - public boolean isAllowedToExecute(Context context) { - return true; - } - @Override public Options getOptions() { if (options == null) { diff --git a/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImportScriptConfiguration.java b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImportScriptConfiguration.java index 65994040badc..ce2f7fb68af1 100644 --- a/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImportScriptConfiguration.java +++ b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImportScriptConfiguration.java @@ -8,22 +8,15 @@ package org.dspace.app.bulkedit; import java.io.InputStream; -import java.sql.SQLException; import org.apache.commons.cli.Options; -import org.dspace.authorize.service.AuthorizeService; -import org.dspace.core.Context; import org.dspace.scripts.configuration.ScriptConfiguration; -import org.springframework.beans.factory.annotation.Autowired; /** * The {@link ScriptConfiguration} for the {@link MetadataImport} script */ public class MetadataImportScriptConfiguration extends ScriptConfiguration { - @Autowired - private AuthorizeService authorizeService; - private Class dspaceRunnableClass; @Override @@ -40,15 +33,6 @@ public void setDspaceRunnableClass(Class dspaceRunnableClass) { this.dspaceRunnableClass = dspaceRunnableClass; } - @Override - public boolean isAllowedToExecute(Context context) { - try { - return authorizeService.isAdmin(context); - } catch (SQLException e) { - throw new RuntimeException("SQLException occurred when checking if the current user is an admin", e); - } - } - @Override public Options getOptions() { if (options == null) { diff --git a/dspace-api/src/main/java/org/dspace/app/harvest/HarvestScriptConfiguration.java b/dspace-api/src/main/java/org/dspace/app/harvest/HarvestScriptConfiguration.java index 982973e47c50..ff83c3ecb225 100644 --- a/dspace-api/src/main/java/org/dspace/app/harvest/HarvestScriptConfiguration.java +++ b/dspace-api/src/main/java/org/dspace/app/harvest/HarvestScriptConfiguration.java @@ -7,18 +7,11 @@ */ package org.dspace.app.harvest; -import java.sql.SQLException; - import org.apache.commons.cli.Options; -import org.dspace.authorize.service.AuthorizeService; -import org.dspace.core.Context; import org.dspace.scripts.configuration.ScriptConfiguration; -import org.springframework.beans.factory.annotation.Autowired; public class HarvestScriptConfiguration extends ScriptConfiguration { - @Autowired - private AuthorizeService authorizeService; private Class dspaceRunnableClass; @@ -32,13 +25,6 @@ public void setDspaceRunnableClass(Class dspaceRunnableClass) { this.dspaceRunnableClass = dspaceRunnableClass; } - public boolean isAllowedToExecute(final Context context) { - try { - return authorizeService.isAdmin(context); - } catch (SQLException e) { - throw new RuntimeException("SQLException occurred when checking if the current user is an admin", e); - } - } public Options getOptions() { Options options = new Options(); diff --git a/dspace-api/src/main/java/org/dspace/app/itemexport/ItemExportScriptConfiguration.java b/dspace-api/src/main/java/org/dspace/app/itemexport/ItemExportScriptConfiguration.java index cf70120d27d3..b37df5f5ea59 100644 --- a/dspace-api/src/main/java/org/dspace/app/itemexport/ItemExportScriptConfiguration.java +++ b/dspace-api/src/main/java/org/dspace/app/itemexport/ItemExportScriptConfiguration.java @@ -7,14 +7,9 @@ */ package org.dspace.app.itemexport; -import java.sql.SQLException; - import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; -import org.dspace.authorize.service.AuthorizeService; -import org.dspace.core.Context; import org.dspace.scripts.configuration.ScriptConfiguration; -import org.springframework.beans.factory.annotation.Autowired; /** * The {@link ScriptConfiguration} for the {@link ItemExport} script @@ -23,9 +18,6 @@ */ public class ItemExportScriptConfiguration extends ScriptConfiguration { - @Autowired - private AuthorizeService authorizeService; - private Class dspaceRunnableClass; @Override @@ -38,15 +30,6 @@ public void setDspaceRunnableClass(Class dspaceRunnableClass) { this.dspaceRunnableClass = dspaceRunnableClass; } - @Override - public boolean isAllowedToExecute(final Context context) { - try { - return authorizeService.isAdmin(context); - } catch (SQLException e) { - throw new RuntimeException("SQLException occurred when checking if the current user is an admin", e); - } - } - @Override public Options getOptions() { Options options = new Options(); diff --git a/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportScriptConfiguration.java b/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportScriptConfiguration.java index a3149040c49b..fd895e2f44d6 100644 --- a/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportScriptConfiguration.java +++ b/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportScriptConfiguration.java @@ -8,14 +8,10 @@ package org.dspace.app.itemimport; import java.io.InputStream; -import java.sql.SQLException; import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; -import org.dspace.authorize.service.AuthorizeService; -import org.dspace.core.Context; import org.dspace.scripts.configuration.ScriptConfiguration; -import org.springframework.beans.factory.annotation.Autowired; /** * The {@link ScriptConfiguration} for the {@link ItemImport} script @@ -24,9 +20,6 @@ */ public class ItemImportScriptConfiguration extends ScriptConfiguration { - @Autowired - private AuthorizeService authorizeService; - private Class dspaceRunnableClass; @Override @@ -39,15 +32,6 @@ public void setDspaceRunnableClass(Class dspaceRunnableClass) { this.dspaceRunnableClass = dspaceRunnableClass; } - @Override - public boolean isAllowedToExecute(final Context context) { - try { - return authorizeService.isAdmin(context); - } catch (SQLException e) { - throw new RuntimeException("SQLException occurred when checking if the current user is an admin", e); - } - } - @Override public Options getOptions() { Options options = new Options(); diff --git a/dspace-api/src/main/java/org/dspace/app/mediafilter/MediaFilterScriptConfiguration.java b/dspace-api/src/main/java/org/dspace/app/mediafilter/MediaFilterScriptConfiguration.java index 26347c56ee96..867e684db86b 100644 --- a/dspace-api/src/main/java/org/dspace/app/mediafilter/MediaFilterScriptConfiguration.java +++ b/dspace-api/src/main/java/org/dspace/app/mediafilter/MediaFilterScriptConfiguration.java @@ -7,25 +7,16 @@ */ package org.dspace.app.mediafilter; -import java.sql.SQLException; - import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; -import org.dspace.authorize.service.AuthorizeService; -import org.dspace.core.Context; import org.dspace.scripts.configuration.ScriptConfiguration; -import org.springframework.beans.factory.annotation.Autowired; public class MediaFilterScriptConfiguration extends ScriptConfiguration { - @Autowired - private AuthorizeService authorizeService; - private Class dspaceRunnableClass; private static final String MEDIA_FILTER_PLUGINS_KEY = "filter.plugins"; - @Override public Class getDspaceRunnableClass() { return dspaceRunnableClass; @@ -36,16 +27,6 @@ public void setDspaceRunnableClass(Class dspaceRunnableClass) { this.dspaceRunnableClass = dspaceRunnableClass; } - - @Override - public boolean isAllowedToExecute(final Context context) { - try { - return authorizeService.isAdmin(context); - } catch (SQLException e) { - throw new RuntimeException("SQLException occurred when checking if the current user is an admin", e); - } - } - @Override public Options getOptions() { Options options = new Options(); diff --git a/dspace-api/src/main/java/org/dspace/app/solrdatabaseresync/SolrDatabaseResyncCliScriptConfiguration.java b/dspace-api/src/main/java/org/dspace/app/solrdatabaseresync/SolrDatabaseResyncCliScriptConfiguration.java index b238ccf061f3..067c76cce8b3 100644 --- a/dspace-api/src/main/java/org/dspace/app/solrdatabaseresync/SolrDatabaseResyncCliScriptConfiguration.java +++ b/dspace-api/src/main/java/org/dspace/app/solrdatabaseresync/SolrDatabaseResyncCliScriptConfiguration.java @@ -8,7 +8,6 @@ package org.dspace.app.solrdatabaseresync; import org.apache.commons.cli.Options; -import org.dspace.core.Context; import org.dspace.scripts.configuration.ScriptConfiguration; /** @@ -27,11 +26,6 @@ public void setDspaceRunnableClass(Class dspaceRunnableCl this.dspaceRunnableClass = dspaceRunnableClass; } - @Override - public boolean isAllowedToExecute(Context context) { - return true; - } - @Override public Options getOptions() { if (options == null) { 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 34543c078ae2..bfd933f48265 100644 --- a/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java @@ -43,6 +43,7 @@ import org.dspace.discovery.SearchServiceException; import org.dspace.discovery.indexobject.IndexableCollection; import org.dspace.discovery.indexobject.IndexableCommunity; +import org.dspace.discovery.indexobject.IndexableItem; import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; import org.dspace.eperson.service.GroupService; @@ -809,6 +810,19 @@ public boolean isCollectionAdmin(Context context) throws SQLException { return performCheck(context, "search.resourcetype:" + IndexableCollection.TYPE); } + /** + * Checks that the context's current user is an item admin in the site by querying the solr database. + * + * @param context context with the current user + * @return true if the current user is an item admin in the site + * false when this is not the case, or an exception occurred + * @throws java.sql.SQLException passed through. + */ + @Override + public boolean isItemAdmin(Context context) throws SQLException { + return performCheck(context, "search.resourcetype:" + IndexableItem.TYPE); + } + /** * Checks that the context's current user is a community or collection admin in the site. * diff --git a/dspace-api/src/main/java/org/dspace/authorize/service/AuthorizeService.java b/dspace-api/src/main/java/org/dspace/authorize/service/AuthorizeService.java index 36679f94c6a4..86ff23616867 100644 --- a/dspace-api/src/main/java/org/dspace/authorize/service/AuthorizeService.java +++ b/dspace-api/src/main/java/org/dspace/authorize/service/AuthorizeService.java @@ -532,6 +532,15 @@ void switchPoliciesAction(Context context, DSpaceObject dso, int fromAction, int */ boolean isCollectionAdmin(Context context) throws SQLException; + /** + * Checks that the context's current user is an item admin in the site by querying the solr database. + * + * @param context context with the current user + * @return true if the current user is an item admin in the site + * false when this is not the case, or an exception occurred + */ + boolean isItemAdmin(Context context) throws SQLException; + /** * Checks that the context's current user is a community or collection admin in the site. * diff --git a/dspace-api/src/main/java/org/dspace/content/dao/ProcessDAO.java b/dspace-api/src/main/java/org/dspace/content/dao/ProcessDAO.java index 69bac319c6d6..95ec40c7a542 100644 --- a/dspace-api/src/main/java/org/dspace/content/dao/ProcessDAO.java +++ b/dspace-api/src/main/java/org/dspace/content/dao/ProcessDAO.java @@ -14,6 +14,7 @@ import org.dspace.content.ProcessStatus; import org.dspace.core.Context; import org.dspace.core.GenericDAO; +import org.dspace.eperson.EPerson; import org.dspace.scripts.Process; import org.dspace.scripts.ProcessQueryParameterContainer; @@ -97,4 +98,26 @@ int countTotalWithParameters(Context context, ProcessQueryParameterContainer pro List findByStatusAndCreationTimeOlderThan(Context context, List statuses, Date date) throws SQLException; + /** + * Returns a list of all Process objects in the database by the given user. + * + * @param context The relevant DSpace context + * @param user The user to search for + * @param limit The limit for the amount of Processes returned + * @param offset The offset for the Processes to be returned + * @return The list of all Process objects in the Database + * @throws SQLException If something goes wrong + */ + List findByUser(Context context, EPerson user, int limit, int offset) throws SQLException; + + /** + * Count all the processes which is related to the given user. + * + * @param context The relevant DSpace context + * @param user The user to search for + * @return The number of results matching the query + * @throws SQLException If something goes wrong + */ + int countByUser(Context context, EPerson user) throws SQLException; + } diff --git a/dspace-api/src/main/java/org/dspace/content/dao/impl/ProcessDAOImpl.java b/dspace-api/src/main/java/org/dspace/content/dao/impl/ProcessDAOImpl.java index 23ce6ce381b5..d719b5006c14 100644 --- a/dspace-api/src/main/java/org/dspace/content/dao/impl/ProcessDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/dao/impl/ProcessDAOImpl.java @@ -24,6 +24,7 @@ import org.dspace.content.dao.ProcessDAO; import org.dspace.core.AbstractHibernateDAO; import org.dspace.core.Context; +import org.dspace.eperson.EPerson; import org.dspace.scripts.Process; import org.dspace.scripts.ProcessQueryParameterContainer; import org.dspace.scripts.Process_; @@ -168,6 +169,33 @@ public List findByStatusAndCreationTimeOlderThan(Context context, List< return list(context, criteriaQuery, false, Process.class, -1, -1); } + @Override + public List findByUser(Context context, EPerson user, int limit, int offset) throws SQLException { + CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); + CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, Process.class); + + Root processRoot = criteriaQuery.from(Process.class); + criteriaQuery.select(processRoot); + criteriaQuery.where(criteriaBuilder.equal(processRoot.get(Process_.E_PERSON), user)); + + List orderList = new LinkedList<>(); + orderList.add(criteriaBuilder.desc(processRoot.get(Process_.PROCESS_ID))); + criteriaQuery.orderBy(orderList); + + return list(context, criteriaQuery, false, Process.class, limit, offset); + } + + @Override + public int countByUser(Context context, EPerson user) throws SQLException { + CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); + CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, Process.class); + + Root processRoot = criteriaQuery.from(Process.class); + criteriaQuery.select(processRoot); + criteriaQuery.where(criteriaBuilder.equal(processRoot.get(Process_.E_PERSON), user)); + return count(context, criteriaQuery, criteriaBuilder, processRoot); + } + } diff --git a/dspace-api/src/main/java/org/dspace/curate/CurationScriptConfiguration.java b/dspace-api/src/main/java/org/dspace/curate/CurationScriptConfiguration.java index fefb4eb768ea..2587e6b0251e 100644 --- a/dspace-api/src/main/java/org/dspace/curate/CurationScriptConfiguration.java +++ b/dspace-api/src/main/java/org/dspace/curate/CurationScriptConfiguration.java @@ -8,12 +8,15 @@ package org.dspace.curate; import java.sql.SQLException; +import java.util.List; import org.apache.commons.cli.Options; -import org.dspace.authorize.service.AuthorizeService; +import org.dspace.content.DSpaceObject; import org.dspace.core.Context; +import org.dspace.handle.factory.HandleServiceFactory; +import org.dspace.handle.service.HandleService; +import org.dspace.scripts.DSpaceCommandLineParameter; import org.dspace.scripts.configuration.ScriptConfiguration; -import org.springframework.beans.factory.annotation.Autowired; /** * The {@link ScriptConfiguration} for the {@link Curation} script @@ -22,9 +25,6 @@ */ public class CurationScriptConfiguration extends ScriptConfiguration { - @Autowired - private AuthorizeService authorizeService; - private Class dspaceRunnableClass; @Override @@ -38,16 +38,37 @@ public void setDspaceRunnableClass(Class dspaceRunnableClass) { } /** - * Only admin can run Curation script via the scripts and processes endpoints. - * @param context The relevant DSpace context - * @return True if currentUser is admin, otherwise false + * Only repository admins or admins of the target object can run Curation script via the scripts + * and processes endpoints. + * + * @param context The relevant DSpace context + * @param commandLineParameters the parameters that will be used to start the process if known, + * null otherwise + * @return true if the currentUser is allowed to run the script with the specified parameters or + * at least in some case if the parameters are not yet known */ @Override - public boolean isAllowedToExecute(Context context) { + public boolean isAllowedToExecute(Context context, List commandLineParameters) { try { - return authorizeService.isAdmin(context); + if (commandLineParameters == null) { + return authorizeService.isAdmin(context) || authorizeService.isComColAdmin(context) + || authorizeService.isItemAdmin(context); + } else if (commandLineParameters.stream() + .map(DSpaceCommandLineParameter::getName) + .noneMatch("-i"::equals)) { + return authorizeService.isAdmin(context); + } else { + String dspaceObjectID = commandLineParameters.stream() + .filter(parameter -> "-i".equals(parameter.getName())) + .map(DSpaceCommandLineParameter::getValue) + .findFirst() + .get(); + HandleService handleService = HandleServiceFactory.getInstance().getHandleService(); + DSpaceObject dso = handleService.resolveToObject(context, dspaceObjectID); + return authorizeService.isAdmin(context, dso); + } } catch (SQLException e) { - throw new RuntimeException("SQLException occurred when checking if the current user is an admin", e); + throw new RuntimeException(e); } } diff --git a/dspace-api/src/main/java/org/dspace/discovery/IndexDiscoveryScriptConfiguration.java b/dspace-api/src/main/java/org/dspace/discovery/IndexDiscoveryScriptConfiguration.java index 8bf3cf2aba62..8707b733a637 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/IndexDiscoveryScriptConfiguration.java +++ b/dspace-api/src/main/java/org/dspace/discovery/IndexDiscoveryScriptConfiguration.java @@ -7,22 +7,14 @@ */ package org.dspace.discovery; -import java.sql.SQLException; - import org.apache.commons.cli.Options; -import org.dspace.authorize.service.AuthorizeService; -import org.dspace.core.Context; import org.dspace.scripts.configuration.ScriptConfiguration; -import org.springframework.beans.factory.annotation.Autowired; /** * The {@link ScriptConfiguration} for the {@link IndexClient} script */ public class IndexDiscoveryScriptConfiguration extends ScriptConfiguration { - @Autowired - private AuthorizeService authorizeService; - private Class dspaceRunnableClass; @Override @@ -30,15 +22,6 @@ public Class getDspaceRunnableClass() { return dspaceRunnableClass; } - @Override - public boolean isAllowedToExecute(Context context) { - try { - return authorizeService.isAdmin(context); - } catch (SQLException e) { - throw new RuntimeException("SQLException occurred when checking if the current user is an admin", e); - } - } - @Override public Options getOptions() { if (options == null) { diff --git a/dspace-api/src/main/java/org/dspace/orcid/script/OrcidBulkPushScriptConfiguration.java b/dspace-api/src/main/java/org/dspace/orcid/script/OrcidBulkPushScriptConfiguration.java index 1a657343c017..88a1033eca5f 100644 --- a/dspace-api/src/main/java/org/dspace/orcid/script/OrcidBulkPushScriptConfiguration.java +++ b/dspace-api/src/main/java/org/dspace/orcid/script/OrcidBulkPushScriptConfiguration.java @@ -7,13 +7,8 @@ */ package org.dspace.orcid.script; -import java.sql.SQLException; - import org.apache.commons.cli.Options; -import org.dspace.authorize.service.AuthorizeService; -import org.dspace.core.Context; import org.dspace.scripts.configuration.ScriptConfiguration; -import org.springframework.beans.factory.annotation.Autowired; /** * Script configuration for {@link OrcidBulkPush}. @@ -24,20 +19,8 @@ */ public class OrcidBulkPushScriptConfiguration extends ScriptConfiguration { - @Autowired - private AuthorizeService authorizeService; - private Class dspaceRunnableClass; - @Override - public boolean isAllowedToExecute(Context context) { - try { - return authorizeService.isAdmin(context); - } catch (SQLException e) { - throw new RuntimeException("SQLException occurred when checking if the current user is an admin", e); - } - } - @Override public Class getDspaceRunnableClass() { return dspaceRunnableClass; diff --git a/dspace-api/src/main/java/org/dspace/scripts/ProcessServiceImpl.java b/dspace-api/src/main/java/org/dspace/scripts/ProcessServiceImpl.java index 33fea75add92..2e14aeaa36c0 100644 --- a/dspace-api/src/main/java/org/dspace/scripts/ProcessServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/scripts/ProcessServiceImpl.java @@ -129,6 +129,11 @@ public List findAllSortByStartTime(Context context) throws SQLException return processes; } + @Override + public List findByUser(Context context, EPerson eperson, int limit, int offset) throws SQLException { + return processDAO.findByUser(context, eperson, limit, offset); + } + @Override public void start(Context context, Process process) throws SQLException { process.setProcessStatus(ProcessStatus.RUNNING); @@ -311,6 +316,11 @@ public List findByStatusAndCreationTimeOlderThan(Context context, List< return this.processDAO.findByStatusAndCreationTimeOlderThan(context, statuses, date); } + @Override + public int countByUser(Context context, EPerson user) throws SQLException { + return processDAO.countByUser(context, user); + } + private String formatLogLine(int processId, String scriptName, String output, ProcessLogLevel processLogLevel) { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); StringBuilder sb = new StringBuilder(); diff --git a/dspace-api/src/main/java/org/dspace/scripts/ScriptServiceImpl.java b/dspace-api/src/main/java/org/dspace/scripts/ScriptServiceImpl.java index c8a7812a5159..abb700cb10c9 100644 --- a/dspace-api/src/main/java/org/dspace/scripts/ScriptServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/scripts/ScriptServiceImpl.java @@ -37,7 +37,7 @@ public ScriptConfiguration getScriptConfiguration(String name) { @Override public List getScriptConfigurations(Context context) { return serviceManager.getServicesByType(ScriptConfiguration.class).stream().filter( - scriptConfiguration -> scriptConfiguration.isAllowedToExecute(context)) + scriptConfiguration -> scriptConfiguration.isAllowedToExecute(context, null)) .sorted(Comparator.comparing(ScriptConfiguration::getName)) .collect(Collectors.toList()); } diff --git a/dspace-api/src/main/java/org/dspace/scripts/configuration/ScriptConfiguration.java b/dspace-api/src/main/java/org/dspace/scripts/configuration/ScriptConfiguration.java index 4b15c22f444a..e22063eb4954 100644 --- a/dspace-api/src/main/java/org/dspace/scripts/configuration/ScriptConfiguration.java +++ b/dspace-api/src/main/java/org/dspace/scripts/configuration/ScriptConfiguration.java @@ -7,17 +7,28 @@ */ package org.dspace.scripts.configuration; +import java.sql.SQLException; +import java.util.List; + import org.apache.commons.cli.Options; +import org.dspace.authorize.service.AuthorizeService; import org.dspace.core.Context; +import org.dspace.scripts.DSpaceCommandLineParameter; import org.dspace.scripts.DSpaceRunnable; import org.springframework.beans.factory.BeanNameAware; +import org.springframework.beans.factory.annotation.Autowired; /** * This class represents an Abstract class that a ScriptConfiguration can inherit to further implement this - * and represent a script's configuration + * and represent a script's configuration. + * By default script are available only to repository administrators script that have a broader audience + * must override the {@link #isAllowedToExecute(Context, List)} method. */ public abstract class ScriptConfiguration implements BeanNameAware { + @Autowired + protected AuthorizeService authorizeService; + /** * The possible options for this script */ @@ -70,6 +81,7 @@ public void setName(String name) { * @param dspaceRunnableClass The dspaceRunnableClass to be set on this IndexDiscoveryScriptConfiguration */ public abstract void setDspaceRunnableClass(Class dspaceRunnableClass); + /** * This method will return if the script is allowed to execute in the given context. This is by default set * to the currentUser in the context being an admin, however this can be overwritten by each script individually @@ -77,7 +89,13 @@ public void setName(String name) { * @param context The relevant DSpace context * @return A boolean indicating whether the script is allowed to execute or not */ - public abstract boolean isAllowedToExecute(Context context); + public boolean isAllowedToExecute(Context context, List commandLineParameters) { + try { + return authorizeService.isAdmin(context); + } catch (SQLException e) { + throw new RuntimeException("SQLException occurred when checking if the current user is an admin", e); + } + } /** * The getter for the options of the Script diff --git a/dspace-api/src/main/java/org/dspace/scripts/service/ProcessService.java b/dspace-api/src/main/java/org/dspace/scripts/service/ProcessService.java index ce6a173b0eda..c6fc24888155 100644 --- a/dspace-api/src/main/java/org/dspace/scripts/service/ProcessService.java +++ b/dspace-api/src/main/java/org/dspace/scripts/service/ProcessService.java @@ -255,4 +255,26 @@ void createLogBitstream(Context context, Process process) */ List findByStatusAndCreationTimeOlderThan(Context context, List statuses, Date date) throws SQLException; + + /** + * Returns a list of all Process objects in the database by the given user. + * + * @param context The relevant DSpace context + * @param user The user to search for + * @param limit The limit for the amount of Processes returned + * @param offset The offset for the Processes to be returned + * @return The list of all Process objects in the Database + * @throws SQLException If something goes wrong + */ + List findByUser(Context context, EPerson user, int limit, int offset) throws SQLException; + + /** + * Count all the processes which is related to the given user. + * + * @param context The relevant DSpace context + * @param user The user to search for + * @return The number of results matching the query + * @throws SQLException If something goes wrong + */ + int countByUser(Context context, EPerson user) throws SQLException; } diff --git a/dspace-api/src/main/java/org/dspace/statistics/export/RetryFailedOpenUrlTrackerScriptConfiguration.java b/dspace-api/src/main/java/org/dspace/statistics/export/RetryFailedOpenUrlTrackerScriptConfiguration.java index dcae4aa4cbcd..7d1015c8e2ba 100644 --- a/dspace-api/src/main/java/org/dspace/statistics/export/RetryFailedOpenUrlTrackerScriptConfiguration.java +++ b/dspace-api/src/main/java/org/dspace/statistics/export/RetryFailedOpenUrlTrackerScriptConfiguration.java @@ -7,13 +7,8 @@ */ package org.dspace.statistics.export; -import java.sql.SQLException; - import org.apache.commons.cli.Options; -import org.dspace.authorize.service.AuthorizeService; -import org.dspace.core.Context; import org.dspace.scripts.configuration.ScriptConfiguration; -import org.springframework.beans.factory.annotation.Autowired; /** * The {@link ScriptConfiguration} for the {@link RetryFailedOpenUrlTracker} script @@ -21,9 +16,6 @@ public class RetryFailedOpenUrlTrackerScriptConfiguration extends ScriptConfiguration { - @Autowired - private AuthorizeService authorizeService; - private Class dspaceRunnableClass; @Override @@ -41,15 +33,6 @@ public void setDspaceRunnableClass(Class dspaceRunnableClass) { this.dspaceRunnableClass = dspaceRunnableClass; } - @Override - public boolean isAllowedToExecute(Context context) { - try { - return authorizeService.isAdmin(context); - } catch (SQLException e) { - throw new RuntimeException("SQLException occurred when checking if the current user is an admin", e); - } - } - @Override public Options getOptions() { if (options == null) { diff --git a/dspace-api/src/main/java/org/dspace/submit/migration/SubmissionFormsMigrationCliScriptConfiguration.java b/dspace-api/src/main/java/org/dspace/submit/migration/SubmissionFormsMigrationCliScriptConfiguration.java index 41b15ddd7a5a..894d3491a181 100644 --- a/dspace-api/src/main/java/org/dspace/submit/migration/SubmissionFormsMigrationCliScriptConfiguration.java +++ b/dspace-api/src/main/java/org/dspace/submit/migration/SubmissionFormsMigrationCliScriptConfiguration.java @@ -7,13 +7,8 @@ */ package org.dspace.submit.migration; -import java.sql.SQLException; - import org.apache.commons.cli.Options; -import org.dspace.authorize.service.AuthorizeService; -import org.dspace.core.Context; import org.dspace.scripts.configuration.ScriptConfiguration; -import org.springframework.beans.factory.annotation.Autowired; /** * The {@link ScriptConfiguration} for the {@link SubmissionFormsMigration} script @@ -23,9 +18,6 @@ public class SubmissionFormsMigrationCliScriptConfiguration extends ScriptConfiguration { - @Autowired - private AuthorizeService authorizeService; - private Class dspaceRunnableClass; @Override @@ -38,15 +30,6 @@ public void setDspaceRunnableClass(Class dspaceRunnableClass) { this.dspaceRunnableClass = dspaceRunnableClass; } - @Override - public boolean isAllowedToExecute(Context context) { - try { - return authorizeService.isAdmin(context); - } catch (SQLException e) { - throw new RuntimeException("SQLException occurred when checking if the current user is an admin", e); - } - } - @Override public Options getOptions() { if (options == null) { diff --git a/dspace-api/src/main/java/org/dspace/submit/migration/SubmissionFormsMigrationScriptConfiguration.java b/dspace-api/src/main/java/org/dspace/submit/migration/SubmissionFormsMigrationScriptConfiguration.java index af3574da699e..6d9f3198fe26 100644 --- a/dspace-api/src/main/java/org/dspace/submit/migration/SubmissionFormsMigrationScriptConfiguration.java +++ b/dspace-api/src/main/java/org/dspace/submit/migration/SubmissionFormsMigrationScriptConfiguration.java @@ -7,7 +7,12 @@ */ package org.dspace.submit.migration; +import java.util.List; + +import org.apache.commons.cli.Options; import org.dspace.core.Context; +import org.dspace.scripts.DSpaceCommandLineParameter; +import org.dspace.scripts.configuration.ScriptConfiguration; /** * Subclass of {@link SubmissionFormsMigrationCliScriptConfiguration} to be use in rest/scripts.xml configuration so @@ -15,10 +20,37 @@ * * @author Maria Verdonck (Atmire) on 05/01/2021 */ -public class SubmissionFormsMigrationScriptConfiguration extends SubmissionFormsMigrationCliScriptConfiguration { +public class SubmissionFormsMigrationScriptConfiguration + extends ScriptConfiguration { + + private Class dspaceRunnableClass; + + @Override + public Class getDspaceRunnableClass() { + return this.dspaceRunnableClass; + } + + @Override + public void setDspaceRunnableClass(Class dspaceRunnableClass) { + this.dspaceRunnableClass = dspaceRunnableClass; + } + + @Override + public Options getOptions() { + if (options == null) { + Options options = new Options(); + + options.addOption("f", "input-forms", true, "Path to source input-forms.xml file location"); + options.addOption("s", "item-submission", true, "Path to source item-submission.xml file location"); + options.addOption("h", "help", false, "help"); + + super.options = options; + } + return options; + } @Override - public boolean isAllowedToExecute(Context context) { + public boolean isAllowedToExecute(Context context, List commandLineParameters) { // Script is not allowed to be executed from REST side return false; } diff --git a/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationConfiguration.java b/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationConfiguration.java index 52685b563d9b..dd61fab9671c 100644 --- a/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationConfiguration.java +++ b/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationConfiguration.java @@ -8,15 +8,11 @@ package org.dspace.subscriptions; -import java.sql.SQLException; import java.util.Objects; import org.apache.commons.cli.Options; -import org.dspace.authorize.AuthorizeServiceImpl; -import org.dspace.core.Context; import org.dspace.scripts.DSpaceRunnable; import org.dspace.scripts.configuration.ScriptConfiguration; -import org.springframework.beans.factory.annotation.Autowired; /** * Implementation of {@link DSpaceRunnable} to find subscribed objects and send notification mails about them @@ -26,18 +22,6 @@ public class SubscriptionEmailNotificationConfiguration dspaceRunnableClass; - @Autowired - private AuthorizeServiceImpl authorizeService; - - @Override - public boolean isAllowedToExecute(Context context) { - try { - return authorizeService.isAdmin(context); - } catch (SQLException e) { - throw new RuntimeException("SQLException occurred when checking if the current user is an admin", e); - } - } - @Override public Options getOptions() { if (Objects.isNull(options)) { diff --git a/dspace-api/src/test/java/org/dspace/builder/AbstractDSpaceObjectBuilder.java b/dspace-api/src/test/java/org/dspace/builder/AbstractDSpaceObjectBuilder.java index ff1083d318d9..b20515017af0 100644 --- a/dspace-api/src/test/java/org/dspace/builder/AbstractDSpaceObjectBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/AbstractDSpaceObjectBuilder.java @@ -162,8 +162,8 @@ protected > B setOnlyReadPermission(DSp return (B) this; } /** - * Support method to grant the {@link Constants#READ} permission over an object only to a specific group. Any other - * READ permissions will be removed + * Support method to grant the {@link Constants#ADMIN} permission over an object only to a specific eperson. + * If another ADMIN policy is in place for an eperson it will be replaced * * @param dso * the DSpaceObject on which grant the permission diff --git a/dspace-api/src/test/java/org/dspace/builder/ItemBuilder.java b/dspace-api/src/test/java/org/dspace/builder/ItemBuilder.java index 70dea309f2cf..3e5ab0f38f5b 100644 --- a/dspace-api/src/test/java/org/dspace/builder/ItemBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/ItemBuilder.java @@ -353,9 +353,9 @@ public ItemBuilder withOtherIdentifier(String identifier) { } /** - * Create an admin group for the collection with the specified members + * Assign the admin permission to the specified eperson * - * @param ePerson epersons to add to the admin group + * @param ePerson the eperson that will get the ADMIN permission on the item * @return this builder * @throws SQLException * @throws AuthorizeException diff --git a/dspace-api/src/test/java/org/dspace/builder/ProcessBuilder.java b/dspace-api/src/test/java/org/dspace/builder/ProcessBuilder.java index 86573940e416..0631e1b55a37 100644 --- a/dspace-api/src/test/java/org/dspace/builder/ProcessBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/ProcessBuilder.java @@ -113,6 +113,9 @@ public void delete(Context c, Process dso) throws Exception { } public static void deleteProcess(Integer integer) throws SQLException, IOException { + if (integer == null) { + return; + } try (Context c = new Context()) { c.turnOffAuthorisationSystem(); Process process = processService.find(c, integer); diff --git a/dspace-api/src/test/java/org/dspace/scripts/MockDSpaceRunnableScriptConfiguration.java b/dspace-api/src/test/java/org/dspace/scripts/MockDSpaceRunnableScriptConfiguration.java index f69c0e3af762..632b4e2f83f4 100644 --- a/dspace-api/src/test/java/org/dspace/scripts/MockDSpaceRunnableScriptConfiguration.java +++ b/dspace-api/src/test/java/org/dspace/scripts/MockDSpaceRunnableScriptConfiguration.java @@ -8,21 +8,13 @@ package org.dspace.scripts; import java.io.InputStream; -import java.sql.SQLException; import org.apache.commons.cli.Options; -import org.dspace.authorize.service.AuthorizeService; -import org.dspace.core.Context; import org.dspace.scripts.configuration.ScriptConfiguration; import org.dspace.scripts.impl.MockDSpaceRunnableScript; -import org.springframework.beans.factory.annotation.Autowired; public class MockDSpaceRunnableScriptConfiguration extends ScriptConfiguration { - - @Autowired - private AuthorizeService authorizeService; - private Class dspaceRunnableClass; @Override @@ -39,15 +31,6 @@ public void setDspaceRunnableClass(Class dspaceRunnableClass) { this.dspaceRunnableClass = dspaceRunnableClass; } - @Override - public boolean isAllowedToExecute(Context context) { - try { - return authorizeService.isAdmin(context); - } catch (SQLException e) { - throw new RuntimeException("SQLException occurred when checking if the current user is an admin", e); - } - } - @Override public Options getOptions() { if (options == null) { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/ScriptProcessesController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/ScriptProcessesController.java index 196cade5dd51..3aeec9535be5 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/ScriptProcessesController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/ScriptProcessesController.java @@ -12,6 +12,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.dspace.app.rest.converter.ConverterService; +import org.dspace.app.rest.exception.DSpaceBadRequestException; import org.dspace.app.rest.model.ProcessRest; import org.dspace.app.rest.model.ScriptRest; import org.dspace.app.rest.model.hateoas.ProcessResource; @@ -24,6 +25,7 @@ import org.springframework.hateoas.RepresentationModel; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.PathVariable; @@ -59,8 +61,8 @@ public class ScriptProcessesController { * @return The ProcessResource object for the created process * @throws Exception If something goes wrong */ - @RequestMapping(method = RequestMethod.POST) - @PreAuthorize("hasAuthority('ADMIN')") + @RequestMapping(method = RequestMethod.POST, consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + @PreAuthorize("hasAuthority('AUTHENTICATED')") public ResponseEntity> startProcess( @PathVariable(name = "name") String scriptName, @RequestParam(name = "file", required = false) List files) @@ -75,4 +77,13 @@ public ResponseEntity> startProcess( return ControllerUtils.toResponseEntity(HttpStatus.ACCEPTED, new HttpHeaders(), processResource); } + @RequestMapping(method = RequestMethod.POST, consumes = "!" + MediaType.MULTIPART_FORM_DATA_VALUE) + @PreAuthorize("hasAuthority('AUTHENTICATED')") + public ResponseEntity> startProcessInvalidMimeType( + @PathVariable(name = "name") String scriptName, + @RequestParam(name = "file", required = false) List files) + throws Exception { + throw new DSpaceBadRequestException("Invalid mimetype"); + } + } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ProcessRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ProcessRestRepository.java index 33addf704916..2479eeda97f5 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ProcessRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ProcessRestRepository.java @@ -94,6 +94,22 @@ public Page findAll(Context context, Pageable pageable) { } } + @SearchRestMethod(name = "own") + @PreAuthorize("hasAuthority('AUTHENTICATED')") + public Page findByCurrentUser(Pageable pageable) { + + try { + Context context = obtainContext(); + long total = processService.countByUser(context, context.getCurrentUser()); + List processes = processService.findByUser(context, context.getCurrentUser(), + pageable.getPageSize(), + Math.toIntExact(pageable.getOffset())); + return converter.toRestPage(processes, pageable, total, utils.obtainProjection()); + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + /** * Calls on the getBitstreams method to retrieve all the Bitstreams of this process * @param processId The processId of the Process to retrieve the Bitstreams for diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ScriptRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ScriptRestRepository.java index d974a6d78a6c..2fc996a327cc 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ScriptRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ScriptRestRepository.java @@ -37,6 +37,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.data.rest.webmvc.ResourceNotFoundException; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Component; @@ -56,29 +57,24 @@ public class ScriptRestRepository extends DSpaceRestRepository findAll(Context context, Pageable pageable) { List scriptConfigurations = scriptService.getScriptConfigurations(context); @@ -104,11 +100,17 @@ public ProcessRest startProcess(Context context, String scriptName, List dSpaceCommandLineParameters = processPropertiesToDSpaceCommandLineParameters(properties); ScriptConfiguration scriptToExecute = scriptService.getScriptConfiguration(scriptName); + if (scriptToExecute == null) { - throw new DSpaceBadRequestException("The script for name: " + scriptName + " wasn't found"); + throw new ResourceNotFoundException("The script for name: " + scriptName + " wasn't found"); } - if (!scriptToExecute.isAllowedToExecute(context)) { - throw new AuthorizeException("Current user is not eligible to execute script with name: " + scriptName); + try { + if (!scriptToExecute.isAllowedToExecute(context, dSpaceCommandLineParameters)) { + throw new AuthorizeException("Current user is not eligible to execute script with name: " + scriptName + + " and the specified parameters " + StringUtils.join(dSpaceCommandLineParameters, ", ")); + } + } catch (IllegalArgumentException e) { + throw new DSpaceBadRequestException("missed handle"); } RestDSpaceRunnableHandler restDSpaceRunnableHandler = new RestDSpaceRunnableHandler( context.getCurrentUser(), scriptToExecute.getName(), dSpaceCommandLineParameters, diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ProcessRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ProcessRestRepositoryIT.java index 5ac416e6067d..d76e20b23d19 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ProcessRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ProcessRestRepositoryIT.java @@ -7,6 +7,8 @@ */ package org.dspace.app.rest; +import static org.dspace.app.rest.matcher.ProcessMatcher.matchProcess; +import static org.dspace.content.ProcessStatus.SCHEDULED; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.is; @@ -783,6 +785,30 @@ public void searchProcessTestByUserSortedOnNonExistingIsSortedAsDefault() throws .andExpect(status().isBadRequest()); } + @Test + public void testFindByCurrentUser() throws Exception { + + Process process1 = ProcessBuilder.createProcess(context, eperson, "mock-script", parameters) + .withStartAndEndTime("10/01/1990", "20/01/1990") + .build(); + ProcessBuilder.createProcess(context, admin, "mock-script", parameters) + .withStartAndEndTime("11/01/1990", "19/01/1990") + .build(); + Process process3 = ProcessBuilder.createProcess(context, eperson, "mock-script", parameters) + .withStartAndEndTime("12/01/1990", "18/01/1990") + .build(); + + String token = getAuthToken(eperson.getEmail(), password); + + getClient(token).perform(get("/api/system/processes/search/own")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.processes", contains( + matchProcess(process3.getName(), eperson.getID().toString(), process3.getID(), parameters, SCHEDULED), + matchProcess(process1.getName(), eperson.getID().toString(), process1.getID(), parameters, SCHEDULED)))) + .andExpect(jsonPath("$.page", is(PageMatcher.pageEntryWithTotalPagesAndElements(0, 20, 1, 2)))); + + } + @Test public void getProcessOutput() throws Exception { try (InputStream is = IOUtils.toInputStream("Test File For Process", CharEncoding.UTF_8)) { diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ScriptRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ScriptRestRepositoryIT.java index 07edfeec33d3..16e691ef6f95 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ScriptRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ScriptRestRepositoryIT.java @@ -12,6 +12,7 @@ import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.greaterThanOrEqualTo; import static org.hamcrest.Matchers.hasItem; import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertThat; @@ -44,6 +45,7 @@ import org.dspace.authorize.AuthorizeException; 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.ProcessBuilder; @@ -53,6 +55,7 @@ import org.dspace.content.Item; import org.dspace.content.ProcessStatus; import org.dspace.content.service.BitstreamService; +import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; import org.dspace.scripts.DSpaceCommandLineParameter; import org.dspace.scripts.Process; @@ -123,12 +126,65 @@ public void findAllScriptsSortedAlphabeticallyTest() throws Exception { @Test - public void findAllScriptsUnauthorizedTest() throws Exception { + public void findAllScriptsGenericLoggedInUserTest() throws Exception { String token = getAuthToken(eperson.getEmail(), password); getClient(token).perform(get("/api/system/scripts")) - .andExpect(status().isForbidden()); + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", is(0))); + } + @Test + public void findAllScriptsLocalAdminsTest() throws Exception { + context.turnOffAuthorisationSystem(); + EPerson comAdmin = EPersonBuilder.createEPerson(context) + .withEmail("comAdmin@example.com") + .withPassword(password).build(); + EPerson colAdmin = EPersonBuilder.createEPerson(context) + .withEmail("colAdmin@example.com") + .withPassword(password).build(); + EPerson itemAdmin = EPersonBuilder.createEPerson(context) + .withEmail("itemAdmin@example.com") + .withPassword(password).build(); + Community community = CommunityBuilder.createCommunity(context) + .withName("Community") + .withAdminGroup(comAdmin) + .build(); + Collection collection = CollectionBuilder.createCollection(context, community) + .withName("Collection") + .withAdminGroup(colAdmin) + .build(); + ItemBuilder.createItem(context, collection).withAdminUser(itemAdmin) + .withTitle("Test item to curate").build(); + context.restoreAuthSystemState(); + ScriptConfiguration curateScriptConfiguration = + scriptConfigurations.stream().filter(scriptConfiguration + -> scriptConfiguration.getName().equals("curate")) + .findAny().get(); + + // the local admins have at least access to the curate script + // and not access to process-cleaner script + String comAdminToken = getAuthToken(comAdmin.getEmail(), password); + getClient(comAdminToken).perform(get("/api/system/scripts").param("size", "100")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.scripts", Matchers.hasItem( + ScriptMatcher.matchScript(curateScriptConfiguration.getName(), + curateScriptConfiguration.getDescription())))) + .andExpect(jsonPath("$.page.totalElements", greaterThanOrEqualTo(1))); + String colAdminToken = getAuthToken(colAdmin.getEmail(), password); + getClient(colAdminToken).perform(get("/api/system/scripts").param("size", "100")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.scripts", Matchers.hasItem( + ScriptMatcher.matchScript(curateScriptConfiguration.getName(), + curateScriptConfiguration.getDescription())))) + .andExpect(jsonPath("$.page.totalElements", greaterThanOrEqualTo(1))); + String itemAdminToken = getAuthToken(itemAdmin.getEmail(), password); + getClient(itemAdminToken).perform(get("/api/system/scripts").param("size", "100")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.scripts", Matchers.hasItem( + ScriptMatcher.matchScript(curateScriptConfiguration.getName(), + curateScriptConfiguration.getDescription())))) + .andExpect(jsonPath("$.page.totalElements", greaterThanOrEqualTo(1))); } @Test @@ -222,6 +278,63 @@ public void findOneScriptByNameTest() throws Exception { )); } + @Test + public void findOneScriptByNameLocalAdminsTest() throws Exception { + context.turnOffAuthorisationSystem(); + EPerson comAdmin = EPersonBuilder.createEPerson(context) + .withEmail("comAdmin@example.com") + .withPassword(password).build(); + EPerson colAdmin = EPersonBuilder.createEPerson(context) + .withEmail("colAdmin@example.com") + .withPassword(password).build(); + EPerson itemAdmin = EPersonBuilder.createEPerson(context) + .withEmail("itemAdmin@example.com") + .withPassword(password).build(); + Community community = CommunityBuilder.createCommunity(context) + .withName("Community") + .withAdminGroup(comAdmin) + .build(); + Collection collection = CollectionBuilder.createCollection(context, community) + .withName("Collection") + .withAdminGroup(colAdmin) + .build(); + ItemBuilder.createItem(context, collection).withAdminUser(itemAdmin) + .withTitle("Test item to curate").build(); + context.restoreAuthSystemState(); + ScriptConfiguration curateScriptConfiguration = + scriptConfigurations.stream().filter(scriptConfiguration + -> scriptConfiguration.getName().equals("curate")) + .findAny().get(); + + String comAdminToken = getAuthToken(comAdmin.getEmail(), password); + String colAdminToken = getAuthToken(colAdmin.getEmail(), password); + String itemAdminToken = getAuthToken(itemAdmin.getEmail(), password); + getClient(comAdminToken).perform(get("/api/system/scripts/" + curateScriptConfiguration.getName())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", ScriptMatcher + .matchScript( + curateScriptConfiguration.getName(), + curateScriptConfiguration.getDescription()))); + getClient(colAdminToken).perform(get("/api/system/scripts/" + curateScriptConfiguration.getName())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", ScriptMatcher + .matchScript( + curateScriptConfiguration.getName(), + curateScriptConfiguration.getDescription()))); + getClient(itemAdminToken).perform(get("/api/system/scripts/" + curateScriptConfiguration.getName())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", ScriptMatcher + .matchScript( + curateScriptConfiguration.getName(), + curateScriptConfiguration.getDescription()))); + } + + @Test + public void findOneScriptByNameNotAuthenticatedTest() throws Exception { + getClient().perform(get("/api/system/scripts/mock-script")) + .andExpect(status().isUnauthorized()); + } + @Test public void findOneScriptByNameTestAccessDenied() throws Exception { String token = getAuthToken(eperson.getEmail(), password); @@ -235,7 +348,7 @@ public void findOneScriptByInvalidNameBadRequestExceptionTest() throws Exception String token = getAuthToken(admin.getEmail(), password); getClient(token).perform(get("/api/system/scripts/mock-script-invalid")) - .andExpect(status().isBadRequest()); + .andExpect(status().isNotFound()); } @Test @@ -277,16 +390,6 @@ public void postProcessAdminWrongOptionsException() throws Exception { @Test public void postProcessAdminNoOptionsFailedStatus() throws Exception { -// List list = new LinkedList<>(); -// -// ParameterValueRest parameterValueRest = new ParameterValueRest(); -// parameterValueRest.setName("-z"); -// parameterValueRest.setValue("test"); -// ParameterValueRest parameterValueRest1 = new ParameterValueRest(); -// parameterValueRest1.setName("-q"); -// list.add(parameterValueRest); -// list.add(parameterValueRest1); - LinkedList parameters = new LinkedList<>(); parameters.add(new DSpaceCommandLineParameter("-z", "test")); @@ -322,7 +425,7 @@ public void postProcessNonExistingScriptNameException() throws Exception { String token = getAuthToken(admin.getEmail(), password); getClient(token).perform(multipart("/api/system/scripts/mock-script-invalid/processes")) - .andExpect(status().isBadRequest()); + .andExpect(status().isNotFound()); } @Test @@ -434,6 +537,8 @@ public void postProcessAndVerifyOutput() throws Exception { } + + @Test public void postProcessAdminWithWrongContentTypeBadRequestException() throws Exception { @@ -601,9 +706,9 @@ public void TrackSpecialGroupduringprocessSchedulingTest() throws Exception { } } - @After public void destroy() throws Exception { + context.turnOffAuthorisationSystem(); CollectionUtils.emptyIfNull(processService.findAll(context)).stream().forEach(process -> { try { processService.delete(context, process); @@ -611,6 +716,7 @@ public void destroy() throws Exception { throw new RuntimeException(e); } }); + context.restoreAuthSystemState(); super.destroy(); } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/scripts/TypeConversionTestScriptConfiguration.java b/dspace-server-webapp/src/test/java/org/dspace/app/scripts/TypeConversionTestScriptConfiguration.java index 27c37f1487e4..ccb7d43a2378 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/scripts/TypeConversionTestScriptConfiguration.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/scripts/TypeConversionTestScriptConfiguration.java @@ -11,7 +11,6 @@ import org.apache.commons.cli.Options; import org.dspace.app.rest.converter.ScriptConverter; -import org.dspace.core.Context; import org.dspace.scripts.configuration.ScriptConfiguration; /** @@ -28,10 +27,6 @@ public void setDspaceRunnableClass(final Class dspaceRunnableClass) { } - public boolean isAllowedToExecute(final Context context) { - return true; - } - public Options getOptions() { Options options = new Options(); diff --git a/dspace-server-webapp/src/test/java/org/dspace/curate/CurationScriptIT.java b/dspace-server-webapp/src/test/java/org/dspace/curate/CurationScriptIT.java index a528f4351356..3e40a8559482 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/curate/CurationScriptIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/curate/CurationScriptIT.java @@ -14,6 +14,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import java.io.File; +import java.util.Arrays; import java.util.LinkedList; import java.util.List; import java.util.concurrent.atomic.AtomicReference; @@ -29,13 +30,19 @@ import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.builder.CollectionBuilder; import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.EPersonBuilder; import org.dspace.builder.ItemBuilder; import org.dspace.builder.ProcessBuilder; import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.Item; import org.dspace.content.ProcessStatus; +import org.dspace.content.Site; +import org.dspace.content.factory.ContentServiceFactory; +import org.dspace.eperson.EPerson; import org.dspace.scripts.DSpaceCommandLineParameter; +import org.dspace.scripts.configuration.ScriptConfiguration; +import org.dspace.scripts.service.ScriptService; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -49,6 +56,9 @@ public class CurationScriptIT extends AbstractControllerIntegrationTest { @Autowired private DSpaceRunnableParameterConverter dSpaceRunnableParameterConverter; + @Autowired + private ScriptService scriptService; + private final static String SCRIPTS_ENDPOINT = "/api/" + ScriptRest.CATEGORY + "/" + ScriptRest.PLURAL_NAME; private final static String CURATE_SCRIPT_ENDPOINT = SCRIPTS_ENDPOINT + "/curate/" + ProcessRest.PLURAL_NAME; @@ -371,6 +381,263 @@ public void curateScript_EPersonInParametersFails() throws Exception { } } + /** + * This test will create a basic structure of communities, collections and items with some local admins at each + * level and verify that the local admins can only run the curate script on their own objects + */ + @Test + public void securityCurateTest() throws Exception { + context.turnOffAuthorisationSystem(); + EPerson comAdmin = EPersonBuilder.createEPerson(context) + .withEmail("comAdmin@example.com") + .withPassword(password).build(); + EPerson colAdmin = EPersonBuilder.createEPerson(context) + .withEmail("colAdmin@example.com") + .withPassword(password).build(); + EPerson itemAdmin = EPersonBuilder.createEPerson(context) + .withEmail("itemAdmin@example.com") + .withPassword(password).build(); + Community community = CommunityBuilder.createCommunity(context) + .withName("Community") + .withAdminGroup(comAdmin) + .build(); + Community anotherCommunity = CommunityBuilder.createCommunity(context) + .withName("Another Community") + .build(); + Collection collection = CollectionBuilder.createCollection(context, community) + .withName("Collection") + .withAdminGroup(colAdmin) + .build(); + Collection anotherCollection = CollectionBuilder.createCollection(context, anotherCommunity) + .withName("AnotherCollection") + .build(); + Item item = ItemBuilder.createItem(context, collection).withAdminUser(itemAdmin) + .withTitle("Test item to curate").build(); + Item anotherItem = ItemBuilder.createItem(context, anotherCollection) + .withTitle("Another Test item to curate").build(); + Site site = ContentServiceFactory.getInstance().getSiteService().findSite(context); + context.restoreAuthSystemState(); + LinkedList siteParameters = new LinkedList<>(); + siteParameters.add(new DSpaceCommandLineParameter("-i", site.getHandle())); + siteParameters.add(new DSpaceCommandLineParameter("-t", "noop")); + LinkedList comParameters = new LinkedList<>(); + comParameters.add(new DSpaceCommandLineParameter("-i", community.getHandle())); + comParameters.add(new DSpaceCommandLineParameter("-t", "noop")); + LinkedList anotherComParameters = new LinkedList<>(); + anotherComParameters.add(new DSpaceCommandLineParameter("-i", anotherCommunity.getHandle())); + anotherComParameters.add(new DSpaceCommandLineParameter("-t", "noop")); + LinkedList colParameters = new LinkedList<>(); + colParameters.add(new DSpaceCommandLineParameter("-i", collection.getHandle())); + colParameters.add(new DSpaceCommandLineParameter("-t", "noop")); + LinkedList anotherColParameters = new LinkedList<>(); + anotherColParameters.add(new DSpaceCommandLineParameter("-i", anotherCollection.getHandle())); + anotherColParameters.add(new DSpaceCommandLineParameter("-t", "noop")); + LinkedList itemParameters = new LinkedList<>(); + itemParameters.add(new DSpaceCommandLineParameter("-i", item.getHandle())); + itemParameters.add(new DSpaceCommandLineParameter("-t", "noop")); + LinkedList anotherItemParameters = new LinkedList<>(); + anotherItemParameters.add(new DSpaceCommandLineParameter("-i", anotherItem.getHandle())); + anotherItemParameters.add(new DSpaceCommandLineParameter("-t", "noop")); + + String comAdminToken = getAuthToken(comAdmin.getEmail(), password); + String colAdminToken = getAuthToken(colAdmin.getEmail(), password); + String itemAdminToken = getAuthToken(itemAdmin.getEmail(), password); + + List listCurateSite = siteParameters.stream() + .map(dSpaceCommandLineParameter -> dSpaceRunnableParameterConverter + .convert(dSpaceCommandLineParameter, Projection.DEFAULT)) + .collect(Collectors.toList()); + List listCom = comParameters.stream() + .map(dSpaceCommandLineParameter -> dSpaceRunnableParameterConverter + .convert(dSpaceCommandLineParameter, Projection.DEFAULT)) + .collect(Collectors.toList()); + List listAnotherCom = anotherComParameters.stream() + .map(dSpaceCommandLineParameter -> dSpaceRunnableParameterConverter + .convert(dSpaceCommandLineParameter, Projection.DEFAULT)) + .collect(Collectors.toList()); + List listCol = colParameters.stream() + .map(dSpaceCommandLineParameter -> dSpaceRunnableParameterConverter + .convert(dSpaceCommandLineParameter, Projection.DEFAULT)) + .collect(Collectors.toList()); + List listAnotherCol = anotherColParameters.stream() + .map(dSpaceCommandLineParameter -> dSpaceRunnableParameterConverter + .convert(dSpaceCommandLineParameter, Projection.DEFAULT)) + .collect(Collectors.toList()); + List listItem = itemParameters.stream() + .map(dSpaceCommandLineParameter -> dSpaceRunnableParameterConverter + .convert(dSpaceCommandLineParameter, Projection.DEFAULT)) + .collect(Collectors.toList()); + List listAnotherItem = anotherItemParameters.stream() + .map(dSpaceCommandLineParameter -> dSpaceRunnableParameterConverter + .convert(dSpaceCommandLineParameter, Projection.DEFAULT)) + .collect(Collectors.toList()); + String adminToken = getAuthToken(admin.getEmail(), password); + List acceptableProcessStatuses = new LinkedList<>(); + acceptableProcessStatuses.addAll(Arrays.asList(ProcessStatus.SCHEDULED, + ProcessStatus.RUNNING, + ProcessStatus.COMPLETED)); + + AtomicReference idSiteRef = new AtomicReference<>(); + AtomicReference idComRef = new AtomicReference<>(); + AtomicReference idComColRef = new AtomicReference<>(); + AtomicReference idComItemRef = new AtomicReference<>(); + AtomicReference idColRef = new AtomicReference<>(); + AtomicReference idColItemRef = new AtomicReference<>(); + AtomicReference idItemRef = new AtomicReference<>(); + + ScriptConfiguration curateScriptConfiguration = scriptService.getScriptConfiguration("curate"); + // we should be able to start the curate script with all our admins on the respective dso + try { + // start a process as general admin + getClient(adminToken) + .perform(multipart("/api/system/scripts/" + curateScriptConfiguration.getName() + "/processes") + .param("properties", new ObjectMapper().writeValueAsString(listCurateSite))) + .andExpect(status().isAccepted()) + .andExpect(jsonPath("$", is( + ProcessMatcher.matchProcess("curate", + String.valueOf(admin.getID()), + siteParameters, + acceptableProcessStatuses)))) + .andDo(result -> idSiteRef + .set(read(result.getResponse().getContentAsString(), "$.processId"))); + + // check with the com admin + getClient(comAdminToken) + .perform(multipart("/api/system/scripts/" + curateScriptConfiguration.getName() + "/processes") + .param("properties", new ObjectMapper().writeValueAsString(listCom))) + .andExpect(status().isAccepted()) + .andExpect(jsonPath("$", is( + ProcessMatcher.matchProcess("curate", + String.valueOf(comAdmin.getID()), + comParameters, + acceptableProcessStatuses)))) + .andDo(result -> idComRef + .set(read(result.getResponse().getContentAsString(), "$.processId"))); + // the com admin should be able to run the curate also over the children collection and item + getClient(comAdminToken) + .perform(multipart("/api/system/scripts/" + curateScriptConfiguration.getName() + "/processes") + .param("properties", new ObjectMapper().writeValueAsString(listCol))) + .andExpect(status().isAccepted()) + .andExpect(jsonPath("$", is( + ProcessMatcher.matchProcess("curate", + String.valueOf(comAdmin.getID()), + colParameters, + acceptableProcessStatuses)))) + .andDo(result -> idComColRef + .set(read(result.getResponse().getContentAsString(), "$.processId"))); + getClient(comAdminToken) + .perform(multipart("/api/system/scripts/" + curateScriptConfiguration.getName() + "/processes") + .param("properties", new ObjectMapper().writeValueAsString(listItem))) + .andExpect(status().isAccepted()) + .andExpect(jsonPath("$", is( + ProcessMatcher.matchProcess("curate", + String.valueOf(comAdmin.getID()), + itemParameters, + acceptableProcessStatuses)))) + .andDo(result -> idComItemRef + .set(read(result.getResponse().getContentAsString(), "$.processId"))); + // the com admin should be NOT able to run the curate over other com, col or items + getClient(comAdminToken) + .perform(multipart("/api/system/scripts/" + curateScriptConfiguration.getName() + "/processes") + .param("properties", new ObjectMapper().writeValueAsString(listCurateSite))) + .andExpect(status().isForbidden()); + getClient(comAdminToken) + .perform(multipart("/api/system/scripts/" + curateScriptConfiguration.getName() + "/processes") + .param("properties", new ObjectMapper().writeValueAsString(listAnotherCom))) + .andExpect(status().isForbidden()); + getClient(comAdminToken) + .perform(multipart("/api/system/scripts/" + curateScriptConfiguration.getName() + "/processes") + .param("properties", new ObjectMapper().writeValueAsString(listAnotherCol))) + .andExpect(status().isForbidden()); + getClient(comAdminToken) + .perform(multipart("/api/system/scripts/" + curateScriptConfiguration.getName() + "/processes") + .param("properties", new ObjectMapper().writeValueAsString(listAnotherItem))) + .andExpect(status().isForbidden()); + + // check with the col admin + getClient(colAdminToken) + .perform(multipart("/api/system/scripts/" + curateScriptConfiguration.getName() + "/processes") + .param("properties", new ObjectMapper().writeValueAsString(listCol))) + .andExpect(status().isAccepted()) + .andExpect(jsonPath("$", is( + ProcessMatcher.matchProcess("curate", + String.valueOf(colAdmin.getID()), + colParameters, + acceptableProcessStatuses)))) + .andDo(result -> idColRef + .set(read(result.getResponse().getContentAsString(), "$.processId"))); + // the col admin should be able to run the curate also over the owned item + getClient(colAdminToken) + .perform(multipart("/api/system/scripts/" + curateScriptConfiguration.getName() + "/processes") + .param("properties", new ObjectMapper().writeValueAsString(listItem))) + .andExpect(status().isAccepted()) + .andExpect(jsonPath("$", is( + ProcessMatcher.matchProcess("curate", + String.valueOf(colAdmin.getID()), + itemParameters, + acceptableProcessStatuses)))) + .andDo(result -> idColItemRef + .set(read(result.getResponse().getContentAsString(), "$.processId"))); + + // the col admin should be NOT able to run the curate over the community nor another collection nor + // on a not owned item + getClient(colAdminToken) + .perform(multipart("/api/system/scripts/" + curateScriptConfiguration.getName() + "/processes") + .param("properties", new ObjectMapper().writeValueAsString(listCurateSite))) + .andExpect(status().isForbidden()); + getClient(colAdminToken) + .perform(multipart("/api/system/scripts/" + curateScriptConfiguration.getName() + "/processes") + .param("properties", new ObjectMapper().writeValueAsString(listCom))) + .andExpect(status().isForbidden()); + getClient(colAdminToken) + .perform(multipart("/api/system/scripts/" + curateScriptConfiguration.getName() + "/processes") + .param("properties", new ObjectMapper().writeValueAsString(listAnotherCol))) + .andExpect(status().isForbidden()); + getClient(colAdminToken) + .perform(multipart("/api/system/scripts/" + curateScriptConfiguration.getName() + "/processes") + .param("properties", new ObjectMapper().writeValueAsString(listAnotherItem))) + .andExpect(status().isForbidden()); + + // check with the item admin + getClient(itemAdminToken) + .perform(multipart("/api/system/scripts/" + curateScriptConfiguration.getName() + "/processes") + .param("properties", new ObjectMapper().writeValueAsString(listItem))) + .andExpect(status().isAccepted()) + .andExpect(jsonPath("$", is( + ProcessMatcher.matchProcess("curate", + String.valueOf(itemAdmin.getID()), + itemParameters, + acceptableProcessStatuses)))) + .andDo(result -> idItemRef + .set(read(result.getResponse().getContentAsString(), "$.processId"))); + // the item admin should be NOT able to run the curate over the community nor the collection nor + // on a not owned item + getClient(itemAdminToken) + .perform(multipart("/api/system/scripts/" + curateScriptConfiguration.getName() + "/processes") + .param("properties", new ObjectMapper().writeValueAsString(listCurateSite))) + .andExpect(status().isForbidden()); + getClient(itemAdminToken) + .perform(multipart("/api/system/scripts/" + curateScriptConfiguration.getName() + "/processes") + .param("properties", new ObjectMapper().writeValueAsString(listCom))) + .andExpect(status().isForbidden()); + getClient(itemAdminToken) + .perform(multipart("/api/system/scripts/" + curateScriptConfiguration.getName() + "/processes") + .param("properties", new ObjectMapper().writeValueAsString(listCol))) + .andExpect(status().isForbidden()); + getClient(itemAdminToken) + .perform(multipart("/api/system/scripts/" + curateScriptConfiguration.getName() + "/processes") + .param("properties", new ObjectMapper().writeValueAsString(listAnotherItem))) + .andExpect(status().isForbidden()); + } finally { + ProcessBuilder.deleteProcess(idSiteRef.get()); + ProcessBuilder.deleteProcess(idComRef.get()); + ProcessBuilder.deleteProcess(idComColRef.get()); + ProcessBuilder.deleteProcess(idComItemRef.get()); + ProcessBuilder.deleteProcess(idColRef.get()); + ProcessBuilder.deleteProcess(idColItemRef.get()); + ProcessBuilder.deleteProcess(idItemRef.get()); + } + } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/scripts/MockDSpaceRunnableScriptConfiguration.java b/dspace-server-webapp/src/test/java/org/dspace/scripts/MockDSpaceRunnableScriptConfiguration.java index f69c0e3af762..632b4e2f83f4 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/scripts/MockDSpaceRunnableScriptConfiguration.java +++ b/dspace-server-webapp/src/test/java/org/dspace/scripts/MockDSpaceRunnableScriptConfiguration.java @@ -8,21 +8,13 @@ package org.dspace.scripts; import java.io.InputStream; -import java.sql.SQLException; import org.apache.commons.cli.Options; -import org.dspace.authorize.service.AuthorizeService; -import org.dspace.core.Context; import org.dspace.scripts.configuration.ScriptConfiguration; import org.dspace.scripts.impl.MockDSpaceRunnableScript; -import org.springframework.beans.factory.annotation.Autowired; public class MockDSpaceRunnableScriptConfiguration extends ScriptConfiguration { - - @Autowired - private AuthorizeService authorizeService; - private Class dspaceRunnableClass; @Override @@ -39,15 +31,6 @@ public void setDspaceRunnableClass(Class dspaceRunnableClass) { this.dspaceRunnableClass = dspaceRunnableClass; } - @Override - public boolean isAllowedToExecute(Context context) { - try { - return authorizeService.isAdmin(context); - } catch (SQLException e) { - throw new RuntimeException("SQLException occurred when checking if the current user is an admin", e); - } - } - @Override public Options getOptions() { if (options == null) { From 7ba68c40ab47863d3530e28ac9d3572c55dc5537 Mon Sep 17 00:00:00 2001 From: Nikita Krivonosov Date: Thu, 4 May 2023 15:58:04 +0200 Subject: [PATCH 133/510] [CST-5728] LYRASIS [Donated]: DSpace7: Implement signposting pattern in DSpace (REST) --- .../dspace/content/MetadataSchemaEnum.java | 3 +- .../RelationshipMetadataServiceImpl.java | 3 +- .../org/dspace/util/FrontendUrlService.java | 74 ++++ .../java/org/dspace/app/rest/Application.java | 19 +- .../security/WebSecurityConfiguration.java | 2 +- .../controller/LinksetRestController.java | 227 ++++++++++++ .../converter/LinksetConverter.java | 37 ++ .../LinksetHttpMessageConverter.java | 81 +++++ .../signposting/converter/LsetConverter.java | 37 ++ .../signposting/hateoas/LinksetResource.java | 26 ++ .../app/rest/signposting/model/Linkset.java | 127 +++++++ .../rest/signposting/model/LinksetRest.java | 73 ++++ .../app/rest/signposting/model/Lset.java | 68 ++++ .../app/rest/signposting/model/Relation.java | 34 ++ .../rest/signposting/model/TypedLinkRest.java | 97 +++++ .../BitstreamSignPostingProcessor.java | 28 ++ .../processor/ItemSignPostingProcessor.java | 30 ++ .../processor/SignPostingProcessor.java | 30 ++ .../relation/ASignPostingProcessor.java | 48 +++ .../relation/BitstreamLicenseProcessor.java | 74 ++++ .../relation/BitstreamLinksetProcessor.java | 85 +++++ .../BitstreamPublicationBundaryProcessor.java | 76 ++++ .../relation/BitstreamTypeProcessor.java | 74 ++++ .../relation/ItemAuthorProcessor.java | 109 ++++++ .../relation/ItemIdentifierProcessor.java | 89 +++++ .../relation/ItemLandingPageProcessor.java | 59 +++ .../relation/ItemLicenseProcessor.java | 79 ++++ .../ItemPublicationBundaryProcessor.java | 87 +++++ .../relation/ItemTypeProcessor.java | 79 ++++ .../app/rest/utils/ApplicationConfig.java | 27 ++ .../controller/LinksetRestControllerIT.java | 336 ++++++++++++++++++ dspace/config/dspace.cfg | 1 + dspace/config/modules/signposting.cfg | 29 ++ dspace/config/spring/api/core-services.xml | 2 + dspace/config/spring/rest/signposting.xml | 46 +++ 35 files changed, 2291 insertions(+), 5 deletions(-) create mode 100644 dspace-api/src/main/java/org/dspace/util/FrontendUrlService.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/controller/LinksetRestController.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/converter/LinksetConverter.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/converter/LinksetHttpMessageConverter.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/converter/LsetConverter.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/hateoas/LinksetResource.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/model/Linkset.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/model/LinksetRest.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/model/Lset.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/model/Relation.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/model/TypedLinkRest.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/BitstreamSignPostingProcessor.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/ItemSignPostingProcessor.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/SignPostingProcessor.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/relation/ASignPostingProcessor.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/relation/BitstreamLicenseProcessor.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/relation/BitstreamLinksetProcessor.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/relation/BitstreamPublicationBundaryProcessor.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/relation/BitstreamTypeProcessor.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/relation/ItemAuthorProcessor.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/relation/ItemIdentifierProcessor.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/relation/ItemLandingPageProcessor.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/relation/ItemLicenseProcessor.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/relation/ItemPublicationBundaryProcessor.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/relation/ItemTypeProcessor.java create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/signposting/controller/LinksetRestControllerIT.java create mode 100644 dspace/config/modules/signposting.cfg create mode 100644 dspace/config/spring/rest/signposting.xml diff --git a/dspace-api/src/main/java/org/dspace/content/MetadataSchemaEnum.java b/dspace-api/src/main/java/org/dspace/content/MetadataSchemaEnum.java index deca62566aae..559e3bf5cf5a 100644 --- a/dspace-api/src/main/java/org/dspace/content/MetadataSchemaEnum.java +++ b/dspace-api/src/main/java/org/dspace/content/MetadataSchemaEnum.java @@ -16,7 +16,8 @@ public enum MetadataSchemaEnum { DC("dc"), EPERSON("eperson"), - RELATION("relation"); + RELATION("relation"), + PERSON("person"); /** * The String representation of the MetadataSchemaEnum diff --git a/dspace-api/src/main/java/org/dspace/content/RelationshipMetadataServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/RelationshipMetadataServiceImpl.java index 14ed441b819e..c6cf21a55fc7 100644 --- a/dspace-api/src/main/java/org/dspace/content/RelationshipMetadataServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/RelationshipMetadataServiceImpl.java @@ -110,7 +110,8 @@ protected List findLatestForDiscoveryMetadataValues( // on the left item as a storage/performance improvement. // As a consequence, when searching for related items (using discovery) // on the pages of the right items you won't be able to find the left item. - if (relationshipType.getTilted() != RIGHT && relationshipType.getLeftType().equals(itemEntityType)) { + if (relationshipType.getTilted() != RIGHT + && Objects.equals(relationshipType.getLeftType(), itemEntityType)) { String element = relationshipType.getLeftwardType(); List data = relationshipService .findByLatestItemAndRelationshipType(context, item, relationshipType, true); diff --git a/dspace-api/src/main/java/org/dspace/util/FrontendUrlService.java b/dspace-api/src/main/java/org/dspace/util/FrontendUrlService.java new file mode 100644 index 000000000000..5e15c9c50a9e --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/util/FrontendUrlService.java @@ -0,0 +1,74 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.util; + +import static org.apache.commons.collections4.CollectionUtils.isNotEmpty; +import static org.apache.commons.lang3.StringUtils.isNotBlank; +import static org.apache.commons.lang3.StringUtils.lowerCase; + +import java.util.List; +import java.util.Optional; + +import org.apache.logging.log4j.Logger; +import org.dspace.content.Item; +import org.dspace.core.Context; +import org.dspace.discovery.DiscoverQuery; +import org.dspace.discovery.DiscoverResult; +import org.dspace.discovery.SearchService; +import org.dspace.discovery.SearchServiceException; +import org.dspace.services.ConfigurationService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * Service class for generation of front-end urls. + */ +@Component +public class FrontendUrlService { + + private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(FrontendUrlService.class); + + @Autowired + private ConfigurationService configurationService; + + @Autowired + private SearchService searchService; + + /** + * Generates front-end url for specified item. + * + * @param item item + * @return front-end url + */ + public String generateUrl(Item item) { + String uiURL = configurationService.getProperty("dspace.ui.url"); + Context context = new Context(Context.Mode.READ_ONLY); + return generateUrlWithSearchService(item, uiURL, context) + .orElseGet(() -> uiURL + "/items/" + item.getID()); + } + + private Optional generateUrlWithSearchService(Item item, String uiURLStem, Context context) { + DiscoverQuery entityQuery = new DiscoverQuery(); + entityQuery.setQuery("search.uniqueid:\"Item-" + item.getID() + "\" and entityType:*"); + entityQuery.addSearchField("entityType"); + + try { + DiscoverResult discoverResult = searchService.search(context, entityQuery); + if (isNotEmpty(discoverResult.getIndexableObjects())) { + List entityTypes = discoverResult.getSearchDocument(discoverResult.getIndexableObjects() + .get(0)).get(0).getSearchFieldValues("entityType"); + if (isNotEmpty(entityTypes) && isNotBlank(entityTypes.get(0))) { + return Optional.of(uiURLStem + "/entities/" + lowerCase(entityTypes.get(0)) + "/" + item.getID()); + } + } + } catch (SearchServiceException e) { + log.error("Failed getting entitytype through solr for item " + item.getID() + ": " + e.getMessage()); + } + return Optional.empty(); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/Application.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/Application.java index f8922f528c0d..07b802b684ee 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/Application.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/Application.java @@ -178,12 +178,15 @@ public void addCorsMappings(@NonNull CorsRegistry registry) { // Get allowed origins for api and iiif endpoints. // The actuator endpoints are configured using management.endpoints.web.cors.* properties String[] corsAllowedOrigins = configuration - .getCorsAllowedOrigins(configuration.getCorsAllowedOriginsConfig()); + .getCorsAllowedOrigins(configuration.getCorsAllowedOriginsConfig()); String[] iiifAllowedOrigins = configuration - .getCorsAllowedOrigins(configuration.getIiifAllowedOriginsConfig()); + .getCorsAllowedOrigins(configuration.getIiifAllowedOriginsConfig()); + String[] signpostingAllowedOrigins = configuration + .getCorsAllowedOrigins(configuration.getSignpostingAllowedOriginsConfig()); boolean corsAllowCredentials = configuration.getCorsAllowCredentials(); boolean iiifAllowCredentials = configuration.getIiifAllowCredentials(); + boolean signpostingAllowCredentials = configuration.getSignpostingAllowCredentials(); if (corsAllowedOrigins != null) { registry.addMapping("/api/**").allowedMethods(CorsConfiguration.ALL) // Set Access-Control-Allow-Credentials to "true" and specify which origins are valid @@ -209,6 +212,18 @@ public void addCorsMappings(@NonNull CorsRegistry registry) { // Allow list of response headers allowed to be sent by us (the server) to the client .exposedHeaders("Authorization", "DSPACE-XSRF-TOKEN", "Location", "WWW-Authenticate"); } + if (signpostingAllowedOrigins != null) { + registry.addMapping("/signposting/**").allowedMethods(CorsConfiguration.ALL) + // Set Access-Control-Allow-Credentials to "true" and specify which origins are valid + // for our Access-Control-Allow-Origin header + .allowCredentials(signpostingAllowCredentials).allowedOrigins(signpostingAllowedOrigins) + // Allow list of request preflight headers allowed to be sent to us from the client + .allowedHeaders("Accept", "Authorization", "Content-Type", "Origin", "X-On-Behalf-Of", + "X-Requested-With", "X-XSRF-TOKEN", "X-CORRELATION-ID", "X-REFERRER", + "x-recaptcha-token", "access-control-allow-headers") + // Allow list of response headers allowed to be sent by us (the server) to the client + .exposedHeaders("Authorization", "DSPACE-XSRF-TOKEN", "Location", "WWW-Authenticate"); + } } /** diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WebSecurityConfiguration.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WebSecurityConfiguration.java index fad60c20d250..7bd698a63a60 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WebSecurityConfiguration.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WebSecurityConfiguration.java @@ -87,7 +87,7 @@ protected void configure(HttpSecurity http) throws Exception { // Configure authentication requirements for ${dspace.server.url}/api/ URL only // NOTE: REST API is hardcoded to respond on /api/. Other modules (OAI, SWORD, IIIF, etc) use other root paths. http.requestMatchers() - .antMatchers("/api/**", "/iiif/**", actuatorBasePath + "/**") + .antMatchers("/api/**", "/iiif/**", actuatorBasePath + "/**", "/signposting/**") .and() // Enable Spring Security authorization on these paths .authorizeRequests() diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/controller/LinksetRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/controller/LinksetRestController.java new file mode 100644 index 000000000000..9197e2cdf407 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/controller/LinksetRestController.java @@ -0,0 +1,227 @@ +/** + * 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.signposting.controller; + +import static org.dspace.app.rest.utils.RegexUtils.REGEX_REQUESTMAPPING_IDENTIFIER_AS_UUID; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import javax.servlet.http.HttpServletRequest; + +import org.dspace.app.rest.converter.ConverterService; +import org.dspace.app.rest.signposting.model.Linkset; +import org.dspace.app.rest.signposting.model.LinksetRest; +import org.dspace.app.rest.signposting.model.Lset; +import org.dspace.app.rest.signposting.model.Relation; +import org.dspace.app.rest.signposting.model.TypedLinkRest; +import org.dspace.app.rest.signposting.processor.BitstreamSignPostingProcessor; +import org.dspace.app.rest.signposting.processor.ItemSignPostingProcessor; +import org.dspace.app.rest.utils.ContextUtil; +import org.dspace.app.rest.utils.Utils; +import org.dspace.content.Bitstream; +import org.dspace.content.DSpaceObject; +import org.dspace.content.Item; +import org.dspace.content.service.BitstreamService; +import org.dspace.content.service.ItemService; +import org.dspace.core.Constants; +import org.dspace.core.Context; +import org.dspace.handle.service.HandleService; +import org.dspace.services.ConfigurationService; +import org.dspace.utils.DSpace; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.data.rest.webmvc.ResourceNotFoundException; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +/** + * This RestController takes care of the retrieval of {@link LinksetRest}. + * This class will receive the UUID of an {@link Item} or {@link Bitstream}. + * + * @author Francesco Pio Scognamiglio (francescopio.scognamiglio at 4science.com) + */ +@RestController +@RequestMapping("/${signposting.path:signposting}") +@ConditionalOnProperty("signposting.enabled") +public class LinksetRestController { + + @Autowired + private Utils utils; + @Autowired + private BitstreamService bitstreamService; + @Autowired + private ItemService itemService; + @Autowired + private HandleService handleService; + @Autowired + private ConfigurationService configurationService; + @Autowired + private ConverterService converter; + + @PreAuthorize("permitAll()") + @RequestMapping(method = RequestMethod.GET) + public ResponseEntity getAll() { + return ResponseEntity.status(HttpStatus.METHOD_NOT_ALLOWED).build(); + } + + @PreAuthorize("permitAll()") + @RequestMapping(value = "/linksets" + REGEX_REQUESTMAPPING_IDENTIFIER_AS_UUID, method = RequestMethod.GET) + public ResponseEntity getOne() { + return ResponseEntity.status(HttpStatus.METHOD_NOT_ALLOWED).build(); + } + + @PreAuthorize("permitAll()") + @RequestMapping(value = "/linksets" + REGEX_REQUESTMAPPING_IDENTIFIER_AS_UUID + "/json", + method = RequestMethod.GET, produces = "application/linkset+json") + public LinksetRest getJson(HttpServletRequest request, @PathVariable UUID uuid) { + try { + Context context = ContextUtil.obtainContext(request); + + DSpaceObject dso = null; + dso = itemService.find(context, uuid); + if (dso == null) { + throw new ResourceNotFoundException("No such Item: " + uuid); + } + + List linksets = new ArrayList<>(); + Linkset primaryLinkset = new Linkset(); + linksets.add(primaryLinkset); + + if (dso.getType() == Constants.ITEM) { + primaryLinkset.setAnchor(handleService.resolveToURL( + context, dso.getHandle())); + List ispp = new DSpace().getServiceManager() + .getServicesByType(ItemSignPostingProcessor.class); + for (ItemSignPostingProcessor sp : ispp) { + sp.buildRelation(context, request, (Item) dso, linksets, primaryLinkset); + } + } + + LinksetRest linksetRest = null; + for (Linkset linkset : linksets) { + if (linksetRest == null) { + linksetRest = converter.toRest(linkset, utils.obtainProjection()); + } else { + linksetRest.getLinkset().add(linkset); + } + } + return linksetRest; + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + + @PreAuthorize("permitAll()") + @RequestMapping(value = "/linksets" + REGEX_REQUESTMAPPING_IDENTIFIER_AS_UUID, + method = RequestMethod.GET, produces = "application/linkset") + public LinksetRest getLset(HttpServletRequest request, @PathVariable UUID uuid) { + try { + Context context = ContextUtil.obtainContext(request); + + DSpaceObject dso = null; + dso = itemService.find(context, uuid); + if (dso == null) { + throw new ResourceNotFoundException("No such Item: " + uuid); + } + + List lsets = new ArrayList<>(); + if (dso.getType() == Constants.ITEM) { + List ispp = new DSpace().getServiceManager() + .getServicesByType(ItemSignPostingProcessor.class); + for (ItemSignPostingProcessor sp : ispp) { + sp.buildLset(context, request, (Item) dso, lsets); + } + } + + LinksetRest linksetRest = null; + for (Lset lset : lsets) { + if (linksetRest == null) { + linksetRest = converter.toRest(lset, utils.obtainProjection()); + } else { + linksetRest.getLset().add(lset); + } + } + return linksetRest; + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + + @PreAuthorize("permitAll()") + @RequestMapping(value = "/links" + REGEX_REQUESTMAPPING_IDENTIFIER_AS_UUID, method = RequestMethod.GET) + public List getHeader(HttpServletRequest request, @PathVariable UUID uuid) { + try { + Context context = ContextUtil.obtainContext(request); + + DSpaceObject dso = null; + dso = bitstreamService.find(context, uuid); + if (dso == null) { + dso = itemService.find(context, uuid); + if (dso == null) { + throw new ResourceNotFoundException("No such resource: " + uuid); + } + } + + List linksets = new ArrayList<>(); + Linkset primaryLinkset = new Linkset(); + linksets.add(primaryLinkset); + + if (dso.getType() == Constants.ITEM) { + primaryLinkset.setAnchor(handleService.resolveToURL( + context, dso.getHandle())); + List ispp = new DSpace().getServiceManager() + .getServicesByType(ItemSignPostingProcessor.class); + for (ItemSignPostingProcessor sp : ispp) { + sp.buildRelation(context, request, (Item) dso, linksets, primaryLinkset); + } + } else { + List bspp = new DSpace().getServiceManager() + .getServicesByType(BitstreamSignPostingProcessor.class); + for (BitstreamSignPostingProcessor sp : bspp) { + sp.buildRelation(context, request, (Bitstream) dso, linksets, primaryLinkset); + } + String url = configurationService.getProperty("dspace.ui.url"); + primaryLinkset.setAnchor(url + "/bitstreams/" + dso.getID() + "/download"); + } + + return linksets.stream() + .flatMap(linkset -> mapTypedLinks(linkset).stream()) + .collect(Collectors.toList()); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + + private static List mapTypedLinks(Linkset linkset) { + return Stream.of( + mapTypedLinks(TypedLinkRest.Relation.LANDING_PAGE, linkset.getLandingPage()), + mapTypedLinks(TypedLinkRest.Relation.ITEM, linkset.getItem()), + mapTypedLinks(TypedLinkRest.Relation.CITE_AS, linkset.getCiteAs()), + mapTypedLinks(TypedLinkRest.Relation.AUTHOR, linkset.getAuthor()), + mapTypedLinks(TypedLinkRest.Relation.TYPE, linkset.getType()), + mapTypedLinks(TypedLinkRest.Relation.LICENSE, linkset.getLicense()), + mapTypedLinks(TypedLinkRest.Relation.COLLECTION, linkset.getCollection()), + mapTypedLinks(TypedLinkRest.Relation.LINKSET, linkset.getLinkset()) + ).flatMap(List::stream).collect(Collectors.toList()); + } + + private static List mapTypedLinks(TypedLinkRest.Relation relationType, List relations) { + return relations.stream() + .map(relation -> new TypedLinkRest(relation.getHref(), relationType, relation.getType())) + .collect(Collectors.toList()); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/converter/LinksetConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/converter/LinksetConverter.java new file mode 100644 index 000000000000..e8f151f14a00 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/converter/LinksetConverter.java @@ -0,0 +1,37 @@ +/** + * 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.signposting.converter; + +import org.dspace.app.rest.converter.DSpaceConverter; +import org.dspace.app.rest.projection.Projection; +import org.dspace.app.rest.signposting.model.Linkset; +import org.dspace.app.rest.signposting.model.LinksetRest; +import org.springframework.stereotype.Component; + + +/** + * This is the converter from/to the Linkset in the DSpace API data model and the REST data model. + * + * @author Francesco Pio Scognamiglio (francescopio.scognamiglio at 4science.com) + */ +@Component +public class LinksetConverter implements DSpaceConverter { + + @Override + public LinksetRest convert(Linkset linkset, Projection projection) { + LinksetRest linksetRest = new LinksetRest(); + linksetRest.setProjection(projection); + linksetRest.getLinkset().add(linkset); + return linksetRest; + } + + @Override + public Class getModelClass() { + return Linkset.class; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/converter/LinksetHttpMessageConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/converter/LinksetHttpMessageConverter.java new file mode 100644 index 000000000000..0cc6628c3bc8 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/converter/LinksetHttpMessageConverter.java @@ -0,0 +1,81 @@ +/** + * 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.signposting.converter; + +import static java.lang.String.format; +import static org.apache.commons.lang.StringUtils.isNotBlank; + +import java.io.IOException; +import java.lang.reflect.Type; + +import org.apache.commons.lang.NotImplementedException; +import org.dspace.app.rest.signposting.model.LinksetRest; +import org.springframework.http.HttpInputMessage; +import org.springframework.http.HttpOutputMessage; +import org.springframework.http.MediaType; +import org.springframework.http.converter.AbstractGenericHttpMessageConverter; +import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.http.converter.HttpMessageNotWritableException; + +/** + * Converter for converting LinksetRest message into application/linkset format. + */ +public class LinksetHttpMessageConverter extends AbstractGenericHttpMessageConverter { + + public LinksetHttpMessageConverter() { + super(MediaType.valueOf("application/linkset")); + } + + @Override + protected void writeInternal(LinksetRest linksetRest, Type type, HttpOutputMessage outputMessage) + throws IOException, HttpMessageNotWritableException { + StringBuilder responseBody = new StringBuilder(); + linksetRest.getLset().forEach(lset -> { + if (isNotBlank(lset.getLink())) { + responseBody.append(format("<%s> ", lset.getLink())); + } + if (isNotBlank(lset.getRelation())) { + responseBody.append(format("; rel=\"%s\" ", lset.getRelation())); + } + if (isNotBlank(lset.getType())) { + responseBody.append(format("; type=\"%s\" ", lset.getType())); + } + if (isNotBlank(lset.getAnchor())) { + responseBody.append(format("; anchor=\"%s\" ", lset.getAnchor())); + } + responseBody.append(", "); + }); + outputMessage.getBody().write(responseBody.toString().trim().getBytes()); + outputMessage.getBody().flush(); + } + + @Override + protected LinksetRest readInternal(Class clazz, HttpInputMessage inputMessage) + throws HttpMessageNotReadableException { + throw new NotImplementedException(); + } + + @Override + protected boolean canRead(MediaType mediaType) { + return false; + } + + @Override + public boolean canWrite(Class clazz, MediaType mediaType) { + boolean isAppropriateClass = LinksetRest.class.isAssignableFrom(clazz); + boolean isAppropriateMediaType = getSupportedMediaTypes().stream() + .anyMatch(supportedType -> supportedType.isCompatibleWith(mediaType)); + return isAppropriateClass && isAppropriateMediaType; + } + + @Override + public LinksetRest read(Type type, Class contextClass, HttpInputMessage inputMessage) + throws IOException, HttpMessageNotReadableException { + throw new NotImplementedException(); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/converter/LsetConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/converter/LsetConverter.java new file mode 100644 index 000000000000..1e2e3884a434 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/converter/LsetConverter.java @@ -0,0 +1,37 @@ +/** + * 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.signposting.converter; + +import org.dspace.app.rest.converter.DSpaceConverter; +import org.dspace.app.rest.projection.Projection; +import org.dspace.app.rest.signposting.model.LinksetRest; +import org.dspace.app.rest.signposting.model.Lset; +import org.springframework.stereotype.Component; + + +/** + * This is the converter from/to the Lset in the DSpace API data model and the REST data model. + * + * @author Francesco Pio Scognamiglio (francescopio.scognamiglio at 4science.com) + */ +@Component +public class LsetConverter implements DSpaceConverter { + + @Override + public LinksetRest convert(Lset lset, Projection projection) { + LinksetRest linksetRest = new LinksetRest(); + linksetRest.setProjection(projection); + linksetRest.getLset().add(lset); + return linksetRest; + } + + @Override + public Class getModelClass() { + return Lset.class; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/hateoas/LinksetResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/hateoas/LinksetResource.java new file mode 100644 index 000000000000..8a0c2158d1ea --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/hateoas/LinksetResource.java @@ -0,0 +1,26 @@ +/** + * 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.signposting.hateoas; + +import org.dspace.app.rest.model.hateoas.DSpaceResource; +import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource; +import org.dspace.app.rest.signposting.model.LinksetRest; +import org.dspace.app.rest.utils.Utils; + +/** + * Linkset Rest HAL Resource. The HAL Resource wraps the REST Resource + * adding support for the links and embedded resources + * + * @author Francesco Pio Scognamiglio (francescopio.scognamiglio at 4science.com) + */ +@RelNameDSpaceResource(LinksetRest.NAME) +public class LinksetResource extends DSpaceResource { + public LinksetResource(LinksetRest linkset, Utils utils) { + super(linkset, utils); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/model/Linkset.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/model/Linkset.java new file mode 100644 index 000000000000..f97d7a5df39c --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/model/Linkset.java @@ -0,0 +1,127 @@ +/** + * 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.signposting.model; + +import java.util.ArrayList; +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * DTO object represents a set of links. + */ +public class Linkset { + + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private List author; + @JsonProperty("cite-as") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private List citeAs; + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private List item; + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private List collection; + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private List landingPage; + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private List type; + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private List license; + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private List linkset; + @JsonInclude(JsonInclude.Include.NON_NULL) + private String anchor; + + public List getAuthor() { + if (this.author == null) { + this.author = new ArrayList<>(); + } + return author; + } + public void setAuthor(List author) { + this.author = author; + } + + public List getCiteAs() { + if (this.citeAs == null) { + this.citeAs = new ArrayList<>(); + } + return citeAs; + } + public void setCiteAs(List citeAs) { + this.citeAs = citeAs; + } + + public List getItem() { + if (this.item == null) { + this.item = new ArrayList<>(); + } + return item; + } + public void setItem(List item) { + this.item = item; + } + + public List getCollection() { + if (this.collection == null) { + this.collection = new ArrayList<>(); + } + return collection; + } + public void setCollection(List collection) { + this.collection = collection; + } + + public List getLandingPage() { + if (landingPage == null) { + landingPage = new ArrayList<>(); + } + return landingPage; + } + public void setLandingPage(List landingPage) { + this.landingPage = landingPage; + } + + public List getType() { + if (type == null) { + type = new ArrayList<>(); + } + return type; + } + public void setType(List type) { + this.type = type; + } + + public List getLicense() { + if (license == null) { + license = new ArrayList<>(); + } + return license; + } + public void setLicense(List license) { + this.license = license; + } + + public List getLinkset() { + if (linkset == null) { + linkset = new ArrayList<>(); + } + return linkset; + } + public void setLinkset(List linkset) { + this.linkset = linkset; + } + + public String getAnchor() { + return anchor; + } + public void setAnchor(String anchor) { + this.anchor = anchor; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/model/LinksetRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/model/LinksetRest.java new file mode 100644 index 000000000000..6558d75d5851 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/model/LinksetRest.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.rest.signposting.model; + +import java.util.ArrayList; +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import org.dspace.app.rest.RestResourceController; +import org.dspace.app.rest.model.LinksRest; +import org.dspace.app.rest.model.RestAddressableModel; + +/** + * The REST object for the Linkset objects. + * + * @author Francesco Pio Scognamiglio (francescopio.scognamiglio at 4science.com) + */ +@LinksRest +public class LinksetRest extends RestAddressableModel { + public static final String NAME = "linkset"; + public static final String PLURAL_NAME = "linksets"; + public static final String CATEGORY = RestAddressableModel.CORE; + + public static final String JSON = "json"; + + @JsonInclude(Include.NON_EMPTY) + private List linkset; + @JsonInclude(Include.NON_EMPTY) + private List lset; + + public List getLinkset() { + if (this.linkset == null) { + this.linkset = new ArrayList<>(); + } + return linkset; + } + public void setLinkset(List linkset) { + this.linkset = linkset; + } + + public List getLset() { + if (this.lset == null) { + this.lset = new ArrayList<>(); + } + return lset; + } + public void setLset(List lset) { + this.lset = lset; + } + + @JsonIgnore + @Override + public String getType() { + return NAME; + } + + @Override + public String getCategory() { + return CATEGORY; + } + + @Override + public Class getController() { + return RestResourceController.class; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/model/Lset.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/model/Lset.java new file mode 100644 index 000000000000..dbfabfcb00ae --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/model/Lset.java @@ -0,0 +1,68 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.signposting.model; + +import com.fasterxml.jackson.annotation.JsonInclude; + +/** + * DTO object represents a node of a link set. + */ +public class Lset { + + @JsonInclude(JsonInclude.Include.NON_NULL) + private String link; + @JsonInclude(JsonInclude.Include.NON_NULL) + private String relation; + @JsonInclude(JsonInclude.Include.NON_NULL) + private String type; + @JsonInclude(JsonInclude.Include.NON_NULL) + private String anchor; + + public Lset(String link, String relation, String type, String anchor) { + this(link, relation, anchor); + this.type = type; + } + + public Lset(String link, String relation, String anchor) { + this.link = link; + this.relation = relation; + this.anchor = anchor; + } + + public String getLink() { + return link; + } + + public void setLink(String link) { + this.link = link; + } + + public String getRelation() { + return relation; + } + + public void setRelation(String relation) { + this.relation = relation; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getAnchor() { + return anchor; + } + + public void setAnchor(String anchor) { + this.anchor = anchor; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/model/Relation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/model/Relation.java new file mode 100644 index 000000000000..9b3139fe1ee6 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/model/Relation.java @@ -0,0 +1,34 @@ +/** + * 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.signposting.model; + +import com.fasterxml.jackson.annotation.JsonInclude; + +/** + * DTO object represents a relation to specific resource. + */ +public class Relation { + + @JsonInclude(JsonInclude.Include.NON_NULL) + private String href; + @JsonInclude(JsonInclude.Include.NON_NULL) + private String type; + + public Relation(String href, String type) { + this.href = href; + this.type = type; + } + + public String getHref() { + return href; + } + + public String getType() { + return type; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/model/TypedLinkRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/model/TypedLinkRest.java new file mode 100644 index 000000000000..2a6bcc4a012f --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/model/TypedLinkRest.java @@ -0,0 +1,97 @@ +/** + * 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.signposting.model; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonValue; +import org.dspace.app.rest.RestResourceController; +import org.dspace.app.rest.model.LinksRest; +import org.dspace.app.rest.model.RestAddressableModel; + +/** + * The REST object represents Typed Link. + */ +@LinksRest +@JsonInclude(JsonInclude.Include.NON_EMPTY) +public class TypedLinkRest extends RestAddressableModel { + public static final String NAME = "linkset"; + public static final String PLURAL_NAME = "linksets"; + public static final String CATEGORY = RestAddressableModel.CORE; + + private String href; + + private Relation rel; + + private String type; + + public TypedLinkRest() { + } + + public TypedLinkRest(String href, Relation rel, String type) { + this.href = href; + this.rel = rel; + this.type = type; + } + + public String getHref() { + return href; + } + + public void setHref(String href) { + this.href = href; + } + + public Relation getRel() { + return rel; + } + + public void setRel(Relation rel) { + this.rel = rel; + } + + public void setType(String type) { + this.type = type; + } + + @Override + public String getType() { + return type; + } + + @Override + public String getCategory() { + return CATEGORY; + } + + @Override + public Class getController() { + return RestResourceController.class; + } + + public enum Relation { + LANDING_PAGE("landing page"), + ITEM("item"), + CITE_AS("cite-as"), + AUTHOR("author"), + TYPE("type"), + LICENSE("license"), + COLLECTION("collection"), + LINKSET("linkset"); + + private final String name; + + Relation(String name) { + this.name = name; + } + + @JsonValue + public String getName() { + return name; + } + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/BitstreamSignPostingProcessor.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/BitstreamSignPostingProcessor.java new file mode 100644 index 000000000000..e80c3b4c39c2 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/BitstreamSignPostingProcessor.java @@ -0,0 +1,28 @@ +/** + * 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.signposting.processor; + +import org.dspace.content.Bitstream; +import org.dspace.services.ConfigurationService; +import org.dspace.services.factory.DSpaceServicesFactory; + +/** + * BitstreamSignPostingProcessor interface represents SignPostingProcessor for a bitstream. + * + * @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.com) + * @author Francesco Pio Scognamiglio (francescopio.scognamiglio at 4science.com) + */ +public interface BitstreamSignPostingProcessor extends SignPostingProcessor { + + default String buildAnchor(Bitstream bitstream) { + ConfigurationService configurationService = + DSpaceServicesFactory.getInstance().getConfigurationService(); + String url = configurationService.getProperty("dspace.ui.url"); + return url + "/bitstreams/" + bitstream.getID() + "/download"; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/ItemSignPostingProcessor.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/ItemSignPostingProcessor.java new file mode 100644 index 000000000000..2d4b21464cb3 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/ItemSignPostingProcessor.java @@ -0,0 +1,30 @@ +/** + * 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.signposting.processor; + +import java.sql.SQLException; + +import org.dspace.content.Item; +import org.dspace.core.Context; +import org.dspace.handle.factory.HandleServiceFactory; +import org.dspace.handle.service.HandleService; + +/** + * ItemSignPostingProcessor interface represents SignPostingProcessor for an item. + * + * @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.com) + * @author Francesco Pio Scognamiglio (francescopio.scognamiglio at 4science.com) + */ +public interface ItemSignPostingProcessor extends SignPostingProcessor { + + default String buildAnchor(Context context, Item item) throws SQLException { + HandleService handleService = + HandleServiceFactory.getInstance().getHandleService(); + return handleService.resolveToURL(context, item.getHandle()); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/SignPostingProcessor.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/SignPostingProcessor.java new file mode 100644 index 000000000000..aab13b6626b1 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/SignPostingProcessor.java @@ -0,0 +1,30 @@ +/** + * 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.signposting.processor; + +import java.util.List; +import javax.servlet.http.HttpServletRequest; + +import org.dspace.app.rest.signposting.model.Linkset; +import org.dspace.app.rest.signposting.model.Lset; +import org.dspace.content.DSpaceObject; +import org.dspace.core.Context; + +/** + * SignPostingProcessor interface. + * + * @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.com) + * @author Francesco Pio Scognamiglio (francescopio.scognamiglio at 4science.com) + */ +public interface SignPostingProcessor { + void buildRelation(Context context, HttpServletRequest request, + T object, List linksets, Linkset primaryLinkset); + + void buildLset(Context context, HttpServletRequest request, + T object, List lsets); +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/relation/ASignPostingProcessor.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/relation/ASignPostingProcessor.java new file mode 100644 index 000000000000..22ae0a72b704 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/relation/ASignPostingProcessor.java @@ -0,0 +1,48 @@ +/** + * 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.signposting.relation; + +/** + * An abstract class of generic signposting relation. + * + * @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.com) + * @author Francesco Pio Scognamiglio (francescopio.scognamiglio at 4science.com) + */ +public abstract class ASignPostingProcessor { + + private String metadataField; + + private String relation; + + private String pattern; + + public String getMetadataField() { + return metadataField; + } + + public void setMetadataField(String metadataField) { + this.metadataField = metadataField; + } + + public String getRelation() { + return relation; + } + + public void setRelation(String relation) { + this.relation = relation; + } + + public String getPattern() { + return pattern; + } + + public void setPattern(String pattern) { + this.pattern = pattern; + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/relation/BitstreamLicenseProcessor.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/relation/BitstreamLicenseProcessor.java new file mode 100644 index 000000000000..99439770417c --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/relation/BitstreamLicenseProcessor.java @@ -0,0 +1,74 @@ +/** + * 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.signposting.relation; + +import java.text.MessageFormat; +import java.util.List; +import javax.servlet.http.HttpServletRequest; + +import org.apache.commons.lang.StringUtils; +import org.apache.log4j.Logger; +import org.dspace.app.rest.signposting.model.Linkset; +import org.dspace.app.rest.signposting.model.Lset; +import org.dspace.app.rest.signposting.model.Relation; +import org.dspace.app.rest.signposting.processor.BitstreamSignPostingProcessor; +import org.dspace.app.rest.signposting.processor.ItemSignPostingProcessor; +import org.dspace.content.Bitstream; +import org.dspace.content.service.BitstreamService; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * An implementation of {@link ItemSignPostingProcessor} for the license relation. + */ +public class BitstreamLicenseProcessor extends ASignPostingProcessor + implements BitstreamSignPostingProcessor { + + private static Logger log = Logger.getLogger(BitstreamLicenseProcessor.class); + + @Autowired + private BitstreamService bitstreamService; + + public BitstreamLicenseProcessor() { + setRelation("license"); + } + + @Override + public void buildRelation(Context context, HttpServletRequest request, + Bitstream bitstream, List linksets, + Linkset primaryLinkset) { + try { + String license = bitstreamService.getMetadata(bitstream, getMetadataField()); + if (StringUtils.isNotBlank(license)) { + if (StringUtils.isNotBlank(getPattern())) { + license = MessageFormat.format(getPattern(), license); + } + primaryLinkset.getLicense().add(new Relation(license, null)); + } + } catch (Exception e) { + log.error(e.getMessage(), e); + } + } + + @Override + public void buildLset(Context context, HttpServletRequest request, + Bitstream bitstream, List lsets) { + try { + String license = bitstreamService.getMetadata(bitstream, getMetadataField()); + if (StringUtils.isNotBlank(license)) { + if (StringUtils.isNotBlank(getPattern())) { + license = MessageFormat.format(getPattern(), license); + } + lsets.add(new Lset(license, getRelation(), buildAnchor(bitstream))); + } + } catch (Exception e) { + log.error(e.getMessage(), e); + } + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/relation/BitstreamLinksetProcessor.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/relation/BitstreamLinksetProcessor.java new file mode 100644 index 000000000000..3c13b767ee0c --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/relation/BitstreamLinksetProcessor.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.app.rest.signposting.relation; + +import java.util.List; +import javax.servlet.http.HttpServletRequest; + +import org.apache.log4j.Logger; +import org.dspace.app.rest.signposting.model.Linkset; +import org.dspace.app.rest.signposting.model.Lset; +import org.dspace.app.rest.signposting.model.Relation; +import org.dspace.app.rest.signposting.processor.BitstreamSignPostingProcessor; +import org.dspace.app.rest.signposting.processor.ItemSignPostingProcessor; +import org.dspace.content.Bitstream; +import org.dspace.content.Item; +import org.dspace.content.service.BitstreamService; +import org.dspace.core.Context; +import org.dspace.services.ConfigurationService; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * An implementation of {@link ItemSignPostingProcessor} for the linkset relation. + */ +public class BitstreamLinksetProcessor extends ASignPostingProcessor + implements BitstreamSignPostingProcessor { + + private static Logger log = Logger.getLogger(BitstreamLinksetProcessor.class); + + @Autowired + private BitstreamService bitstreamService; + + @Autowired + private ConfigurationService configurationService; + + public BitstreamLinksetProcessor() { + setRelation("linkset"); + } + + @Override + public void buildRelation(Context context, HttpServletRequest request, + Bitstream bitstream, List linksets, + Linkset primaryLinkset) { + try { + Item item = (Item) bitstreamService.getParentObject(context, bitstream); + if (item != null) { + String baseUrl = configurationService.getProperty("dspace.server.url"); + String linksetUrl = baseUrl + "/signposting/linksets/" + item.getID(); + String linksetJsonUrl = baseUrl + "/signposting/linksets/" + item.getID() + "/json"; + List relations = List.of( + new Relation(linksetUrl, "application/linkset"), + new Relation(linksetJsonUrl, "application/linkset+json") + ); + primaryLinkset.getLinkset().addAll(relations); + } + } catch (Exception e) { + log.error(e.getMessage(), e); + } + } + + @Override + public void buildLset(Context context, HttpServletRequest request, + Bitstream bitstream, List lsets) { + try { + Item item = (Item) bitstreamService.getParentObject(context, bitstream); + if (item != null) { + String baseUrl = configurationService.getProperty("dspace.server.url"); + String linksetUrl = baseUrl + "/signposting/linksets/" + item.getID(); + String linksetJsonUrl = baseUrl + "/signposting/linksets/" + item.getID() + "/json"; + List links = List.of( + new Lset(linksetUrl, getRelation(), "application/linkset", buildAnchor(bitstream)), + new Lset(linksetJsonUrl, getRelation(), "application/linkset+json", buildAnchor(bitstream)) + ); + lsets.addAll(links); + } + } catch (Exception e) { + log.error(e.getMessage(), e); + } + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/relation/BitstreamPublicationBundaryProcessor.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/relation/BitstreamPublicationBundaryProcessor.java new file mode 100644 index 000000000000..9d86856d6f90 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/relation/BitstreamPublicationBundaryProcessor.java @@ -0,0 +1,76 @@ +/** + * 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.signposting.relation; + +import java.util.List; +import javax.servlet.http.HttpServletRequest; + +import org.apache.log4j.Logger; +import org.dspace.app.rest.signposting.model.Linkset; +import org.dspace.app.rest.signposting.model.Lset; +import org.dspace.app.rest.signposting.model.Relation; +import org.dspace.app.rest.signposting.processor.BitstreamSignPostingProcessor; +import org.dspace.app.rest.signposting.processor.ItemSignPostingProcessor; +import org.dspace.content.Bitstream; +import org.dspace.content.Item; +import org.dspace.content.service.BitstreamService; +import org.dspace.core.Context; +import org.dspace.util.FrontendUrlService; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * An implementation of {@link ItemSignPostingProcessor} for the collection relation. + * + * @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.com) + * @author Francesco Pio Scognamiglio (francescopio.scognamiglio at 4science.com) + */ +public class BitstreamPublicationBundaryProcessor extends ASignPostingProcessor + implements BitstreamSignPostingProcessor { + + private static Logger log = Logger.getLogger(BitstreamPublicationBundaryProcessor.class); + + @Autowired + private BitstreamService bitstreamService; + + @Autowired + private FrontendUrlService frontendUrlService; + + public BitstreamPublicationBundaryProcessor() { + setRelation("collection"); + } + + @Override + public void buildRelation(Context context, HttpServletRequest request, + Bitstream bitstream, List linksets, + Linkset primaryLinkset) { + try { + Item item = (Item) bitstreamService.getParentObject(context, bitstream); + if (item != null) { + String itemUiUrl = frontendUrlService.generateUrl(item); + primaryLinkset.getCollection().add(new Relation(itemUiUrl, "text/html")); + } + } catch (Exception e) { + log.error(e.getMessage(), e); + } + } + + @Override + public void buildLset(Context context, HttpServletRequest request, + Bitstream bitstream, List lsets) { + try { + Item item = (Item) bitstreamService.getParentObject(context, bitstream); + if (item != null) { + String itemUiUrl = frontendUrlService.generateUrl(item); + lsets.add(new Lset(itemUiUrl, getRelation(), "text/html", buildAnchor(bitstream))); + } + } catch (Exception e) { + log.error(e.getMessage(), e); + } + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/relation/BitstreamTypeProcessor.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/relation/BitstreamTypeProcessor.java new file mode 100644 index 000000000000..5c4569e4c165 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/relation/BitstreamTypeProcessor.java @@ -0,0 +1,74 @@ +/** + * 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.signposting.relation; + +import java.text.MessageFormat; +import java.util.List; +import javax.servlet.http.HttpServletRequest; + +import org.apache.commons.lang.StringUtils; +import org.apache.log4j.Logger; +import org.dspace.app.rest.signposting.model.Linkset; +import org.dspace.app.rest.signposting.model.Lset; +import org.dspace.app.rest.signposting.model.Relation; +import org.dspace.app.rest.signposting.processor.BitstreamSignPostingProcessor; +import org.dspace.app.rest.signposting.processor.ItemSignPostingProcessor; +import org.dspace.content.Bitstream; +import org.dspace.content.service.BitstreamService; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * An implementation of {@link ItemSignPostingProcessor} for the type relation. + */ +public class BitstreamTypeProcessor extends ASignPostingProcessor + implements BitstreamSignPostingProcessor { + + private static Logger log = Logger.getLogger(BitstreamTypeProcessor.class); + + @Autowired + private BitstreamService bitstreamService; + + public BitstreamTypeProcessor() { + setRelation("type"); + } + + @Override + public void buildRelation(Context context, HttpServletRequest request, + Bitstream bitstream, List linksets, + Linkset primaryLinkset) { + try { + String type = bitstreamService.getMetadata(bitstream, getMetadataField()); + if (StringUtils.isNotBlank(type)) { + if (StringUtils.isNotBlank(getPattern())) { + type = MessageFormat.format(getPattern(), type); + } + primaryLinkset.getType().add(new Relation(type, null)); + } + } catch (Exception e) { + log.error(e.getMessage(), e); + } + } + + @Override + public void buildLset(Context context, HttpServletRequest request, + Bitstream bitstream, List lsets) { + try { + String type = bitstreamService.getMetadata(bitstream, getMetadataField()); + if (StringUtils.isNotBlank(type)) { + if (StringUtils.isNotBlank(getPattern())) { + type = MessageFormat.format(getPattern(), type); + } + lsets.add(new Lset(type, getRelation(), buildAnchor(bitstream))); + } + } catch (Exception e) { + log.error(e.getMessage(), e); + } + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/relation/ItemAuthorProcessor.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/relation/ItemAuthorProcessor.java new file mode 100644 index 000000000000..bdaecd41f201 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/relation/ItemAuthorProcessor.java @@ -0,0 +1,109 @@ +/** + * 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.signposting.relation; + +import static org.dspace.content.Item.ANY; +import static org.dspace.content.MetadataSchemaEnum.PERSON; + +import java.util.List; +import java.util.UUID; +import javax.servlet.http.HttpServletRequest; + +import org.apache.commons.lang3.StringUtils; +import org.apache.log4j.Logger; +import org.dspace.app.rest.signposting.model.Linkset; +import org.dspace.app.rest.signposting.model.Lset; +import org.dspace.app.rest.signposting.model.Relation; +import org.dspace.app.rest.signposting.processor.ItemSignPostingProcessor; +import org.dspace.content.Item; +import org.dspace.content.MetadataSchemaEnum; +import org.dspace.content.MetadataValue; +import org.dspace.content.service.ItemService; +import org.dspace.core.Context; +import org.dspace.util.FrontendUrlService; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * An implementation of {@link ItemSignPostingProcessor} for the author relation. + * + * @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.com) + * @author Francesco Pio Scognamiglio (francescopio.scognamiglio at 4science.com) + */ +public class ItemAuthorProcessor extends ASignPostingProcessor + implements ItemSignPostingProcessor { + + /** log4j category */ + private static Logger log = Logger.getLogger(ItemAuthorProcessor.class); + + @Autowired + private ItemService itemService; + + @Autowired + private FrontendUrlService frontendUrlService; + + private String retrievedExternally; + + public String getRetrievedExternally() { + return retrievedExternally; + } + + public void setRetrievedExternally(String retrievedExternally) { + this.retrievedExternally = retrievedExternally; + } + + public ItemAuthorProcessor() { + setRelation("author"); + } + + @Override + public void buildRelation(Context context, HttpServletRequest request, + Item item, List linksets, Linkset primaryLinkset) { + try { + List authors = itemService + .getMetadata(item, MetadataSchemaEnum.DC.getName(), "contributor", ANY, ANY); + for (MetadataValue author : authors) { + if (author.getAuthority() != null) { + String authorUuid = author.getAuthority(); + Item authorItem = itemService.find(context, UUID.fromString(authorUuid)); + String authorOrcid = itemService + .getMetadataFirstValue(authorItem, PERSON.getName(), "identifier", "orcid", ANY); + if (StringUtils.isNotBlank(authorOrcid)) { + String href = frontendUrlService.generateUrl(authorItem); + primaryLinkset.getAuthor().add(new Relation(href, authorOrcid)); + } + } + } + } catch (Exception e) { + log.error("Problem to add signposting pattern", e); + } + } + + @Override + public void buildLset(Context context, HttpServletRequest request, + Item item, List lsets) { + try { + List authors = itemService + .getMetadata(item, MetadataSchemaEnum.DC.getName(), "contributor", ANY, ANY); + for (MetadataValue author : authors) { + if (author.getAuthority() != null) { + String authorUuid = author.getAuthority(); + Item authorItem = itemService.find(context, UUID.fromString(authorUuid)); + String authorOrcid = itemService + .getMetadataFirstValue(authorItem, PERSON.getName(), "identifier", "orcid", ANY); + if (StringUtils.isNotBlank(authorOrcid)) { + String href = frontendUrlService.generateUrl(authorItem); + lsets.add(new Lset(href, getRelation(), authorOrcid, buildAnchor(context, item))); + } + } + } + } catch (Exception e) { + log.error("Problem to add signposting pattern", e); + } + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/relation/ItemIdentifierProcessor.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/relation/ItemIdentifierProcessor.java new file mode 100644 index 000000000000..5f5f5d86b6a7 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/relation/ItemIdentifierProcessor.java @@ -0,0 +1,89 @@ +/** + * 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.signposting.relation; + +import java.text.MessageFormat; +import java.util.List; +import javax.servlet.http.HttpServletRequest; + +import org.apache.commons.lang3.StringUtils; +import org.apache.log4j.Logger; +import org.dspace.app.rest.signposting.model.Linkset; +import org.dspace.app.rest.signposting.model.Lset; +import org.dspace.app.rest.signposting.model.Relation; +import org.dspace.app.rest.signposting.processor.ItemSignPostingProcessor; +import org.dspace.content.Item; +import org.dspace.content.MetadataValue; +import org.dspace.content.service.ItemService; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * An implementation of {@link ItemSignPostingProcessor} for the author relation. + * + * @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.com) + * @author Francesco Pio Scognamiglio (francescopio.scognamiglio at 4science.com) + */ +public class ItemIdentifierProcessor extends ASignPostingProcessor + implements ItemSignPostingProcessor { + + /** + * log4j category + */ + private static Logger log = Logger.getLogger(ItemIdentifierProcessor.class); + + @Autowired + private ItemService itemService; + + public ItemIdentifierProcessor() { + setRelation("cite-as"); + } + + @Override + public void buildRelation(Context context, HttpServletRequest request, + Item item, List linksets, Linkset primaryLinkset) { + try { + List identifiers = itemService.getMetadataByMetadataString(item, getMetadataField()); + for (MetadataValue identifier : identifiers) { + if (identifier != null) { + String identifierValue = identifier.getValue(); + if (StringUtils.isNotBlank(identifierValue)) { + if (StringUtils.isNotBlank(getPattern())) { + identifierValue = MessageFormat.format(getPattern(), identifierValue); + } + primaryLinkset.getCiteAs().add(new Relation(identifierValue, null)); + } + } + } + } catch (Exception e) { + log.error(e.getMessage(), e); + } + } + + @Override + public void buildLset(Context context, HttpServletRequest request, + Item item, List lsets) { + try { + List identifiers = itemService.getMetadataByMetadataString(item, getMetadataField()); + for (MetadataValue identifier : identifiers) { + if (identifier != null) { + String identifierValue = identifier.getValue(); + if (StringUtils.isNotBlank(identifierValue)) { + if (StringUtils.isNotBlank(getPattern())) { + identifierValue = MessageFormat.format(getPattern(), identifierValue); + } + lsets.add(new Lset(identifierValue, getRelation(), buildAnchor(context, item))); + } + } + } + } catch (Exception e) { + log.error(e.getMessage(), e); + } + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/relation/ItemLandingPageProcessor.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/relation/ItemLandingPageProcessor.java new file mode 100644 index 000000000000..8c048a9f7c40 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/relation/ItemLandingPageProcessor.java @@ -0,0 +1,59 @@ +/** + * 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.signposting.relation; + +import java.util.List; +import javax.servlet.http.HttpServletRequest; + +import org.apache.log4j.Logger; +import org.dspace.app.rest.signposting.model.Linkset; +import org.dspace.app.rest.signposting.model.Lset; +import org.dspace.app.rest.signposting.model.Relation; +import org.dspace.app.rest.signposting.model.TypedLinkRest; +import org.dspace.app.rest.signposting.processor.ItemSignPostingProcessor; +import org.dspace.content.Item; +import org.dspace.core.Context; +import org.dspace.util.FrontendUrlService; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * An implementation of {@link ItemSignPostingProcessor} for the Landing Page relation. + */ +public class ItemLandingPageProcessor extends ASignPostingProcessor implements ItemSignPostingProcessor { + + private static Logger log = Logger.getLogger(ItemLandingPageProcessor.class); + + @Autowired + private FrontendUrlService frontendUrlService; + + public ItemLandingPageProcessor() { + setRelation(TypedLinkRest.Relation.LANDING_PAGE.getName()); + } + + @Override + public void buildRelation(Context context, HttpServletRequest request, + Item item, List linksets, Linkset primaryLinkset) { + try { + String landingPageUrl = frontendUrlService.generateUrl(item); + primaryLinkset.getLandingPage().add(new Relation(landingPageUrl, "text/html")); + } catch (Exception e) { + log.error(e.getMessage(), e); + } + } + + @Override + public void buildLset(Context context, HttpServletRequest request, Item item, List lsets) { + try { + String landingPageUrl = frontendUrlService.generateUrl(item); + lsets.add(new Lset(landingPageUrl, getRelation(), "text/html", buildAnchor(context, item))); + } catch (Exception e) { + log.error(e.getMessage(), e); + } + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/relation/ItemLicenseProcessor.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/relation/ItemLicenseProcessor.java new file mode 100644 index 000000000000..90177f8c2a93 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/relation/ItemLicenseProcessor.java @@ -0,0 +1,79 @@ +/** + * 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.signposting.relation; + +import java.text.MessageFormat; +import java.util.List; +import javax.servlet.http.HttpServletRequest; + +import org.apache.commons.lang3.StringUtils; +import org.apache.log4j.Logger; +import org.dspace.app.rest.signposting.model.Linkset; +import org.dspace.app.rest.signposting.model.Lset; +import org.dspace.app.rest.signposting.model.Relation; +import org.dspace.app.rest.signposting.processor.ItemSignPostingProcessor; +import org.dspace.content.Item; +import org.dspace.content.MetadataFieldName; +import org.dspace.content.service.ItemService; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * An implementation of {@link ItemSignPostingProcessor} for the license relation. + */ +public class ItemLicenseProcessor extends ASignPostingProcessor + implements ItemSignPostingProcessor { + + private static Logger log = Logger.getLogger(ItemLicenseProcessor.class); + + @Autowired + private ItemService itemService; + + public ItemLicenseProcessor() { + setRelation("license"); + } + + @Override + public void buildRelation(Context context, HttpServletRequest request, + Item item, List linksets, Linkset primaryLinkset) { + try { + if (StringUtils.isNotEmpty(getMetadataField())) { + String license = itemService + .getMetadataFirstValue(item, new MetadataFieldName(getMetadataField()), Item.ANY); + if (StringUtils.isNotBlank(license)) { + if (StringUtils.isNotBlank(getPattern())) { + license = MessageFormat.format(getPattern(), license); + } + primaryLinkset.getLicense().add(new Relation(license, null)); + } + } + } catch (Exception e) { + log.error(e.getMessage(), e); + } + } + + @Override + public void buildLset(Context context, HttpServletRequest request, + Item item, List lsets) { + try { + if (StringUtils.isNotEmpty(getMetadataField())) { + String license = itemService + .getMetadataFirstValue(item, new MetadataFieldName(getMetadataField()), Item.ANY); + if (StringUtils.isNotBlank(license)) { + if (StringUtils.isNotBlank(getPattern())) { + license = MessageFormat.format(getPattern(), license); + } + lsets.add(new Lset(license, getRelation(), buildAnchor(context, item))); + } + } + } catch (Exception e) { + log.error(e.getMessage(), e); + } + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/relation/ItemPublicationBundaryProcessor.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/relation/ItemPublicationBundaryProcessor.java new file mode 100644 index 000000000000..265b991521ae --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/relation/ItemPublicationBundaryProcessor.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.signposting.relation; + +import java.sql.SQLException; +import java.text.MessageFormat; +import java.util.List; +import javax.servlet.http.HttpServletRequest; + +import org.apache.log4j.Logger; +import org.dspace.app.rest.signposting.model.Linkset; +import org.dspace.app.rest.signposting.model.Lset; +import org.dspace.app.rest.signposting.model.Relation; +import org.dspace.app.rest.signposting.processor.ItemSignPostingProcessor; +import org.dspace.content.Bitstream; +import org.dspace.content.Bundle; +import org.dspace.content.Item; +import org.dspace.core.Constants; +import org.dspace.core.Context; +import org.dspace.services.ConfigurationService; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * An implementation of {@link ItemSignPostingProcessor} for the item relation. + * + * @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.com) + * @author Francesco Pio Scognamiglio (francescopio.scognamiglio at 4science.com) + */ +public class ItemPublicationBundaryProcessor extends ASignPostingProcessor + implements ItemSignPostingProcessor { + + /** + * log4j category + */ + private static Logger log = Logger + .getLogger(ItemPublicationBundaryProcessor.class); + + @Autowired + private ConfigurationService configurationService; + + public ItemPublicationBundaryProcessor() { + setRelation("item"); + } + + @Override + public void buildRelation(Context context, HttpServletRequest request, + Item item, List linksets, Linkset primaryLinkset) { + String url = configurationService.getProperty("dspace.ui.url"); + try { + for (Bundle bundle : item.getBundles(Constants.CONTENT_BUNDLE_NAME)) { + for (Bitstream bitstream : bundle.getBitstreams()) { + String mimeType = bitstream.getFormat(context).getMIMEType(); + primaryLinkset.getItem().add( + new Relation( + MessageFormat.format(getPattern(), + url, "bitstreams", bitstream.getID() + "/download"), mimeType)); + } + } + } catch (SQLException e) { + log.error(e.getMessage(), e); + } + } + + @Override + public void buildLset(Context context, HttpServletRequest request, + Item item, List lsets) { + String url = configurationService.getProperty("dspace.ui.url"); + try { + for (Bundle bundle : item.getBundles(Constants.CONTENT_BUNDLE_NAME)) { + for (Bitstream bitstream : bundle.getBitstreams()) { + String mimeType = bitstream.getFormat(context).getMIMEType(); + lsets.add(new Lset(MessageFormat.format(getPattern(), + url, "bitstreams", bitstream.getID() + "/download"), + getRelation(), mimeType, buildAnchor(context, item))); + } + } + } catch (SQLException e) { + log.error(e.getMessage(), e); + } + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/relation/ItemTypeProcessor.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/relation/ItemTypeProcessor.java new file mode 100644 index 000000000000..1d5ecb0a8992 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/relation/ItemTypeProcessor.java @@ -0,0 +1,79 @@ +/** + * 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.signposting.relation; + +import java.text.MessageFormat; +import java.util.List; +import javax.servlet.http.HttpServletRequest; + +import org.apache.commons.lang3.StringUtils; +import org.apache.log4j.Logger; +import org.dspace.app.rest.signposting.model.Linkset; +import org.dspace.app.rest.signposting.model.Lset; +import org.dspace.app.rest.signposting.model.Relation; +import org.dspace.app.rest.signposting.processor.ItemSignPostingProcessor; +import org.dspace.content.Item; +import org.dspace.content.MetadataFieldName; +import org.dspace.content.service.ItemService; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * An implementation of {@link ItemSignPostingProcessor} for the type relation. + */ +public class ItemTypeProcessor extends ASignPostingProcessor + implements ItemSignPostingProcessor { + + private static Logger log = Logger.getLogger(ItemTypeProcessor.class); + + @Autowired + private ItemService itemService; + + public ItemTypeProcessor() { + setRelation("type"); + } + + @Override + public void buildRelation(Context context, HttpServletRequest request, + Item item, List linksets, Linkset primaryLinkset) { + try { + if (StringUtils.isNotBlank(getMetadataField())) { + String itemType = itemService + .getMetadataFirstValue(item, new MetadataFieldName(getMetadataField()), Item.ANY); + if (StringUtils.isNotBlank(itemType)) { + if (StringUtils.isNotBlank(getPattern())) { + itemType = MessageFormat.format(getPattern(), itemType); + } + primaryLinkset.getType().add(new Relation(itemType, null)); + } + } + } catch (Exception e) { + log.error(e.getMessage(), e); + } + } + + @Override + public void buildLset(Context context, HttpServletRequest request, + Item item, List lsets) { + try { + if (StringUtils.isNotEmpty(getMetadataField())) { + String itemType = itemService + .getMetadataFirstValue(item, new MetadataFieldName(getMetadataField()), Item.ANY); + if (StringUtils.isNotBlank(itemType)) { + if (StringUtils.isNotBlank(getPattern())) { + itemType = MessageFormat.format(getPattern(), itemType); + } + lsets.add(new Lset(itemType, getRelation(), buildAnchor(context, item))); + } + } + } catch (Exception e) { + log.error(e.getMessage(), e); + } + } + +} 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 c2136781f927..369d56481bc7 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 @@ -35,6 +35,11 @@ public class ApplicationConfig { @Value("${iiif.cors.allowed-origins}") private String[] iiifCorsAllowedOrigins; + // Allowed Signposting CORS origins ("Access-Control-Allow-Origin" header) + // Can be overridden in DSpace configuration + @Value("${signposting.cors.allowed-origins}") + private String[] signpostingCorsAllowedOrigins; + // Whether to allow credentials (cookies) in CORS requests ("Access-Control-Allow-Credentials" header) // Defaults to true. Can be overridden in DSpace configuration @Value("${rest.cors.allow-credentials:true}") @@ -45,6 +50,11 @@ public class ApplicationConfig { @Value("${iiif.cors.allow-credentials:true}") private boolean iiifCorsAllowCredentials; + // Whether to allow credentials (cookies) in CORS requests ("Access-Control-Allow-Credentials" header) + // Defaults to true. Can be overridden in DSpace configuration + @Value("${signposting.cors.allow-credentials:true}") + private boolean signpostingCorsAllowCredentials; + // Configured User Interface URL (default: http://localhost:4000) @Value("${dspace.ui.url:http://localhost:4000}") private String uiURL; @@ -90,6 +100,14 @@ public String[] getIiifAllowedOriginsConfig() { return this.iiifCorsAllowedOrigins; } + /** + * Returns the signposting.cors.allowed-origins (for Signposting access) defined in DSpace configuration. + * @return allowed origins + */ + public String[] getSignpostingAllowedOriginsConfig() { + return this.signpostingCorsAllowedOrigins; + } + /** * Return whether to allow credentials (cookies) on CORS requests. This is used to set the * CORS "Access-Control-Allow-Credentials" header in Application class. @@ -107,4 +125,13 @@ public boolean getCorsAllowCredentials() { public boolean getIiifAllowCredentials() { return iiifCorsAllowCredentials; } + + /** + * Return whether to allow credentials (cookies) on Signposting requests. This is used to set the + * CORS "Access-Control-Allow-Credentials" header in Application class. Defaults to false. + * @return true or false + */ + public boolean getSignpostingAllowCredentials() { + return signpostingCorsAllowCredentials; + } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/signposting/controller/LinksetRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/signposting/controller/LinksetRestControllerIT.java new file mode 100644 index 000000000000..13f578650516 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/signposting/controller/LinksetRestControllerIT.java @@ -0,0 +1,336 @@ +/** + * 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.signposting.controller; + +import static org.dspace.content.MetadataSchemaEnum.PERSON; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.io.InputStream; +import java.text.MessageFormat; + +import org.apache.commons.codec.CharEncoding; +import org.apache.commons.io.IOUtils; +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.builder.BitstreamBuilder; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.ItemBuilder; +import org.dspace.content.Bitstream; +import org.dspace.content.Collection; +import org.dspace.content.Item; +import org.dspace.content.authority.Choices; +import org.dspace.content.authority.service.ChoiceAuthorityService; +import org.dspace.content.authority.service.MetadataAuthorityService; +import org.dspace.services.ConfigurationService; +import org.dspace.services.factory.DSpaceServicesFactory; +import org.hamcrest.Matchers; +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +public class LinksetRestControllerIT extends AbstractControllerIntegrationTest { + + private static final String doiPattern = "https://doi.org/{0}"; + private static final String doi = "10.1007/978-3-642-35233-1_18"; + + private Collection collection; + + @Autowired + private ConfigurationService configurationService; + + @Autowired + MetadataAuthorityService metadataAuthorityService; + + @Autowired + ChoiceAuthorityService choiceAuthorityService; + + @Before + @Override + public void setUp() throws Exception { + super.setUp(); + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + collection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection") + .withEntityType("Publication") + .build(); + context.restoreAuthSystemState(); + } + + @Test + public void findAllItemsLinksets() throws Exception { + getClient().perform(get("/signposting")) + .andExpect(status().isMethodNotAllowed()); + } + + @Test + public void findOneItemGenericLinksets() throws Exception { + context.turnOffAuthorisationSystem(); + Item item = ItemBuilder.createItem(context, collection) + .withTitle("Item Test") + .withMetadata("dc", "identifier", "doi", doi) + .build(); + context.restoreAuthSystemState(); + + getClient().perform(get("/signposting/linksets/" + item.getID())) + .andExpect(status().isMethodNotAllowed()); + } + + @Test + public void findOneItemJsonLinksets() throws Exception { + context.turnOffAuthorisationSystem(); + Item item = ItemBuilder.createItem(context, collection) + .withTitle("Item Test") + .withMetadata("dc", "identifier", "doi", doi) + .build(); + context.restoreAuthSystemState(); + + getClient().perform(get("/signposting/linksets/" + item.getID() + "/json")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.linkset", + Matchers.hasSize(1))) + .andExpect(jsonPath("$.linkset[0].cite-as[0].href", + Matchers.hasToString(MessageFormat.format(doiPattern, doi)))); + } + + @Test + public void findOneItemJsonLinksetsWithBitstreams() throws Exception { + String bitstream1Content = "ThisIsSomeDummyText"; + String bitstream1MimeType = "text/plain"; + String bitstream2Content = "ThisIsSomeAlternativeDummyText"; + String bitstream2MimeType = "application/pdf"; + + context.turnOffAuthorisationSystem(); + Item item = ItemBuilder.createItem(context, collection) + .withTitle("Item Test") + .withMetadata("dc", "identifier", "doi", doi) + .build(); + Bitstream bitstream1 = null; + try (InputStream is = IOUtils.toInputStream(bitstream1Content, CharEncoding.UTF_8)) { + bitstream1 = BitstreamBuilder.createBitstream(context, item, is) + .withName("Bitstream 1") + .withDescription("description") + .withMimeType(bitstream1MimeType) + .build(); + } + Bitstream bitstream2 = null; + try (InputStream is = IOUtils.toInputStream(bitstream2Content, CharEncoding.UTF_8)) { + bitstream2 = BitstreamBuilder.createBitstream(context, item, is) + .withName("Bitstream 2") + .withDescription("description") + .withMimeType(bitstream2MimeType) + .build(); + } + context.restoreAuthSystemState(); + + String url = configurationService.getProperty("dspace.ui.url"); + getClient().perform(get("/signposting/linksets/" + item.getID() + "/json") + .header("Accept", "application/linkset+json")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.linkset", + Matchers.hasSize(1))) + .andExpect(jsonPath("$.linkset[0].cite-as[0].href", + Matchers.hasToString(MessageFormat.format(doiPattern, doi)))) + .andExpect(jsonPath("$.linkset[0].item[0].href", + Matchers.hasToString(url + "/bitstreams/" + bitstream1.getID() + "/download"))) + .andExpect(jsonPath("$.linkset[0].item[0].type", + Matchers.hasToString(bitstream1MimeType))) + .andExpect(jsonPath("$.linkset[0].item[1].href", + Matchers.hasToString(url + "/bitstreams/" + bitstream2.getID() + "/download"))) + .andExpect(jsonPath("$.linkset[0].item[1].type", + Matchers.hasToString(bitstream2MimeType))) + .andExpect(jsonPath("$.linkset[0].anchor", + Matchers.hasToString(url + "/handle/" + item.getHandle()))) + .andExpect(jsonPath("$.linkset[0].landingPage[0].href", + Matchers.hasToString(url + "/entities/publication/" + item.getID()))) + .andExpect(jsonPath("$.linkset[0].landingPage[0].type", + Matchers.hasToString("text/html"))); + } + + @Test + public void findOneBitstreamJsonLinksets() throws Exception { + String bitstreamContent = "ThisIsSomeDummyText"; + String bitstreamMimeType = "text/plain"; + + context.turnOffAuthorisationSystem(); + Item item = ItemBuilder.createItem(context, collection) + .withTitle("Item Test") + .withMetadata("dc", "identifier", "doi", doi) + .build(); + + Bitstream bitstream = null; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + bitstream = BitstreamBuilder.createBitstream(context, item, is) + .withName("Bitstream") + .withDescription("description") + .withMimeType(bitstreamMimeType) + .build(); + } + context.restoreAuthSystemState(); + + getClient().perform(get("/signposting/linksets/" + bitstream.getID() + "/json") + .header("Accept", "application/linkset+json")) + .andExpect(status().isNotFound()); + } + + @Test + public void findOneItemLsetLinksets() throws Exception { + String bitstream1Content = "ThisIsSomeDummyText"; + String bitstream1MimeType = "text/plain"; + + context.turnOffAuthorisationSystem(); + Item item = ItemBuilder.createItem(context, collection) + .withTitle("Item Test") + .withMetadata("dc", "identifier", "doi", doi) + .build(); + Bitstream bitstream1 = null; + try (InputStream is = IOUtils.toInputStream(bitstream1Content, CharEncoding.UTF_8)) { + bitstream1 = BitstreamBuilder.createBitstream(context, item, is) + .withName("Bitstream 1") + .withDescription("description") + .withMimeType(bitstream1MimeType) + .build(); + } + context.restoreAuthSystemState(); + + String url = configurationService.getProperty("dspace.ui.url"); + String expectedResponse = "<" + MessageFormat.format(doiPattern, doi) + "> ; rel=\"cite-as\" ; anchor=\"" + + url + "/handle/" + item.getHandle() + "\" , <" + url + "/entities/publication/" + item.getID() + + "> ; rel=\"landing page\" ; type=\"text/html\" ; anchor=\"" + url + "/handle/" + item.getHandle() + + "\" , <" + url + "/bitstreams/" + bitstream1.getID() + "/download> ; rel=\"item\" ; " + + "type=\"text/plain\" ; anchor=\"" + url + "/handle/" + item.getHandle() + "\" ,"; + + getClient().perform(get("/signposting/linksets/" + item.getID()) + .header("Accept", "application/linkset")) + .andExpect(content().string(expectedResponse)); + } + + @Test + public void findTypedLinkForItem() throws Exception { + configurationService.setProperty("choices.plugin.dc.contributor.author", "SolrAuthorAuthority"); + configurationService.setProperty("authority.controlled.dc.contributor.author", "true"); + metadataAuthorityService.clearCache(); + choiceAuthorityService.clearCache(); + + String bitstreamContent = "ThisIsSomeDummyText"; + String bitstreamMimeType = "text/plain"; + String orcidValue = "orcidValue"; + + context.turnOffAuthorisationSystem(); + Item author = ItemBuilder.createItem(context, collection) + .withType("John") + .withMetadata(PERSON.getName(), "identifier", "orcid", orcidValue) + .build(); + Item item = ItemBuilder.createItem(context, collection) + .withTitle("Item Test") + .withMetadata("dc", "identifier", "doi", doi) + .withAuthor("John", author.getID().toString(), Choices.CF_ACCEPTED) + .build(); + + Bitstream bitstream = null; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + bitstream = BitstreamBuilder.createBitstream(context, item, is) + .withName("Bitstream") + .withDescription("description") + .withMimeType(bitstreamMimeType) + .build(); + } + context.restoreAuthSystemState(); + + String url = configurationService.getProperty("dspace.ui.url"); + getClient().perform(get("/signposting/links/" + item.getID()) + .header("Accept", "application/json")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", + Matchers.hasSize(4))) + .andExpect(jsonPath("$[0].href", + Matchers.hasToString(url + "/entities/publication/" + item.getID()))) + .andExpect(jsonPath("$[0].rel", + Matchers.hasToString("landing page"))) + .andExpect(jsonPath("$[0].type", + Matchers.hasToString("text/html"))) + .andExpect(jsonPath("$[1].href", + Matchers.hasToString(url + "/bitstreams/" + bitstream.getID() + "/download"))) + .andExpect(jsonPath("$[1].rel", + Matchers.hasToString("item"))) + .andExpect(jsonPath("$[1].type", + Matchers.hasToString("text/plain"))) + .andExpect(jsonPath("$[2].href", + Matchers.hasToString(MessageFormat.format(doiPattern, doi)))) + .andExpect(jsonPath("$[2].rel", + Matchers.hasToString("cite-as"))) + .andExpect(jsonPath("$[3].href", + Matchers.hasToString(url + "/entities/publication/" + author.getID()))) + .andExpect(jsonPath("$[3].rel", + Matchers.hasToString("author"))) + .andExpect(jsonPath("$[3].type", + Matchers.hasToString(orcidValue))); + + DSpaceServicesFactory.getInstance().getConfigurationService().reloadConfig(); + metadataAuthorityService.clearCache(); + choiceAuthorityService.clearCache(); + } + + @Test + public void findTypedLinkForBitstream() throws Exception { + String bitstreamContent = "ThisIsSomeDummyText"; + String bitstreamMimeType = "text/plain"; + + context.turnOffAuthorisationSystem(); + Item item = ItemBuilder.createItem(context, collection) + .withTitle("Item Test") + .withMetadata("dc", "identifier", "doi", doi) + .build(); + + Bitstream bitstream = null; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + bitstream = BitstreamBuilder.createBitstream(context, item, is) + .withName("Bitstream") + .withDescription("description") + .withMimeType(bitstreamMimeType) + .build(); + } + context.restoreAuthSystemState(); + + String uiUrl = configurationService.getProperty("dspace.ui.url"); + String serverUrl = configurationService.getProperty("dspace.server.url"); + getClient().perform(get("/signposting/links/" + bitstream.getID()) + .header("Accept", "application/json")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", + Matchers.hasSize(3))) + .andExpect(jsonPath("$[0].href", + Matchers.hasToString(uiUrl + "/entities/publication/" + item.getID()))) + .andExpect(jsonPath("$[0].rel", + Matchers.hasToString("collection"))) + .andExpect(jsonPath("$[0].type", + Matchers.hasToString("text/html"))) + .andExpect(jsonPath("$[1].href", + Matchers.hasToString(serverUrl + "/signposting/linksets/" + item.getID()))) + .andExpect(jsonPath("$[1].rel", + Matchers.hasToString("linkset"))) + .andExpect(jsonPath("$[1].type", + Matchers.hasToString("application/linkset"))) + .andExpect(jsonPath("$[2].href", + Matchers.hasToString(serverUrl + "/signposting/linksets/" + item.getID() + "/json"))) + .andExpect(jsonPath("$[2].rel", + Matchers.hasToString("linkset"))) + .andExpect(jsonPath("$[2].type", + Matchers.hasToString("application/linkset+json"))); + + DSpaceServicesFactory.getInstance().getConfigurationService().reloadConfig(); + metadataAuthorityService.clearCache(); + choiceAuthorityService.clearCache(); + } + +} diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 65b1f951fad3..f4d14eb9ee09 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -1668,6 +1668,7 @@ include = ${module_dir}/orcid.cfg include = ${module_dir}/rdf.cfg include = ${module_dir}/rest.cfg include = ${module_dir}/iiif.cfg +include = ${module_dir}/signposting.cfg include = ${module_dir}/solr-statistics.cfg include = ${module_dir}/solrauthority.cfg include = ${module_dir}/researcher-profile.cfg diff --git a/dspace/config/modules/signposting.cfg b/dspace/config/modules/signposting.cfg new file mode 100644 index 000000000000..2265294981a2 --- /dev/null +++ b/dspace/config/modules/signposting.cfg @@ -0,0 +1,29 @@ +#---------------------------------------------------------------# +#------------------SIGNPOSTING CONFIGURATIONS-------------------# + +# Allowed Cross-Origin-Resource-Sharing (CORS) origins (in "Access-Control-Allow-Origin" header). +# Only these origins (client URLs) can successfully authenticate with your REST API. +# Defaults to ${dspace.ui.url} if unspecified (as the UI must have access to the REST API). +# Multiple allowed origin URLs may be comma separated. Wildcard value (*) is NOT SUPPORTED. +# (Requires reboot of servlet container, e.g. Tomcat, to reload) +signposting.cors.allowed-origins = ${dspace.ui.url} + +# Whether or not to allow credentials (e.g. cookies) sent by the client/browser in CORS +# requests (in "Access-Control-Allow-Credentials" header). +# For DSpace, we default this to "true" to support external authentication via Shibboleth (and similar). +# However, if any of the "allowed-origins" above are *not* trusted, you may choose to set this to "false" +# for additional security. Defaults to "true" if unspecified. +# (Requires reboot of servlet container, e.g. Tomcat, to reload) +signposting.cors.allow-credentials = true + +# Path where signposting controller is available +# Defaults to "signposting", which means the signposting controller would be available +# at ${dspace.server.url}/signposting +# (Requires reboot of servlet container, e.g. Tomcat, to reload) +signposting.path = signposting + +# Whether or not to enable the signposting controller +# When "true", the signposting controller is accessible on ${signposting.path} +# When "false" or commented out, signposting is disabled/inaccessible. +# (Requires reboot of servlet container, e.g. Tomcat, to reload) +signposting.enabled = true \ 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 212237585ea2..3ede01647c3d 100644 --- a/dspace/config/spring/api/core-services.xml +++ b/dspace/config/spring/api/core-services.xml @@ -121,6 +121,8 @@ + + diff --git a/dspace/config/spring/rest/signposting.xml b/dspace/config/spring/rest/signposting.xml new file mode 100644 index 000000000000..33e73f3b77e1 --- /dev/null +++ b/dspace/config/spring/rest/signposting.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From bc73e312e028962eb914cd1cecc91e85fb4524aa Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Fri, 5 May 2023 18:45:52 +0200 Subject: [PATCH 134/510] 100553: Added test for create metadata schema & field and created test for sort byFieldName --- .../MetadataFieldRestRepository.java | 19 ++- .../MetadataSchemaRestRepository.java | 6 +- .../rest/MetadataSchemaRestRepositoryIT.java | 23 +++- .../rest/MetadatafieldRestRepositoryIT.java | 114 +++++++++++++++++- 4 files changed, 149 insertions(+), 13 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/MetadataFieldRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/MetadataFieldRestRepository.java index c185e8334219..eefd6331d116 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/MetadataFieldRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/MetadataFieldRestRepository.java @@ -14,6 +14,7 @@ import java.io.IOException; import java.sql.SQLException; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; import java.util.Objects; import javax.servlet.http.HttpServletRequest; @@ -45,10 +46,10 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; import org.springframework.data.rest.webmvc.ResourceNotFoundException; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Component; - /** * This is the repository responsible to manage MetadataField Rest object * @@ -213,7 +214,13 @@ private DiscoverQuery createDiscoverQuery(Context context, String schemaName, St DiscoverQuery discoverQuery = new DiscoverQuery(); discoverQuery.addFilterQueries(filterQueries.toArray(new String[filterQueries.size()])); - discoverQuery.setSortField("fieldName_sort", DiscoverQuery.SORT_ORDER.asc); + Iterator orderIterator = pageable.getSort().iterator(); + if (orderIterator.hasNext()) { + Sort.Order order = orderIterator.next(); + discoverQuery.setSortField(order.getProperty() + "_sort", + order.getDirection() == Sort.Direction.ASC ? DiscoverQuery.SORT_ORDER.asc : + DiscoverQuery.SORT_ORDER.desc); + } discoverQuery.setStart(Math.toIntExact(pageable.getOffset())); discoverQuery.setMaxResults(pageable.getPageSize()); return discoverQuery; @@ -254,13 +261,13 @@ protected MetadataFieldRest createAndReturn(Context context) if (isBlank(metadataFieldRest.getElement())) { throw new UnprocessableEntityException("metadata element (in request body) cannot be blank"); } else if (metadataFieldRest.getElement().contains(".")) { - throw new DSpaceBadRequestException("metadata element (in request body) cannot contain dots"); + throw new UnprocessableEntityException("metadata element (in request body) cannot contain dots"); } if (isBlank(metadataFieldRest.getQualifier())) { metadataFieldRest.setQualifier(null); } else if (metadataFieldRest.getQualifier().contains(".")) { - throw new DSpaceBadRequestException("metadata qualifier (in request body) cannot contain dots"); + throw new UnprocessableEntityException("metadata qualifier (in request body) cannot contain dots"); } // create @@ -310,12 +317,12 @@ protected MetadataFieldRest put(Context context, HttpServletRequest request, Str try { metadataFieldRest = new ObjectMapper().readValue(jsonNode.toString(), MetadataFieldRest.class); } catch (JsonProcessingException e) { - throw new UnprocessableEntityException("Cannot parse JSON in request body", e); + throw new DSpaceBadRequestException("Cannot parse JSON in request body", e); } MetadataField metadataField = metadataFieldService.find(context, id); if (metadataField == null) { - throw new ResourceNotFoundException("metadata field with id: " + id + " not found"); + throw new UnprocessableEntityException("metadata field with id: " + id + " not found"); } if (!Objects.equals(metadataFieldRest.getElement(), metadataField.getElement())) { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/MetadataSchemaRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/MetadataSchemaRestRepository.java index c45ac9821ff6..ba49a839bbe5 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/MetadataSchemaRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/MetadataSchemaRestRepository.java @@ -93,11 +93,11 @@ protected MetadataSchemaRest createAndReturn(Context context) // validate fields if (isBlank(metadataSchemaRest.getPrefix())) { throw new UnprocessableEntityException("metadata schema name cannot be blank"); + } else if (metadataSchemaRest.getPrefix().contains(".")) { + throw new UnprocessableEntityException("metadata schema namespace cannot contain dots"); } if (isBlank(metadataSchemaRest.getNamespace())) { throw new UnprocessableEntityException("metadata schema namespace cannot be blank"); - } else if (metadataSchemaRest.getNamespace().contains(".")) { - throw new UnprocessableEntityException("metadata schema namespace cannot contain dots"); } // create @@ -144,7 +144,7 @@ protected MetadataSchemaRest put(Context context, HttpServletRequest request, St try { metadataSchemaRest = new ObjectMapper().readValue(jsonNode.toString(), MetadataSchemaRest.class); } catch (JsonProcessingException e) { - throw new UnprocessableEntityException("Cannot parse JSON in request body", e); + throw new DSpaceBadRequestException("Cannot parse JSON in request body", e); } MetadataSchema metadataSchema = metadataSchemaService.find(context, id); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/MetadataSchemaRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/MetadataSchemaRestRepositoryIT.java index 02d51fb443af..4017d5f2da63 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/MetadataSchemaRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/MetadataSchemaRestRepositoryIT.java @@ -88,7 +88,7 @@ public void createSuccess() throws Exception { context.turnOffAuthorisationSystem(); MetadataSchema metadataSchema = MetadataSchemaBuilder.createMetadataSchema(context, "ATest", "ANamespace") - .build(); + .build(); context.restoreAuthSystemState(); MetadataSchemaRest metadataSchemaRest = metadataSchemaConverter.convert(metadataSchema, Projection.DEFAULT); @@ -116,6 +116,27 @@ public void createSuccess() throws Exception { } } + @Test + public void createUnprocessableEntity_prefixContainingDots() throws Exception { + context.turnOffAuthorisationSystem(); + + MetadataSchema metadataSchema = MetadataSchemaBuilder.createMetadataSchema(context, "ATest", "ANamespace") + .build(); + context.restoreAuthSystemState(); + + MetadataSchemaRest metadataSchemaRest = metadataSchemaConverter.convert(metadataSchema, Projection.DEFAULT); + metadataSchemaRest.setPrefix("test.SchemaName"); + metadataSchemaRest.setNamespace(TEST_NAMESPACE); + + String authToken = getAuthToken(admin.getEmail(), password); + + getClient(authToken) + .perform(post("/api/core/metadataschemas") + .content(new ObjectMapper().writeValueAsBytes(metadataSchemaRest)) + .contentType(contentType)) + .andExpect(status().isUnprocessableEntity()); + } + @Test public void createUnauthorizedTest() throws Exception { diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/MetadatafieldRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/MetadatafieldRestRepositoryIT.java index b85dade17043..da8dacb46935 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/MetadatafieldRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/MetadatafieldRestRepositoryIT.java @@ -9,6 +9,7 @@ import static com.jayway.jsonpath.JsonPath.read; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.hasItem; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; @@ -564,6 +565,70 @@ public void findByFieldName_exactName_combinedDiscoveryQueryParams_qualifier() t .andExpect(status().isUnprocessableEntity()); } + @Test + public void findByFieldName_sortByFieldNameASC() throws Exception { + context.turnOffAuthorisationSystem(); + + MetadataSchema schema = MetadataSchemaBuilder.createMetadataSchema(context, "ASchema", + "http://www.dspace.org/ns/aschema").build(); + + MetadataField metadataField1 = MetadataFieldBuilder + .createMetadataField(context, schema, "2", null, "AScopeNote").build(); + + MetadataField metadataField2 = MetadataFieldBuilder + .createMetadataField(context, schema, "1", null, "AScopeNote").build(); + + MetadataField metadataField3 = MetadataFieldBuilder + .createMetadataField(context, schema, "1", "a", "AScopeNote").build(); + + context.restoreAuthSystemState(); + + getClient().perform(get(SEARCH_BYFIELDNAME_ENDPOINT) + .param("query", schema.getName()) + .param("sort", "fieldName,ASC")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.metadatafields", contains( + MetadataFieldMatcher.matchMetadataField(metadataField2), + MetadataFieldMatcher.matchMetadataField(metadataField3), + MetadataFieldMatcher.matchMetadataField(metadataField1) + ))) + .andExpect(jsonPath("$.page.size", is(20))) + .andExpect(jsonPath("$.page.totalElements", is(3))); + } + + @Test + public void findByFieldName_sortByFieldNameDESC() throws Exception { + context.turnOffAuthorisationSystem(); + + MetadataSchema schema = MetadataSchemaBuilder.createMetadataSchema(context, "ASchema", + "http://www.dspace.org/ns/aschema").build(); + + MetadataField metadataField1 = MetadataFieldBuilder + .createMetadataField(context, schema, "2", null, "AScopeNote").build(); + + MetadataField metadataField2 = MetadataFieldBuilder + .createMetadataField(context, schema, "1", null, "AScopeNote").build(); + + MetadataField metadataField3 = MetadataFieldBuilder + .createMetadataField(context, schema, "1", "a", "AScopeNote").build(); + + context.restoreAuthSystemState(); + + getClient().perform(get(SEARCH_BYFIELDNAME_ENDPOINT) + .param("query", schema.getName()) + .param("sort", "fieldName,DESC")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.metadatafields", contains( + MetadataFieldMatcher.matchMetadataField(metadataField1), + MetadataFieldMatcher.matchMetadataField(metadataField3), + MetadataFieldMatcher.matchMetadataField(metadataField2) + ))) + .andExpect(jsonPath("$.page.size", is(20))) + .andExpect(jsonPath("$.page.totalElements", is(3))); + } + @Test public void createSuccess() throws Exception { @@ -575,7 +640,8 @@ public void createSuccess() throws Exception { String authToken = getAuthToken(admin.getEmail(), password); AtomicReference idRef = new AtomicReference<>(); try { - assertThat(metadataFieldService.findByElement(context, metadataSchema, ELEMENT, QUALIFIER), nullValue()); + assertThat(metadataFieldService.findByElement(context, metadataSchema, metadataFieldRest.getElement(), + metadataFieldRest.getQualifier()), nullValue()); getClient(authToken) .perform(post("/api/core/metadatafields") @@ -606,7 +672,8 @@ public void createBlankQualifier() throws Exception { String authToken = getAuthToken(admin.getEmail(), password); Integer id = null; try { - assertThat(metadataFieldService.findByElement(context, metadataSchema, ELEMENT, null), nullValue()); + assertThat(metadataFieldService.findByElement(context, metadataSchema, metadataFieldRest.getElement(), + null), nullValue()); id = read( getClient(authToken) @@ -641,7 +708,8 @@ public void create_checkAddedToIndex() throws Exception { String authToken = getAuthToken(admin.getEmail(), password); AtomicReference idRef = new AtomicReference<>(); try { - assertThat(metadataFieldService.findByElement(context, metadataSchema, ELEMENT, QUALIFIER), nullValue()); + assertThat(metadataFieldService.findByElement(context, metadataSchema, metadataFieldRest.getElement(), + metadataFieldRest.getQualifier()), nullValue()); getClient(authToken) .perform(post("/api/core/metadatafields") @@ -689,6 +757,46 @@ public void createUnauthorized() throws Exception { .andExpect(status().isUnauthorized()); } + @Test + public void createUnprocessableEntity_elementContainingDots() throws Exception { + MetadataFieldRest metadataFieldRest = new MetadataFieldRest(); + metadataFieldRest.setElement("testElement.ForCreate"); + metadataFieldRest.setQualifier(QUALIFIER); + metadataFieldRest.setScopeNote(SCOPE_NOTE); + + String authToken = getAuthToken(admin.getEmail(), password); + assertThat(metadataFieldService.findByElement(context, metadataSchema, metadataFieldRest.getElement(), + metadataFieldRest.getQualifier()), nullValue()); + + getClient(authToken) + .perform(post("/api/core/metadatafields") + .param("schemaId", String.valueOf(metadataSchema.getID())) + .param("projection", "full") + .content(new ObjectMapper().writeValueAsBytes(metadataFieldRest)) + .contentType(contentType)) + .andExpect(status().isUnprocessableEntity()); + } + + @Test + public void createUnprocessableEntity_qualifierContainingDots() throws Exception { + MetadataFieldRest metadataFieldRest = new MetadataFieldRest(); + metadataFieldRest.setElement(ELEMENT); + metadataFieldRest.setQualifier("testQualifier.ForCreate"); + metadataFieldRest.setScopeNote(SCOPE_NOTE); + + String authToken = getAuthToken(admin.getEmail(), password); + assertThat(metadataFieldService.findByElement(context, metadataSchema, metadataFieldRest.getElement(), + metadataFieldRest.getQualifier()), nullValue()); + + getClient(authToken) + .perform(post("/api/core/metadatafields") + .param("schemaId", String.valueOf(metadataSchema.getID())) + .param("projection", "full") + .content(new ObjectMapper().writeValueAsBytes(metadataFieldRest)) + .contentType(contentType)) + .andExpect(status().isUnprocessableEntity()); + } + @Test public void createUnauthorizedEPersonNoAdminRights() throws Exception { From 2dfc373ad1c8e95146121e0c19f1e740879cb00b Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Sat, 29 Apr 2023 19:24:14 +0200 Subject: [PATCH 135/510] Fixed communities sub-communities and communities collections sorting --- .../CommunityCollectionLinkRepository.java | 10 +++ .../CommunitySubcommunityLinkRepository.java | 10 +++ .../CommunityCollectionLinkRepositoryIT.java | 82 +++++++++++++++++++ ...CommunitySubcommunityLinkRepositoryIT.java | 80 ++++++++++++++++++ 4 files changed, 182 insertions(+) create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/repository/CommunityCollectionLinkRepositoryIT.java create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/repository/CommunitySubcommunityLinkRepositoryIT.java diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CommunityCollectionLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CommunityCollectionLinkRepository.java index c77dcf18dc7b..3c728d8c31b9 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CommunityCollectionLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CommunityCollectionLinkRepository.java @@ -8,6 +8,7 @@ package org.dspace.app.rest.repository; import java.sql.SQLException; +import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.UUID; @@ -31,6 +32,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort.Order; import org.springframework.data.rest.webmvc.ResourceNotFoundException; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Component; @@ -72,6 +74,14 @@ public Page getCollections(@Nullable HttpServletRequest request, discoverQuery.setStart(Math.toIntExact(pageable.getOffset())); discoverQuery.setMaxResults(pageable.getPageSize()); discoverQuery.setSortField("dc.title_sort", DiscoverQuery.SORT_ORDER.asc); + Iterator orderIterator = pageable.getSort().iterator(); + if (orderIterator.hasNext()) { + Order order = orderIterator.next(); + discoverQuery.setSortField( + order.getProperty() + "_sort", + order.getDirection().isAscending() ? DiscoverQuery.SORT_ORDER.asc : DiscoverQuery.SORT_ORDER.desc + ); + } DiscoverResult resp = searchService.search(context, scopeObject, discoverQuery); long tot = resp.getTotalSearchResults(); for (IndexableObject solrCol : resp.getIndexableObjects()) { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CommunitySubcommunityLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CommunitySubcommunityLinkRepository.java index c211810d11f9..135d964f3f42 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CommunitySubcommunityLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CommunitySubcommunityLinkRepository.java @@ -8,6 +8,7 @@ package org.dspace.app.rest.repository; import java.sql.SQLException; +import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.UUID; @@ -29,6 +30,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort.Order; import org.springframework.data.rest.webmvc.ResourceNotFoundException; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Component; @@ -68,6 +70,14 @@ public Page getSubcommunities(@Nullable HttpServletRequest reques discoverQuery.setStart(Math.toIntExact(pageable.getOffset())); discoverQuery.setMaxResults(pageable.getPageSize()); discoverQuery.setSortField("dc.title_sort", DiscoverQuery.SORT_ORDER.asc); + Iterator orderIterator = pageable.getSort().iterator(); + if (orderIterator.hasNext()) { + Order order = orderIterator.next(); + discoverQuery.setSortField( + order.getProperty() + "_sort", + order.getDirection().isAscending() ? DiscoverQuery.SORT_ORDER.asc : DiscoverQuery.SORT_ORDER.desc + ); + } DiscoverResult resp = searchService.search(context, scopeObject, discoverQuery); long tot = resp.getTotalSearchResults(); for (IndexableObject solrCommunities : resp.getIndexableObjects()) { diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/repository/CommunityCollectionLinkRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/repository/CommunityCollectionLinkRepositoryIT.java new file mode 100644 index 000000000000..24a94a4d4bb7 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/repository/CommunityCollectionLinkRepositoryIT.java @@ -0,0 +1,82 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.repository; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import org.dspace.app.rest.matcher.CollectionMatcher; +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.content.Collection; +import org.dspace.content.Community; +import org.hamcrest.Matchers; +import org.junit.Before; +import org.junit.Test; + +/** + * Test class for {@link CommunityCollectionLinkRepository} + */ +public class CommunityCollectionLinkRepositoryIT extends AbstractControllerIntegrationTest { + + Community parentCommunity; + Collection collection1; + Collection collection2; + Collection collection3; + + @Before + @Override + public void setUp() throws Exception { + super.setUp(); + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context) + .build(); + collection1 = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + collection2 = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 2") + .build(); + collection3 = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 3") + .build(); + context.commit(); + context.restoreAuthSystemState(); + } + + @Test + public void getCollections_sortTitleASC() throws Exception { + String adminToken = getAuthToken(admin.getEmail(), password); + + getClient(adminToken).perform(get("/api/core/communities/" + parentCommunity.getID() + "/collections") + .param("sort", "dc.title,ASC")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.collections", Matchers.contains( + CollectionMatcher.matchCollection(collection1), + CollectionMatcher.matchCollection(collection2), + CollectionMatcher.matchCollection(collection3) + ))); + } + + @Test + public void getCollections_sortTitleDESC() throws Exception { + String adminToken = getAuthToken(admin.getEmail(), password); + + getClient(adminToken).perform(get("/api/core/communities/" + parentCommunity.getID() + "/collections") + .param("sort", "dc.title,DESC")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.collections", Matchers.contains( + CollectionMatcher.matchCollection(collection3), + CollectionMatcher.matchCollection(collection2), + CollectionMatcher.matchCollection(collection1) + ))); + } + +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/repository/CommunitySubcommunityLinkRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/repository/CommunitySubcommunityLinkRepositoryIT.java new file mode 100644 index 000000000000..aa3b1c072187 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/repository/CommunitySubcommunityLinkRepositoryIT.java @@ -0,0 +1,80 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.repository; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import org.dspace.app.rest.matcher.CommunityMatcher; +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.builder.CommunityBuilder; +import org.dspace.content.Community; +import org.hamcrest.Matchers; +import org.junit.Before; +import org.junit.Test; + +/** + * Test class for {@link CommunitySubcommunityLinkRepository} + */ +public class CommunitySubcommunityLinkRepositoryIT extends AbstractControllerIntegrationTest { + + Community parentCommunity; + Community subCommunity1; + Community subCommunity2; + Community subCommunity3; + + @Before + @Override + public void setUp() throws Exception { + super.setUp(); + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context) + .build(); + subCommunity1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub community 1") + .build(); + subCommunity2 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub community 2") + .build(); + subCommunity3 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub community 3") + .build(); + context.commit(); + context.restoreAuthSystemState(); + } + + @Test + public void getSubCommunities_sortTitleASC() throws Exception { + String adminToken = getAuthToken(admin.getEmail(), password); + + getClient(adminToken).perform(get("/api/core/communities/" + parentCommunity.getID() + "/subcommunities") + .param("sort", "dc.title,ASC")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.subcommunities", Matchers.contains( + CommunityMatcher.matchCommunity(subCommunity1), + CommunityMatcher.matchCommunity(subCommunity2), + CommunityMatcher.matchCommunity(subCommunity3) + ))); + } + + @Test + public void getSubCommunities_sortTitleDESC() throws Exception { + String adminToken = getAuthToken(admin.getEmail(), password); + + getClient(adminToken).perform(get("/api/core/communities/" + parentCommunity.getID() + "/subcommunities") + .param("sort", "dc.title,DESC")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.subcommunities", Matchers.contains( + CommunityMatcher.matchCommunity(subCommunity3), + CommunityMatcher.matchCommunity(subCommunity2), + CommunityMatcher.matchCommunity(subCommunity1) + ))); + } + +} From 0783f7ff8285a9f0701a1ae125f5d85d348b23f4 Mon Sep 17 00:00:00 2001 From: aroman-arvo Date: Mon, 8 May 2023 08:37:55 +0200 Subject: [PATCH 136/510] DS-8636 - all files option fixed --- .../dspace/app/requestitem/RequestItemEmailNotifier.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemEmailNotifier.java b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemEmailNotifier.java index 61b42fd185e2..4171744c5eb2 100644 --- a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemEmailNotifier.java +++ b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemEmailNotifier.java @@ -167,9 +167,12 @@ static public void sendResponse(Context context, RequestItem ri, String subject, if (!bitstream.getFormat(context).isInternal() && requestItemService.isRestricted(context, bitstream)) { - email.addAttachment(bitstreamService.retrieve(context, - bitstream), bitstream.getName(), - bitstream.getFormat(context).getMIMEType()); + // #8636 Anyone receiving the email can respond to the request without authenticating into DSpace + context.turnOffAuthorisationSystem(); + email.addAttachment(bitstreamService.retrieve(context, + bitstream), bitstream.getName(), + bitstream.getFormat(context).getMIMEType()); + context.restoreAuthSystemState(); } } } From 3c37dd45193304ffccac926cfa6a05f714610b40 Mon Sep 17 00:00:00 2001 From: aroman-arvo Date: Mon, 8 May 2023 08:44:49 +0200 Subject: [PATCH 137/510] ds-8636 - indentation --- .../app/requestitem/RequestItemEmailNotifier.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemEmailNotifier.java b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemEmailNotifier.java index 4171744c5eb2..c804ac71d9e2 100644 --- a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemEmailNotifier.java +++ b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemEmailNotifier.java @@ -167,12 +167,12 @@ static public void sendResponse(Context context, RequestItem ri, String subject, if (!bitstream.getFormat(context).isInternal() && requestItemService.isRestricted(context, bitstream)) { - // #8636 Anyone receiving the email can respond to the request without authenticating into DSpace - context.turnOffAuthorisationSystem(); - email.addAttachment(bitstreamService.retrieve(context, - bitstream), bitstream.getName(), - bitstream.getFormat(context).getMIMEType()); - context.restoreAuthSystemState(); + // #8636 Anyone receiving the email can respond to the request without authenticating into DSpace + context.turnOffAuthorisationSystem(); + email.addAttachment(bitstreamService.retrieve(context, + bitstream), bitstream.getName(), + bitstream.getFormat(context).getMIMEType()); + context.restoreAuthSystemState(); } } } From 909003bfac9945d100f1d9b4a8d4be5881088376 Mon Sep 17 00:00:00 2001 From: aroman-arvo Date: Mon, 8 May 2023 09:01:05 +0200 Subject: [PATCH 138/510] Fix style --- .../org/dspace/app/requestitem/RequestItemEmailNotifier.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemEmailNotifier.java b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemEmailNotifier.java index c804ac71d9e2..caefb4bf402c 100644 --- a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemEmailNotifier.java +++ b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemEmailNotifier.java @@ -167,7 +167,8 @@ static public void sendResponse(Context context, RequestItem ri, String subject, if (!bitstream.getFormat(context).isInternal() && requestItemService.isRestricted(context, bitstream)) { - // #8636 Anyone receiving the email can respond to the request without authenticating into DSpace + // #8636 Anyone receiving the email can respond to the + // request without authenticating into DSpace context.turnOffAuthorisationSystem(); email.addAttachment(bitstreamService.retrieve(context, bitstream), bitstream.getName(), From 6b10fc3783055bba4fbd291726d574752b22aca3 Mon Sep 17 00:00:00 2001 From: aroman-arvo Date: Mon, 8 May 2023 09:15:05 +0200 Subject: [PATCH 139/510] Fix Style --- .../org/dspace/app/requestitem/RequestItemEmailNotifier.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemEmailNotifier.java b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemEmailNotifier.java index caefb4bf402c..8d1959754723 100644 --- a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemEmailNotifier.java +++ b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemEmailNotifier.java @@ -167,7 +167,7 @@ static public void sendResponse(Context context, RequestItem ri, String subject, if (!bitstream.getFormat(context).isInternal() && requestItemService.isRestricted(context, bitstream)) { - // #8636 Anyone receiving the email can respond to the + // #8636 Anyone receiving the email can respond to the // request without authenticating into DSpace context.turnOffAuthorisationSystem(); email.addAttachment(bitstreamService.retrieve(context, From 896cffda7da45b2e825b7b7c2a4a4f2c05853b25 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Mon, 8 May 2023 11:48:56 -0400 Subject: [PATCH 140/510] Get request-copy response email body from backend templates, as with all other emails. There is a considerable quantity of other changes here which were made to facilitate testing by making the affected class a bean and injecting the desired "strategy" class programmatically in the test. --- .../requestitem/RequestItemEmailNotifier.java | 81 +++++--- .../dspace/app/requestitem/package-info.java | 15 +- ...ring-dspace-addon-requestitem-services.xml | 11 +- .../requestitem/JavaMailTestTransport.java | 65 +++++++ .../RequestItemEmailNotifierTest.java | 173 ++++++++++++++++++ .../repository/RequestItemRepository.java | 14 +- .../app/rest/utils/ApplicationConfig.java | 18 +- 7 files changed, 328 insertions(+), 49 deletions(-) create mode 100644 dspace-api/src/test/java/org/dspace/app/requestitem/JavaMailTestTransport.java create mode 100644 dspace-api/src/test/java/org/dspace/app/requestitem/RequestItemEmailNotifierTest.java diff --git a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemEmailNotifier.java b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemEmailNotifier.java index 384f33decaf2..d3813500911d 100644 --- a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemEmailNotifier.java +++ b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemEmailNotifier.java @@ -11,55 +11,57 @@ import java.io.IOException; import java.sql.SQLException; import java.util.List; +import javax.annotation.ManagedBean; +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Singleton; import javax.mail.MessagingException; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.dspace.app.requestitem.factory.RequestItemServiceFactory; import org.dspace.app.requestitem.service.RequestItemService; import org.dspace.authorize.AuthorizeException; import org.dspace.content.Bitstream; import org.dspace.content.Bundle; import org.dspace.content.Item; -import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.BitstreamService; import org.dspace.core.Context; import org.dspace.core.Email; import org.dspace.core.I18nUtil; import org.dspace.core.LogHelper; import org.dspace.eperson.EPerson; -import org.dspace.handle.factory.HandleServiceFactory; import org.dspace.handle.service.HandleService; import org.dspace.services.ConfigurationService; -import org.dspace.services.factory.DSpaceServicesFactory; /** * Send item requests and responses by email. * + *

The "strategy" by which approvers are chosen is in an implementation of + * {@link RequestItemAuthorExtractor} which is injected by the name + * {@code requestItemAuthorExtractor}. See the DI configuration documents. + * * @author Mark H. Wood */ +@Singleton +@ManagedBean public class RequestItemEmailNotifier { private static final Logger LOG = LogManager.getLogger(); - private static final BitstreamService bitstreamService - = ContentServiceFactory.getInstance().getBitstreamService(); - - private static final ConfigurationService configurationService - = DSpaceServicesFactory.getInstance().getConfigurationService(); + @Inject + protected BitstreamService bitstreamService; - private static final HandleService handleService - = HandleServiceFactory.getInstance().getHandleService(); + @Inject + protected ConfigurationService configurationService; - private static final RequestItemService requestItemService - = RequestItemServiceFactory.getInstance().getRequestItemService(); + @Inject + protected HandleService handleService; - private static final RequestItemAuthorExtractor requestItemAuthorExtractor - = DSpaceServicesFactory.getInstance() - .getServiceManager() - .getServiceByName("requestItemAuthorExtractor", - RequestItemAuthorExtractor.class); + @Inject + protected RequestItemService requestItemService; - private RequestItemEmailNotifier() {} + @Inject + @Named("requestItemAuthorExtractor") // alias for selected strategy bean + protected RequestItemAuthorExtractor requestItemAuthorExtractor; /** * Send the request to the approver(s). @@ -70,7 +72,7 @@ private RequestItemEmailNotifier() {} * @throws IOException passed through. * @throws SQLException if the message was not sent. */ - static public void sendRequest(Context context, RequestItem ri, String responseLink) + public void sendRequest(Context context, RequestItem ri, String responseLink) throws IOException, SQLException { // Who is making this request? List authors = requestItemAuthorExtractor @@ -147,12 +149,38 @@ static public void sendRequest(Context context, RequestItem ri, String responseL * @param message email body (may be empty). * @throws IOException if sending failed. */ - static public void sendResponse(Context context, RequestItem ri, String subject, + public void sendResponse(Context context, RequestItem ri, String subject, String message) throws IOException { + // Who granted this request? + List grantors; + try { + grantors = requestItemAuthorExtractor.getRequestItemAuthor(context, ri.getItem()); + } catch (SQLException e) { + LOG.warn("Failed to get grantor's name and address: {}", e.getMessage()); + grantors = List.of(); + } + + String grantorName; + String grantorAddress; + if (grantors.isEmpty()) { + grantorName = configurationService.getProperty("mail.admin.name"); + grantorAddress = configurationService.getProperty("mail.admin"); + } else { + RequestItemAuthor grantor = grantors.get(0); // XXX Cannot know which one + grantorName = grantor.getFullName(); + grantorAddress = grantor.getEmail(); + } + // Build an email back to the requester. - Email email = new Email(); - email.setContent("body", message); + Email email = Email.getEmail(I18nUtil.getEmailFilename(context.getCurrentLocale(), + "request_item.granted")); + email.addArgument(ri.getReqName()); // {0} requestor's name + email.addArgument(ri.getItem().getID().toString()); // {1} URL of the requested Item + email.addArgument(ri.getItem().getName()); // {2} title of the requested Item + email.addArgument(grantorName); // {3} name of the grantor + email.addArgument(grantorAddress); // {4} email of the grantor + email.addArgument(message); // {5} grantor's optional message email.setSubject(subject); email.addRecipient(ri.getReqEmail()); // Attach bitstreams. @@ -167,8 +195,9 @@ static public void sendResponse(Context context, RequestItem ri, String subject, if (!bitstream.getFormat(context).isInternal() && requestItemService.isRestricted(context, bitstream)) { - email.addAttachment(bitstreamService.retrieve(context, - bitstream), bitstream.getName(), + email.addAttachment( + bitstreamService.retrieve(context, bitstream), + bitstream.getName(), bitstream.getFormat(context).getMIMEType()); } } @@ -207,7 +236,7 @@ static public void sendResponse(Context context, RequestItem ri, String subject, * @throws IOException if the message body cannot be loaded or the message * cannot be sent. */ - static public void requestOpenAccess(Context context, RequestItem ri) + public void requestOpenAccess(Context context, RequestItem ri) throws IOException { Email message = Email.getEmail(I18nUtil.getEmailFilename(context.getCurrentLocale(), "request_item.admin")); diff --git a/dspace-api/src/main/java/org/dspace/app/requestitem/package-info.java b/dspace-api/src/main/java/org/dspace/app/requestitem/package-info.java index 5886f16fde1a..fa7c15b23060 100644 --- a/dspace-api/src/main/java/org/dspace/app/requestitem/package-info.java +++ b/dspace-api/src/main/java/org/dspace/app/requestitem/package-info.java @@ -12,10 +12,15 @@ * e-mailed to a responsible party for consideration and action. Find details * in the user documentation under the rubric "Request a Copy". * - *

This package includes several "strategy" classes which discover responsible - * parties in various ways. See {@link RequestItemSubmitterStrategy} and the - * classes which extend it. A strategy class must be configured and identified - * as {@link RequestItemAuthorExtractor} for injection into code which requires - * Request a Copy services. + *

Mailing is handled by {@link RequestItemEmailNotifier}. Responsible + * parties are represented by {@link RequestItemAuthor} + * + *

This package includes several "strategy" classes which discover + * responsible parties in various ways. See + * {@link RequestItemSubmitterStrategy} and the classes which extend it, and + * others which implement {@link RequestItemAuthorExtractor}. A strategy class + * must be configured and identified as {@link requestItemAuthorExtractor} + * (note capitalization) for injection into code which requires Request + * a Copy services. */ package org.dspace.app.requestitem; diff --git a/dspace-api/src/main/resources/spring/spring-dspace-addon-requestitem-services.xml b/dspace-api/src/main/resources/spring/spring-dspace-addon-requestitem-services.xml index b9c11f8164d6..b8ce542ffcac 100644 --- a/dspace-api/src/main/resources/spring/spring-dspace-addon-requestitem-services.xml +++ b/dspace-api/src/main/resources/spring/spring-dspace-addon-requestitem-services.xml @@ -14,16 +14,17 @@ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/context - http://www.springframework.org/schema/context/spring-context-2.5.xsd" - default-autowire-candidates="*Service,*DAO,javax.sql.DataSource"> + http://www.springframework.org/schema/context/spring-context-2.5.xsd"> - + - - + + + This sends various emails between the requestor and the grantor. + + + + - + id="org.dspace.app.requestitem.RequestItemMetadataStrategy"> + + Get recipients from an item metadata field. + - + id="org.dspace.app.requestitem.RequestItemHelpdeskStrategy"> + + HelpDesk to instead get RequestItem emails + + + id='org.dspace.app.requestitem.CollectionAdministratorsRequestItemStrategy'> - Send request emails to administrators of an Item's owning - Collection. + Send request emails to administrators of an Item's owning + Collection. + id='org.dspace.app.requestitem.CombiningRequestItemStrategy'> - Execute multiple strategies and concatenate their lists of - recipients. Mail will go to all members of the combined list. + Execute multiple strategies and concatenate their lists of + recipients. Mail will go to all members of the combined list. - A list of RequestItemAuthorExtractor beans + A list of RequestItemAuthorExtractor beans - - - - - - - - - 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 d88e775f8bbe..492d650cab0f 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 @@ -23,14 +23,18 @@ * @author Tim Donohue */ @Configuration +// Component scanning ignores any parent {@code ApplicationContext}s, so any +// bean which is in the scope of both will be duplicated. dspace-services makes +// its context the parent of this one. If a bean is explicitly configured in +// the parent, it won't be so configured in this context and you may have +// trouble. IOW be careful what you add here. @ComponentScan( { "org.dspace.app.rest.converter", "org.dspace.app.rest.repository", "org.dspace.app.rest.utils", "org.dspace.app.configuration", "org.dspace.iiif", - "org.dspace.app.iiif", - "org.dspace.app.requestitem" + "org.dspace.app.iiif" }) public class ApplicationConfig { // Allowed CORS origins ("Access-Control-Allow-Origin" header) diff --git a/dspace/config/spring/api/requestitem.xml b/dspace/config/spring/api/requestitem.xml index 63bbacd49bf8..e98ec07a1f41 100644 --- a/dspace/config/spring/api/requestitem.xml +++ b/dspace/config/spring/api/requestitem.xml @@ -17,9 +17,10 @@ meld the lists from two or more other strategies. - + - + This sends various emails between the requestor and the grantor. From 4e64afbe3b3f57986fe8a635db7254ada7c12cff Mon Sep 17 00:00:00 2001 From: Mohamed Saber Eskander Date: Thu, 11 May 2023 12:04:20 +0300 Subject: [PATCH 148/510] bulk access controll --- .../bulkaccesscontrol/BulkAccessControl.java | 456 ++++++++++++++++++ .../BulkAccessControlCli.java | 18 + ...lkAccessControlCliScriptConfiguration.java | 19 + .../BulkAccessControlScriptConfiguration.java | 113 +++++ .../exception/BulkAccessControlException.java | 48 ++ .../model/AccessCondition.java | 54 +++ .../model/AccessConditionBitstream.java | 60 +++ .../model/AccessConditionItem.java | 36 ++ .../model/AccessControl.java | 40 ++ .../authorize/ResourcePolicyServiceImpl.java | 9 + .../authorize/dao/ResourcePolicyDAO.java | 3 + .../dao/impl/ResourcePolicyDAOImpl.java | 13 + .../service/ResourcePolicyService.java | 3 + .../org/dspace/content/ItemServiceImpl.java | 33 +- .../dspace/content/service/ItemService.java | 19 + .../submit/model/AccessConditionOption.java | 2 +- .../config/spring/api/scripts.xml | 5 + .../BulkAccessControlIT.java | 437 +++++++++++++++++ .../app/matcher/ResourcePolicyMatcher.java | 126 +++++ .../config/spring/rest/scripts.xml | 5 + dspace/config/spring/api/scripts.xml | 5 + dspace/config/spring/rest/scripts.xml | 6 + 22 files changed, 1505 insertions(+), 5 deletions(-) create mode 100644 dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/BulkAccessControl.java create mode 100644 dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/BulkAccessControlCli.java create mode 100644 dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/BulkAccessControlCliScriptConfiguration.java create mode 100644 dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/BulkAccessControlScriptConfiguration.java create mode 100644 dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/exception/BulkAccessControlException.java create mode 100644 dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/model/AccessCondition.java create mode 100644 dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/model/AccessConditionBitstream.java create mode 100644 dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/model/AccessConditionItem.java create mode 100644 dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/model/AccessControl.java create mode 100644 dspace-api/src/test/java/org/dspace/app/bulkaccesscontrol/BulkAccessControlIT.java create mode 100644 dspace-api/src/test/java/org/dspace/app/matcher/ResourcePolicyMatcher.java 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 new file mode 100644 index 000000000000..dc3a6da56ba4 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/BulkAccessControl.java @@ -0,0 +1,456 @@ +/** + * 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.bulkaccesscontrol; + +import static org.apache.commons.collections4.CollectionUtils.isNotEmpty; +import static org.dspace.authorize.ResourcePolicy.TYPE_CUSTOM; +import static org.dspace.authorize.ResourcePolicy.TYPE_INHERITED; + +import java.io.IOException; +import java.io.InputStream; +import java.sql.SQLException; +import java.util.Date; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; +import java.util.function.Function; +import java.util.stream.Collectors; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.cli.ParseException; +import org.apache.commons.lang3.StringUtils; +import org.dspace.app.bulkaccesscontrol.exception.BulkAccessControlException; +import org.dspace.app.bulkaccesscontrol.model.AccessCondition; +import org.dspace.app.bulkaccesscontrol.model.AccessConditionBitstream; +import org.dspace.app.bulkaccesscontrol.model.AccessControl; +import org.dspace.app.bulkaccesscontrol.model.BulkAccessConditionConfiguration; +import org.dspace.app.bulkaccesscontrol.service.BulkAccessConditionConfigurationService; +import org.dspace.app.util.DSpaceObjectUtilsImpl; +import org.dspace.app.util.service.DSpaceObjectUtils; +import org.dspace.authorize.AuthorizeException; +import org.dspace.authorize.factory.AuthorizeServiceFactory; +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.authorize.service.ResourcePolicyService; +import org.dspace.content.Bitstream; +import org.dspace.content.DSpaceObject; +import org.dspace.content.Item; +import org.dspace.content.factory.ContentServiceFactory; +import org.dspace.content.service.BitstreamService; +import org.dspace.content.service.BundleService; +import org.dspace.content.service.CollectionService; +import org.dspace.content.service.CommunityService; +import org.dspace.content.service.ItemService; +import org.dspace.core.Constants; +import org.dspace.core.Context; +import org.dspace.discovery.DiscoverQuery; +import org.dspace.discovery.SearchService; +import org.dspace.discovery.SearchServiceException; +import org.dspace.discovery.SearchUtils; +import org.dspace.discovery.indexobject.IndexableItem; +import org.dspace.discovery.indexobject.factory.IndexObjectFactoryFactory; +import org.dspace.eperson.EPerson; +import org.dspace.eperson.factory.EPersonServiceFactory; +import org.dspace.handle.factory.HandleServiceFactory; +import org.dspace.handle.service.HandleService; +import org.dspace.scripts.DSpaceRunnable; +import org.dspace.services.ConfigurationService; +import org.dspace.services.factory.DSpaceServicesFactory; +import org.dspace.submit.model.AccessConditionOption; +import org.dspace.util.MultiFormatDateParser; +import org.dspace.utils.DSpace; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Implementation of {@link DSpaceRunnable} to perform a bulk access control via json file. + * + * @author Mohamed Eskander (mohamed.eskander at 4science.it) + * + */ +public class BulkAccessControl extends DSpaceRunnable> { + + private static final Logger LOGGER = LoggerFactory.getLogger(BulkAccessControl.class); + + private DSpaceObjectUtils dSpaceObjectUtils; + + private SearchService searchService; + + private IndexObjectFactoryFactory indexObjectServiceFactory; + + private CommunityService communityService; + + private CollectionService collectionService; + + private ConfigurationService configurationService; + + private ItemService itemService; + + private AuthorizeService authorizeService; + + private String filename; + + private String[] uuids; + + private String [] targetUuids; + + private Context context; + + private BundleService bundleService; + + private BitstreamService bitstreamService; + + private BulkAccessConditionConfigurationService bulkAccessConditionConfigurationService; + + private ResourcePolicyService resourcePolicyService; + + private Map itemAccessConditions; + + private Map uploadAccessConditions; + + @Override + @SuppressWarnings("unchecked") + public void setup() throws ParseException { + + this.searchService = SearchUtils.getSearchService(); + this.indexObjectServiceFactory = IndexObjectFactoryFactory.getInstance(); + this.communityService = ContentServiceFactory.getInstance().getCommunityService(); + this.collectionService = ContentServiceFactory.getInstance().getCollectionService(); + this.itemService = ContentServiceFactory.getInstance().getItemService(); + this.authorizeService = AuthorizeServiceFactory.getInstance().getAuthorizeService(); + this.bundleService = ContentServiceFactory.getInstance().getBundleService(); + this.bitstreamService = ContentServiceFactory.getInstance().getBitstreamService(); + this.resourcePolicyService = AuthorizeServiceFactory.getInstance().getResourcePolicyService(); + this.configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); + this.bulkAccessConditionConfigurationService = new DSpace().getServiceManager().getServiceByName( + "bulkAccessConditionConfigurationService", BulkAccessConditionConfigurationService.class); + this.dSpaceObjectUtils = new DSpace().getServiceManager().getServiceByName( + DSpaceObjectUtilsImpl.class.getName(), DSpaceObjectUtilsImpl.class); + + BulkAccessConditionConfiguration bulkAccessConditionConfiguration = + bulkAccessConditionConfigurationService.getBulkAccessConditionConfiguration("default"); + + itemAccessConditions = bulkAccessConditionConfiguration + .getItemAccessConditionOptions() + .stream() + .collect(Collectors.toMap(AccessConditionOption::getName, Function.identity())); + + uploadAccessConditions = bulkAccessConditionConfiguration + .getBitstreamAccessConditionOptions() + .stream() + .collect(Collectors.toMap(AccessConditionOption::getName, Function.identity())); + + filename = commandLine.getOptionValue('f'); + uuids = commandLine.getOptionValues('u'); + } + + @Override + public void internalRun() throws Exception { + ObjectMapper mapper = new ObjectMapper(); + AccessControl accessControl; + context = new Context(Context.Mode.BATCH_EDIT); + assignCurrentUserInContext(); + assignSpecialGroupsInContext(); + + context.turnOffAuthorisationSystem(); + + if (uuids == null) { + handler.logError("A target uuid must be provided (run with -h flag for details)"); + throw new IllegalArgumentException("A target uuid must be provided"); + } else if (uuids.length <= 0) { + handler.logError("A target uuid must be provided with at least on uuid"); + throw new IllegalArgumentException("A target uuid must be provided with at least on uuid"); + } + + InputStream inputStream = handler.getFileStream(context, filename) + .orElseThrow(() -> new IllegalArgumentException("Error reading file, the file couldn't be " + + "found for filename: " + filename)); + + try { + accessControl = mapper.readValue(inputStream, AccessControl.class); + } catch (IOException e) { + handler.logError("Error parsing json file"); + throw new IllegalArgumentException("Error parsing json file", e); + } + + try { + validate(accessControl); + updateItemsAndBitstreamsPolices(accessControl); + context.complete(); + context.restoreAuthSystemState(); + } catch (Exception e) { + handler.handleException(e); + context.abort(); + } + } + + private void validate(AccessControl accessControl) { + + if (Objects.isNull(accessControl.getItem()) && Objects.isNull(accessControl.getBitstream())) { + handler.logError("item or bitstream nodes must be provided"); + } + + if (Objects.nonNull(accessControl.getItem())) { + if (StringUtils.isNotEmpty(accessControl.getItem().getMode()) && + !(accessControl.getItem().getMode().equals("add") || + accessControl.getItem().getMode().equals("replace"))) { + handler.logError("wrong value for item mode<" + accessControl.getItem().getMode() + ">"); + } + + for (AccessCondition accessCondition : accessControl.getItem().getAccessConditions()) { + validateAccessCondition(accessCondition); + } + } + + if (Objects.nonNull(accessControl.getBitstream())) { + if (StringUtils.isNotEmpty(accessControl.getBitstream().getMode()) && + !(accessControl.getBitstream().getMode().equals("add") || + accessControl.getBitstream().getMode().equals("replace"))) { + handler.logError("wrong value for bitstream mode<" + accessControl.getBitstream().getMode() + ">"); + } + + for (AccessCondition accessCondition : accessControl.getBitstream().getAccessConditions()) { + validateAccessCondition(accessCondition); + } + } + } + + private void validateAccessCondition(AccessCondition accessCondition) { + + if (!itemAccessConditions.containsKey(accessCondition.getName())) { + handler.logError("wrong access condition <" + accessCondition.getName() + ">"); + throw new IllegalArgumentException("wrong access condition <" + accessCondition.getName() + ">"); + } + + try { + itemAccessConditions.get(accessCondition.getName()).validateResourcePolicy( + context, accessCondition.getName(), accessCondition.getStartDate(), accessCondition.getEndDate()); + } catch (Exception e) { + handler.logError("invalid access condition"); + handler.handleException(e); + } + } + + public void updateItemsAndBitstreamsPolices(AccessControl accessControl) + throws SQLException, SearchServiceException, AuthorizeException { + + int counter = 0; + int start = 0; + int limit = 20; + + String query = buildSolrQuery(uuids); + + Iterator itemIterator = findItems(query, start, limit); + + while (itemIterator.hasNext()) { + + Item item = itemIterator.next(); + + if (Objects.nonNull(accessControl.getItem())) { + updateItemPolicies(item, accessControl); + } + + if (Objects.nonNull(accessControl.getBitstream())) { + updateBitstreamsPolicies(item, accessControl); + } + + context.commit(); + context.uncacheEntity(item); + + counter++; + + if (counter == limit) { + start += limit; + itemIterator = findItems(query, start, limit); + } + } + } + + private String buildSolrQuery(String[] uuids) throws SQLException { + HandleService handleService = HandleServiceFactory.getInstance().getHandleService(); + String [] query = new String[uuids.length]; + for (int i = 0 ; i < query.length ; i++) { + DSpaceObject dso = dSpaceObjectUtils.findDSpaceObject(context, UUID.fromString(uuids[i])); + if (dso.getType() == Constants.COMMUNITY) { + query[i] = "location.comm:" + dso.getID(); + } else if (dso.getType() == Constants.COLLECTION) { + query[i] = "location.coll:" + dso.getID(); + } else if (dso.getType() == Constants.ITEM) { + query[i] = "search.resourceid:" + dso.getID(); + } + } + return StringUtils.joinWith(" OR ", query); + } + + private Iterator findItems(String query, int start, int limit) + throws SearchServiceException { + + DiscoverQuery discoverQuery = buildDiscoveryQuery(query, start, limit); + + return searchService.search(context, discoverQuery) + .getIndexableObjects() + .stream() + .map(indexableObject -> + ((IndexableItem) indexableObject).getIndexedObject()) + .collect(Collectors.toList()) + .iterator(); + } + + private DiscoverQuery buildDiscoveryQuery(String query, int start, int limit) { + DiscoverQuery discoverQuery = new DiscoverQuery(); + discoverQuery.setDSpaceObjectFilter(IndexableItem.TYPE); + discoverQuery.setQuery(query); + discoverQuery.setStart(start); + discoverQuery.setMaxResults(limit); + + return discoverQuery; + } + + private void updateItemPolicies(Item item, AccessControl accessControl) throws SQLException, AuthorizeException { + + if ("replace".equals(accessControl.getItem().getMode())) { + removeReadPolicies(item, TYPE_CUSTOM); + removeReadPolicies(item, TYPE_INHERITED); + } + + setItemPolicies(item, accessControl); + } + + private void setItemPolicies(Item item, AccessControl accessControl) throws SQLException, AuthorizeException { + if (isAppendModeDisabled() && item.isArchived()) { + // change to add + itemService.adjustItemPolicies(context, item, item.getOwningCollection()); + } + + accessControl + .getItem() + .getAccessConditions() + .forEach(accessCondition -> createResourcePolicy(item, accessCondition, + itemAccessConditions.get(accessCondition.getName()))); + } + + private void updateBitstreamsPolicies(Item item, AccessControl accessControl) { + + if (containsConstraints(accessControl)) { + findMatchedBitstreams(item, accessControl.getBitstream().getConstraint().getUuids()) + .forEach(bitstream -> + updateBitstreamPolicies(bitstream, item, accessControl)); + } else { + item.getBundles() + .stream() + .flatMap(bundle -> bundle.getBitstreams().stream()) + .forEach(bitstream -> + updateBitstreamPolicies(bitstream, item, accessControl)); + } + } + + private boolean containsConstraints(AccessControl accessControl) { + AccessConditionBitstream controlBitstream = accessControl.getBitstream(); + + return Objects.nonNull(controlBitstream) && + Objects.nonNull(controlBitstream.getConstraint()) && + isNotEmpty(controlBitstream.getConstraint().getUuids()); + } + + private List findMatchedBitstreams(Item item, List uuids) { + return item.getBundles().stream() + .flatMap(bundle -> bundle.getBitstreams().stream()) + .filter(bitstream -> uuids.contains(bitstream.getID().toString())) + .collect(Collectors.toList()); + } + + private void updateBitstreamPolicies(Bitstream bitstream, Item item, AccessControl accessControl) { + + if ("replace".equals(accessControl.getBitstream().getMode())) { + removeReadPolicies(bitstream, TYPE_CUSTOM); + removeReadPolicies(bitstream, TYPE_INHERITED); + } + + try { + setBitstreamPolicies(bitstream, item, accessControl); + } catch (SQLException | AuthorizeException e) { + throw new RuntimeException(e); + } + + } + + private void removeReadPolicies(DSpaceObject dso, String type) { + try { + resourcePolicyService.removePolicies(context, dso, type, Constants.READ); + } catch (SQLException | AuthorizeException e) { + throw new BulkAccessControlException(e); + } + } + + private void setBitstreamPolicies(Bitstream bitstream, Item item, AccessControl accessControl) + throws SQLException, AuthorizeException { + if (isAppendModeDisabled() && item.isArchived()) { + itemService.adjustBitstreamPolicies(context, item, item.getOwningCollection(), bitstream); + } + + accessControl.getBitstream() + .getAccessConditions() + .forEach(accessCondition -> createResourcePolicy(bitstream, accessCondition, + uploadAccessConditions.get(accessCondition.getName()))); + } + + private void createResourcePolicy(DSpaceObject obj, AccessCondition accessCondition, + AccessConditionOption AccessConditionOption) { + + String name = accessCondition.getName(); + String description = accessCondition.getDescription(); + Date startDate = accessCondition.getStartDate(); + Date endDate = accessCondition.getEndDate(); + + try { + AccessConditionOption.createResourcePolicy(context, obj, name, description, startDate, endDate); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + +// private void rollback() { +// try { +// context.rollback(); +// } catch (SQLException e) { +// throw new SQLRuntimeException(e); +// } +// } + + private void assignCurrentUserInContext() throws SQLException { + UUID uuid = getEpersonIdentifier(); + if (uuid != null) { + EPerson ePerson = EPersonServiceFactory.getInstance().getEPersonService().find(context, uuid); + context.setCurrentUser(ePerson); + } + } + + private void assignSpecialGroupsInContext() throws SQLException { + for (UUID uuid : handler.getSpecialGroups()) { + context.setSpecialGroup(uuid); + } + } + + private Date parseDate(String date) { + return MultiFormatDateParser.parse(date); + } + + private boolean isAppendModeDisabled() { + return !configurationService.getBooleanProperty( + "core.authorization.installitem.inheritance-read.append-mode"); + } + + @Override + @SuppressWarnings("unchecked") + public BulkAccessControlScriptConfiguration getScriptConfiguration() { + return new DSpace().getServiceManager() + .getServiceByName("bulk-access-control",BulkAccessControlScriptConfiguration.class); + } + +} diff --git a/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/BulkAccessControlCli.java b/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/BulkAccessControlCli.java new file mode 100644 index 000000000000..054c642298d4 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/BulkAccessControlCli.java @@ -0,0 +1,18 @@ +/** + * 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.bulkaccesscontrol; + +/** + * Extension of {@link BulkAccessControl} for CLI. + * + * @author Mohamed Eskander (mohamed.eskander at 4science.it) + * + */ +public class BulkAccessControlCli extends BulkAccessControl { + +} diff --git a/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/BulkAccessControlCliScriptConfiguration.java b/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/BulkAccessControlCliScriptConfiguration.java new file mode 100644 index 000000000000..25b631c51f46 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/BulkAccessControlCliScriptConfiguration.java @@ -0,0 +1,19 @@ +/** + * 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.bulkaccesscontrol; + +/** + * Extension of {@link BulkAccessControlScriptConfiguration} for CLI. + * + * @author Mohamed Eskander (mohamed.eskander at 4science.it) + * + */ +public class BulkAccessControlCliScriptConfiguration + extends BulkAccessControlScriptConfiguration { + +} diff --git a/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/BulkAccessControlScriptConfiguration.java b/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/BulkAccessControlScriptConfiguration.java new file mode 100644 index 000000000000..edda394c8237 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/BulkAccessControlScriptConfiguration.java @@ -0,0 +1,113 @@ +/** + * 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.bulkaccesscontrol; + +import java.io.InputStream; +import java.sql.SQLException; +import java.util.List; +import java.util.Objects; +import java.util.UUID; +import java.util.stream.Collectors; + +import org.apache.commons.cli.Options; +import org.dspace.app.util.DSpaceObjectUtilsImpl; +import org.dspace.app.util.service.DSpaceObjectUtils; +import org.dspace.content.DSpaceObject; +import org.dspace.core.Context; +import org.dspace.scripts.DSpaceCommandLineParameter; +import org.dspace.scripts.configuration.ScriptConfiguration; +import org.dspace.utils.DSpace; + +/** + * Script configuration for {@link BulkAccessControl}. + * + * @author Mohamed Eskander (mohamed.eskander at 4science.it) + * + * @param the {@link BulkAccessControl} type + */ +public class BulkAccessControlScriptConfiguration extends ScriptConfiguration { + + private Class dspaceRunnableClass; + + @Override + public boolean isAllowedToExecute(Context context, List commandLineParameters) { + + try { + if (Objects.isNull(commandLineParameters)) { + throw new IllegalArgumentException(); + } else if (commandLineParameters.stream() + .map(DSpaceCommandLineParameter::getName) + .noneMatch("-u"::equals)) { + throw new IllegalArgumentException(); + } else { + List dspaceObjectIDs = + commandLineParameters.stream() + .filter(parameter -> "-u".equals(parameter.getName())) + .map(DSpaceCommandLineParameter::getValue) + .collect(Collectors.toList()); + + DSpaceObjectUtils dSpaceObjectUtils = new DSpace().getServiceManager().getServiceByName( + DSpaceObjectUtilsImpl.class.getName(), DSpaceObjectUtilsImpl.class); + + for (String dspaceObjectID : dspaceObjectIDs) { + + DSpaceObject dso = dSpaceObjectUtils.findDSpaceObject(context, UUID.fromString(dspaceObjectID)); + + if (Objects.isNull(dso)) { + throw new IllegalArgumentException(); + } + + if (!authorizeService.isAdmin(context, dso)) { + return false; + } + } + } + } catch (SQLException e) { + throw new RuntimeException(e); + } + + return true; + } + + @Override + public Options getOptions() { + if (options == null) { + Options options = new Options(); + + options.addOption("u", "target", true, "target uuids of communities/collections/items"); + options.getOption("u").setType(String.class); + options.getOption("u").setRequired(true); + + options.addOption("f", "file", true, "source json file"); + options.getOption("f").setType(InputStream.class); + options.getOption("f").setRequired(true); + + options.addOption("h", "help", false, "help"); + + super.options = options; + } + return options; + } + + @Override + public Class getDspaceRunnableClass() { + return dspaceRunnableClass; + } + + /** + * Generic setter for the dspaceRunnableClass + * + * @param dspaceRunnableClass The dspaceRunnableClass to be set on this + * BulkImportScriptConfiguration + */ + @Override + public void setDspaceRunnableClass(Class dspaceRunnableClass) { + this.dspaceRunnableClass = dspaceRunnableClass; + } + +} diff --git a/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/exception/BulkAccessControlException.java b/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/exception/BulkAccessControlException.java new file mode 100644 index 000000000000..092611eb0654 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/exception/BulkAccessControlException.java @@ -0,0 +1,48 @@ +/** + * 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.bulkaccesscontrol.exception; + +/** + * Exception for errors that occurs during the bulk access control + * + * @author Mohamed Eskander (mohamed.eskander at 4science.it) + * + */ +public class BulkAccessControlException extends RuntimeException { + + private static final long serialVersionUID = -74730626862418515L; + + /** + * Constructor with error message and cause. + * + * @param message the error message + * @param cause the error cause + */ + public BulkAccessControlException(String message, Throwable cause) { + super(message, cause); + } + + /** + * Constructor with error message. + * + * @param message the error message + */ + public BulkAccessControlException(String message) { + super(message); + } + + /** + * Constructor with error cause. + * + * @param cause the error cause + */ + public BulkAccessControlException(Throwable cause) { + super(cause); + } + +} diff --git a/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/model/AccessCondition.java b/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/model/AccessCondition.java new file mode 100644 index 000000000000..40ea2d0e25f4 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/model/AccessCondition.java @@ -0,0 +1,54 @@ +/** + * 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.bulkaccesscontrol.model; + +import java.util.Date; + +/** + * Class that model the value of ACCESS_CONDITION_CELL + * of sheet BITSTREAM_METADATA of the Bulk import excel. + * + * @author Mohamed Eskander (mohamed.eskander at 4science.it) + */ +public class AccessCondition { + + private String name; + + private String description; + + private Date startDate; + + private Date endDate; + + public AccessCondition() { + } + + public AccessCondition(String name, String description, Date startDate, Date endDate) { + this.name = name; + this.description = description; + this.startDate = startDate; + this.endDate = endDate; + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public Date getStartDate() { + return startDate; + } + + public Date getEndDate() { + return endDate; + } + +} diff --git a/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/model/AccessConditionBitstream.java b/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/model/AccessConditionBitstream.java new file mode 100644 index 000000000000..0e5ee47d4ec7 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/model/AccessConditionBitstream.java @@ -0,0 +1,60 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.bulkaccesscontrol.model; + +import java.util.List; + +/** + * @author Mohamed Eskander (mohamed.eskander at 4science.it) + */ +public class AccessConditionBitstream { + + private String mode; + + private Constraint constraint; + + private List accessConditions; + + public String getMode() { + return mode; + } + + public void setMode(String mode) { + this.mode = mode; + } + + public Constraint getConstraint() { + return constraint; + } + + public void setConstraint(Constraint constraint) { + this.constraint = constraint; + } + + public List getAccessConditions() { + return accessConditions; + } + + public void setAccessConditions(List accessConditions) { + this.accessConditions = accessConditions; + } + + public class Constraint { + + private List uuids; + + public List getUuids() { + return uuids; + } + + public void setUuids(List uuids) { + this.uuids = uuids; + } + } + +} diff --git a/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/model/AccessConditionItem.java b/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/model/AccessConditionItem.java new file mode 100644 index 000000000000..6176cc220c52 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/model/AccessConditionItem.java @@ -0,0 +1,36 @@ +/** + * 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.bulkaccesscontrol.model; + +import java.util.List; + +/** + * @author Mohamed Eskander (mohamed.eskander at 4science.it) + */ +public class AccessConditionItem { + + String mode; + + List accessConditions; + + public String getMode() { + return mode; + } + + public void setMode(String mode) { + this.mode = mode; + } + + public List getAccessConditions() { + return accessConditions; + } + + public void setAccessConditions(List accessConditions) { + this.accessConditions = accessConditions; + } +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/model/AccessControl.java b/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/model/AccessControl.java new file mode 100644 index 000000000000..8e3c11001fa8 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/model/AccessControl.java @@ -0,0 +1,40 @@ +/** + * 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.bulkaccesscontrol.model; + +public class AccessControl { + + AccessConditionItem item; + + AccessConditionBitstream bitstream; + + public AccessControl() { + } + + public AccessControl(AccessConditionItem item, + AccessConditionBitstream bitstream) { + this.item = item; + this.bitstream = bitstream; + } + + public AccessConditionItem getItem() { + return item; + } + + public void setItem(AccessConditionItem item) { + this.item = item; + } + + public AccessConditionBitstream getBitstream() { + return bitstream; + } + + public void setBitstream(AccessConditionBitstream bitstream) { + this.bitstream = bitstream; + } +} 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 4a2addf781b9..b762107a84c5 100644 --- a/dspace-api/src/main/java/org/dspace/authorize/ResourcePolicyServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/authorize/ResourcePolicyServiceImpl.java @@ -232,6 +232,15 @@ public void removePolicies(Context c, DSpaceObject o, String type) throws SQLExc c.restoreAuthSystemState(); } + @Override + public void removePolicies(Context c, DSpaceObject o, String type, int action) + throws SQLException, AuthorizeException { + resourcePolicyDAO.deleteByDsoAndTypeAndAction(c, o, type, action); + c.turnOffAuthorisationSystem(); + contentServiceFactory.getDSpaceObjectService(o).updateLastModified(c, o); + c.restoreAuthSystemState(); + } + @Override public void removeDsoGroupPolicies(Context context, DSpaceObject dso, Group group) throws SQLException, AuthorizeException { diff --git a/dspace-api/src/main/java/org/dspace/authorize/dao/ResourcePolicyDAO.java b/dspace-api/src/main/java/org/dspace/authorize/dao/ResourcePolicyDAO.java index 5c898a5bca61..4e12cd0bfd66 100644 --- a/dspace-api/src/main/java/org/dspace/authorize/dao/ResourcePolicyDAO.java +++ b/dspace-api/src/main/java/org/dspace/authorize/dao/ResourcePolicyDAO.java @@ -39,6 +39,9 @@ public List findByDsoAndType(Context context, DSpaceObject dSpac public List findByDSoAndAction(Context context, DSpaceObject dso, int actionId) throws SQLException; + public void deleteByDsoAndTypeAndAction(Context context, DSpaceObject dSpaceObject, String type, int action) + throws SQLException; + public List findByTypeGroupAction(Context context, DSpaceObject dso, Group group, int action) throws SQLException; diff --git a/dspace-api/src/main/java/org/dspace/authorize/dao/impl/ResourcePolicyDAOImpl.java b/dspace-api/src/main/java/org/dspace/authorize/dao/impl/ResourcePolicyDAOImpl.java index 651c1ad63b6d..26b6bb1d7345 100644 --- a/dspace-api/src/main/java/org/dspace/authorize/dao/impl/ResourcePolicyDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/authorize/dao/impl/ResourcePolicyDAOImpl.java @@ -103,6 +103,19 @@ public List findByDSoAndAction(Context context, DSpaceObject dso return list(context, criteriaQuery, false, ResourcePolicy.class, -1, -1); } + @Override + public void deleteByDsoAndTypeAndAction(Context context, DSpaceObject dso, String type, int actionId) + throws SQLException { + String queryString = "delete from ResourcePolicy where dSpaceObject.id = :dsoId " + + "AND rptype = :rptype AND actionId= :actionId"; + Query query = createQuery(context, queryString); + query.setParameter("dsoId", dso.getID()); + query.setParameter("rptype", type); + query.setParameter("actionId", actionId); + query.executeUpdate(); + + } + @Override public List findByTypeGroupAction(Context context, DSpaceObject dso, Group group, int action) throws SQLException { diff --git a/dspace-api/src/main/java/org/dspace/authorize/service/ResourcePolicyService.java b/dspace-api/src/main/java/org/dspace/authorize/service/ResourcePolicyService.java index 726078d74382..43735fcd6089 100644 --- a/dspace-api/src/main/java/org/dspace/authorize/service/ResourcePolicyService.java +++ b/dspace-api/src/main/java/org/dspace/authorize/service/ResourcePolicyService.java @@ -93,6 +93,9 @@ public List findByTypeGroupActionExceptId(Context context, DSpac public void removePolicies(Context c, DSpaceObject o, String type) throws SQLException, AuthorizeException; + public void removePolicies(Context c, DSpaceObject o, String type, int action) + throws SQLException, AuthorizeException; + public void removeDsoGroupPolicies(Context context, DSpaceObject dso, Group group) throws SQLException, AuthorizeException; diff --git a/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java index de5826fa015b..d529df9ca01d 100644 --- a/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java @@ -940,14 +940,39 @@ public void adjustBundleBitstreamPolicies(Context context, Item item, Collection for (Bitstream bitstream : mybundle.getBitstreams()) { // if come from InstallItem: remove all submission/workflow policies - authorizeService.removeAllPoliciesByDSOAndType(context, bitstream, ResourcePolicy.TYPE_SUBMISSION); - authorizeService.removeAllPoliciesByDSOAndType(context, bitstream, ResourcePolicy.TYPE_WORKFLOW); - addCustomPoliciesNotInPlace(context, bitstream, defaultItemPolicies); - addDefaultPoliciesNotInPlace(context, bitstream, defaultCollectionPolicies); + removeAllPoliciesAndAddDefault(context, bitstream, defaultItemPolicies, defaultCollectionPolicies); } } } + @Override + public void adjustBitstreamPolicies(Context context, Item item, Collection collection , Bitstream bitstream) + throws SQLException, AuthorizeException { + List defaultCollectionPolicies = authorizeService + .getPoliciesActionFilter(context, collection, Constants.DEFAULT_BITSTREAM_READ); + + List defaultItemPolicies = authorizeService.findPoliciesByDSOAndType(context, item, + ResourcePolicy.TYPE_CUSTOM); + if (defaultCollectionPolicies.size() < 1) { + throw new SQLException("Collection " + collection.getID() + + " (" + collection.getHandle() + ")" + + " has no default bitstream READ policies"); + } + + // remove all policies from bitstream, add new ones + removeAllPoliciesAndAddDefault(context, bitstream, defaultItemPolicies, defaultCollectionPolicies); + } + + private void removeAllPoliciesAndAddDefault(Context context, Bitstream bitstream, + List defaultItemPolicies, + List defaultCollectionPolicies) + throws SQLException, AuthorizeException { + authorizeService.removeAllPoliciesByDSOAndType(context, bitstream, ResourcePolicy.TYPE_SUBMISSION); + authorizeService.removeAllPoliciesByDSOAndType(context, bitstream, ResourcePolicy.TYPE_WORKFLOW); + addCustomPoliciesNotInPlace(context, bitstream, defaultItemPolicies); + addDefaultPoliciesNotInPlace(context, bitstream, defaultCollectionPolicies); + } + @Override public void adjustItemPolicies(Context context, Item item, Collection collection) throws SQLException, AuthorizeException { diff --git a/dspace-api/src/main/java/org/dspace/content/service/ItemService.java b/dspace-api/src/main/java/org/dspace/content/service/ItemService.java index b7a479469bf1..02d83808c1ff 100644 --- a/dspace-api/src/main/java/org/dspace/content/service/ItemService.java +++ b/dspace-api/src/main/java/org/dspace/content/service/ItemService.java @@ -507,6 +507,25 @@ public void inheritCollectionDefaultPolicies(Context context, Item item, Collect public void adjustBundleBitstreamPolicies(Context context, Item item, Collection collection) throws SQLException, AuthorizeException; + /** + * Adjust the Bundle and Bitstream policies to reflect what have been defined + * during the submission/workflow. The temporary SUBMISSION and WORKFLOW + * policies are removed and the policies defined at the item and collection + * level are copied and inherited as appropriate. Custom selected Item policies + * are copied to the bundle/bitstream only if no explicit custom policies were + * already applied to the bundle/bitstream. Collection's policies are inherited + * if there are no other policies defined or if the append mode is defined by + * the configuration via the core.authorization.installitem.inheritance-read.append-mode property + * + * @param context DSpace context object + * @param item Item to adjust policies on + * @param collection Collection + * @throws SQLException If database error + * @throws AuthorizeException If authorization error + */ + public void adjustBitstreamPolicies(Context context, Item item, Collection collection, Bitstream bitstream) + throws SQLException, AuthorizeException; + /** * Adjust the Item's policies to reflect what have been defined during the diff --git a/dspace-api/src/main/java/org/dspace/submit/model/AccessConditionOption.java b/dspace-api/src/main/java/org/dspace/submit/model/AccessConditionOption.java index dbbb7bbc5e4d..4feaced075a9 100644 --- a/dspace-api/src/main/java/org/dspace/submit/model/AccessConditionOption.java +++ b/dspace-api/src/main/java/org/dspace/submit/model/AccessConditionOption.java @@ -184,7 +184,7 @@ public void updateResourcePolicy(Context context, ResourcePolicy resourcePolicy) * @param endDate End date of the resource policy. If {@link #getHasEndDate()} * returns false, endDate should be null. Otherwise endDate may not be null. */ - private void validateResourcePolicy(Context context, String name, Date startDate, Date endDate) + public void validateResourcePolicy(Context context, String name, Date startDate, Date endDate) throws SQLException, AuthorizeException, ParseException { if (getHasStartDate() && Objects.isNull(startDate)) { throw new IllegalStateException("The access condition " + getName() + " requires a start date."); diff --git a/dspace-api/src/test/data/dspaceFolder/config/spring/api/scripts.xml b/dspace-api/src/test/data/dspaceFolder/config/spring/api/scripts.xml index 140fe0abf50c..808d22a5bf24 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/spring/api/scripts.xml +++ b/dspace-api/src/test/data/dspaceFolder/config/spring/api/scripts.xml @@ -86,4 +86,9 @@ + + + + + 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 new file mode 100644 index 000000000000..b63d31f52464 --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/app/bulkaccesscontrol/BulkAccessControlIT.java @@ -0,0 +1,437 @@ +/** + * 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.bulkaccesscontrol; + +import static org.dspace.app.matcher.ResourcePolicyMatcher.matches; +import static org.dspace.authorize.ResourcePolicy.TYPE_CUSTOM; +import static org.dspace.core.Constants.READ; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.is; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Iterator; +import java.util.List; +import java.util.stream.Collectors; + +import javax.validation.constraints.AssertTrue; + +import org.apache.commons.io.file.PathUtils; +import org.dspace.AbstractIntegrationTestWithDatabase; +import org.dspace.app.launcher.ScriptLauncher; +import org.dspace.app.matcher.ResourcePolicyMatcher; +import org.dspace.app.scripts.handler.impl.TestDSpaceRunnableHandler; +import org.dspace.authorize.ResourcePolicy; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.ItemBuilder; +import org.dspace.content.Collection; +import org.dspace.content.Community; +import org.dspace.content.Item; +import org.dspace.content.factory.ContentServiceFactory; +import org.dspace.content.service.ItemService; +import org.dspace.core.Constants; +import org.dspace.eperson.Group; +import org.dspace.eperson.GroupTest; +import org.dspace.eperson.factory.EPersonServiceFactory; +import org.dspace.eperson.service.GroupService; +import org.dspace.matcher.DateMatcher; +import org.dspace.util.MultiFormatDateParser; +import org.dspace.utils.DSpace; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Basic integration testing for the Bulk Access conditions Feature{@link BulkAccessControl}. + * + * @author Mohamed Eskander (mohamed.eskander at 4science.it) + */ +public class BulkAccessControlIT extends AbstractIntegrationTestWithDatabase { + private Path tempDir; + private String tempFilePath; + private Collection collection; + + private ItemService itemService = ContentServiceFactory.getInstance().getItemService(); + private GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); + + @Before + @Override + public void setUp() throws Exception { + + super.setUp(); + + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + collection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection") + .withEntityType("Publication") + .build(); + + context.restoreAuthSystemState(); + + tempDir = Files.createTempDirectory("bulkAccessTest"); + tempFilePath = tempDir + "/bulk-access.json"; + } + + @After + @Override + public void destroy() throws Exception { + PathUtils.deleteDirectory(tempDir); + super.destroy(); + } + + @Test + public void performBulkAccessWithoutRequiredParamTest() throws Exception { + + buildJsonFile(""); + + String[] args = new String[] {"bulk-access-control", "-f", tempFilePath}; + + TestDSpaceRunnableHandler testDSpaceRunnableHandler = new TestDSpaceRunnableHandler(); + ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl), testDSpaceRunnableHandler, kernelImpl); + + assertThat(testDSpaceRunnableHandler.getErrorMessages(), hasSize(1)); + assertThat(testDSpaceRunnableHandler.getWarningMessages(), empty()); + } + + @Test + public void performBulkAccessWithEmptyJsonTest() throws Exception { + context.turnOffAuthorisationSystem(); + + Community community = CommunityBuilder.createCommunity(context) + .withName("community") + .build(); + + Collection collection = CollectionBuilder.createCollection(context, community) + .withName("collection") + .build(); + + Item item = ItemBuilder.createItem(context, collection).withTitle("title").build(); + + context.restoreAuthSystemState(); + + buildJsonFile(""); + + String[] args = new String[] {"bulk-access-control", "-u", item.getID().toString(), "-f", tempFilePath}; + + TestDSpaceRunnableHandler testDSpaceRunnableHandler = new TestDSpaceRunnableHandler(); + ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl), testDSpaceRunnableHandler, kernelImpl); + + assertThat(testDSpaceRunnableHandler.getErrorMessages(), hasSize(1)); + assertThat(testDSpaceRunnableHandler.getWarningMessages(), empty()); + } + + @Test + public void performBulkAccessWithWrongModeOfItemValueTest() throws Exception { + context.turnOffAuthorisationSystem(); + + Community community = CommunityBuilder.createCommunity(context) + .withName("community") + .build(); + + Collection collection = CollectionBuilder.createCollection(context, community) + .withName("collection") + .build(); + + Item item = ItemBuilder.createItem(context, collection).build(); + + context.restoreAuthSystemState(); + + String json = "{ \"item\": {\n" + + " \"mode\": \"wrong\",\n" + + " \"accessConditions\": [\n" + + " {\n" + + " \"name\": \"openaccess\"\n" + + " }\n" + + " ]\n" + + " }}\n"; + + buildJsonFile(json); + + String[] args = new String[] {"bulk-access-control", "-u", item.getID().toString(), "-f", tempFilePath}; + + TestDSpaceRunnableHandler testDSpaceRunnableHandler = new TestDSpaceRunnableHandler(); + ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl), testDSpaceRunnableHandler, kernelImpl); + + assertThat(testDSpaceRunnableHandler.getErrorMessages(), hasSize(1)); + assertThat(testDSpaceRunnableHandler.getWarningMessages(), empty()); + } + + @Test + public void performBulkAccessWithWrongModeOfBitstreamValueTest() throws Exception { + context.turnOffAuthorisationSystem(); + + Community community = CommunityBuilder.createCommunity(context) + .withName("community") + .build(); + + Collection collection = CollectionBuilder.createCollection(context, community) + .withName("collection") + .build(); + + Item item = ItemBuilder.createItem(context, collection).build(); + + context.restoreAuthSystemState(); + + String json = "{ \"bitstream\": {\n" + + " \"mode\": \"wrong\",\n" + + " \"accessConditions\": [\n" + + " {\n" + + " \"name\": \"openaccess\"\n" + + " }\n" + + " ]\n" + + " }}\n"; + + buildJsonFile(json); + + String[] args = new String[] {"bulk-access-control", "-u", item.getID().toString(), "-f", tempFilePath}; + + TestDSpaceRunnableHandler testDSpaceRunnableHandler = new TestDSpaceRunnableHandler(); + ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl), testDSpaceRunnableHandler, kernelImpl); + + assertThat(testDSpaceRunnableHandler.getErrorMessages(), hasSize(1)); + assertThat(testDSpaceRunnableHandler.getWarningMessages(), empty()); + } + + @Test + public void performBulkAccessWithNotFoundAccessConditionNameTest() throws Exception { + context.turnOffAuthorisationSystem(); + Community community = CommunityBuilder.createCommunity(context) + .withName("community") + .build(); + + Collection collection = CollectionBuilder.createCollection(context, community) + .withName("collection") + .build(); + + Item item = ItemBuilder.createItem(context, collection).build(); + + context.restoreAuthSystemState(); + + String json = "{ \"item\": {\n" + + " \"mode\": \"add\",\n" + + " \"accessConditions\": [\n" + + " {\n" + + " \"name\": \"wrongAccess\"\n" + + " }\n" + + " ]\n" + + " }}\n"; + + buildJsonFile(json); + + String[] args = new String[] {"bulk-access-control", "-u", item.getID().toString(), "-f", tempFilePath}; + + TestDSpaceRunnableHandler testDSpaceRunnableHandler = new TestDSpaceRunnableHandler(); + ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl), testDSpaceRunnableHandler, kernelImpl); + + assertThat(testDSpaceRunnableHandler.getErrorMessages(), hasSize(1)); + assertThat(testDSpaceRunnableHandler.getWarningMessages(), empty()); + + context.restoreAuthSystemState(); + } + + @Test + public void performBulkAccessWithInvalidAccessConditionDateTest() throws Exception { + context.turnOffAuthorisationSystem(); + + Community community = CommunityBuilder.createCommunity(context) + .withName("community") + .build(); + + Collection collection = CollectionBuilder.createCollection(context, community) + .withName("collection") + .build(); + + Item item = ItemBuilder.createItem(context, collection).build(); + + context.restoreAuthSystemState(); + + String jsonOne = "{ \"item\": {\n" + + " \"mode\": \"add\",\n" + + " \"accessConditions\": [\n" + + " {\n" + + " \"name\": \"embargo\",\n" + + " \"endDate\": \"2024-06-24T23:59:59.999+0000\"\n" + + " }\n" + + " ]\n" + + " }}\n"; + + buildJsonFile(jsonOne); + + String[] args = new String[] {"bulk-access-control", "-u", item.getID().toString(), "-f", tempFilePath}; + + TestDSpaceRunnableHandler testDSpaceRunnableHandler = new TestDSpaceRunnableHandler(); + ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl), testDSpaceRunnableHandler, kernelImpl); + + assertThat(testDSpaceRunnableHandler.getErrorMessages(), hasSize(1)); + assertThat(testDSpaceRunnableHandler.getWarningMessages(), empty()); + + String jsonTwo = "{ \"item\": {\n" + + " \"mode\": \"add\",\n" + + " \"accessConditions\": [\n" + + " {\n" + + " \"name\": \"lease\",\n" + + " \"startDate\": \"2024-06-24T23:59:59.999+0000\"\n" + + " }\n" + + " ]\n" + + " }}\n"; + + buildJsonFile(jsonTwo); + + args = new String[] {"bulk-access-control", "-u", item.getID().toString(), "-f", tempFilePath}; + + testDSpaceRunnableHandler = new TestDSpaceRunnableHandler(); + ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl), testDSpaceRunnableHandler, kernelImpl); + + assertThat(testDSpaceRunnableHandler.getErrorMessages(), hasSize(1)); + assertThat(testDSpaceRunnableHandler.getWarningMessages(), empty()); + } + + @Test + public void performBulkAccessWithValidJsonTest() throws Exception { + context.turnOffAuthorisationSystem(); + + Community parentCommunity = CommunityBuilder.createCommunity(context) + .withName("parent community") + .build(); + + Community subCommunityOne = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("sub community one") + .build(); + + Community subCommunityTwo = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("sub community two") + .build(); + + Community subCommunityThree = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("sub community two") + .build(); + + Collection collectionOne = CollectionBuilder.createCollection(context, subCommunityOne) + .withName("collection one") + .build(); + + Collection collectionTwo = CollectionBuilder.createCollection(context, subCommunityTwo) + .withName("collection two") + .build(); + + Collection collectionThree = CollectionBuilder.createCollection(context, subCommunityThree) + .withName("collection three") + .build(); + + ItemBuilder.createItem(context, collectionOne).build(); + + ItemBuilder.createItem(context, collectionTwo).build(); + + Item itemThree = ItemBuilder.createItem(context, collectionThree).withTitle("item three title").build(); + + Item itemFour = ItemBuilder.createItem(context, collectionThree).withTitle("item four title").build(); + + context.restoreAuthSystemState(); + + String jsonOne = "{ \"item\": {\n" + + " \"mode\": \"replace\",\n" + + " \"accessConditions\": [\n" + + " {\n" + + " \"name\": \"embargo\",\n" + + " \"startDate\": \"2024-06-24T00:00:00.000Z\"\n" + + " }\n" + + " ]\n" + + " }}\n"; + + buildJsonFile(jsonOne); + + String[] args = new String[] { + "bulk-access-control", + "-u", subCommunityOne.getID().toString(), + "-u", collectionTwo.getID().toString(), + "-u", itemThree.getID().toString(), + "-f", tempFilePath + }; + + TestDSpaceRunnableHandler testDSpaceRunnableHandler = new TestDSpaceRunnableHandler(); + ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl), testDSpaceRunnableHandler, kernelImpl); + + assertThat(testDSpaceRunnableHandler.getErrorMessages(), empty()); + assertThat(testDSpaceRunnableHandler.getWarningMessages(), empty()); + + Iterator itemIteratorOne = itemService.findByCollection(context, collectionOne); + Iterator itemIteratorTwo = itemService.findByCollection(context, collectionTwo); + itemThree = context.reloadEntity(itemThree); + itemFour = context.reloadEntity(itemFour); + + Group anonymousGroup = groupService.findByName(context, Group.ANONYMOUS); + + +// matchItemsResourcePolicies(itemIteratorOne, anonymousGroup, "embargo", TYPE_CUSTOM, "2024-06-24", null); +// matchItemsResourcePolicies(itemIteratorTwo, anonymousGroup, "embargo", TYPE_CUSTOM, "2024-06-24", null); +// matchItemResourcePolicies(itemThree, anonymousGroup, "embargo", TYPE_CUSTOM, "2024-06-24", null); + + assertThat(itemThree.getResourcePolicies(), hasSize(2)); + assertThat(itemThree.getResourcePolicies(), containsInAnyOrder( + matches(Constants.READ, anonymousGroup, ResourcePolicy.TYPE_INHERITED), + matches(READ, anonymousGroup, "embargo", TYPE_CUSTOM, "2024-06-24T00:00:00.000Z", null, null) + )); + + // just a note here is working fine + assertThat(itemThree.getResourcePolicies(), hasItem( + matches(READ, anonymousGroup, "embargo", TYPE_CUSTOM, + itemThree.getResourcePolicies().get(0).getStartDate(), null, null) + )); + + assertThat(itemFour.getResourcePolicies().size(), is(1)); + assertThat(itemFour.getResourcePolicies(), hasItem( + matches(Constants.READ, anonymousGroup, ResourcePolicy.TYPE_INHERITED) + )); + + + + + } + + private void matchItemsResourcePolicies( + Iterator itemIterator, Group group, String rpName, String rpType, String startDate, String endDate) { + while (itemIterator.hasNext()) { + Item item = itemIterator.next(); + matchItemResourcePolicies(item, group, rpName, rpType, startDate, endDate); + } + } + + private void matchItemResourcePolicies( + Item item, Group group, String rpName, String rpType, String startDate, String endDate) { + + assertThat(item.getResourcePolicies(), hasItem( + matches(READ, group, rpName, rpType, startDate, endDate, null))); + } + + private void buildJsonFile(String json) throws IOException { + File file = new File(tempDir + "/bulk-access.json"); + Path path = Paths.get(file.getAbsolutePath()); + Files.writeString(path, json, StandardCharsets.UTF_8); + } +} diff --git a/dspace-api/src/test/java/org/dspace/app/matcher/ResourcePolicyMatcher.java b/dspace-api/src/test/java/org/dspace/app/matcher/ResourcePolicyMatcher.java new file mode 100644 index 000000000000..26ea7dcb5a35 --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/app/matcher/ResourcePolicyMatcher.java @@ -0,0 +1,126 @@ +/** + * 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.matcher; + +import static org.dspace.util.MultiFormatDateParser.parse; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.nullValue; + +import java.util.Date; + +import org.dspace.authorize.ResourcePolicy; +import org.dspace.eperson.EPerson; +import org.dspace.eperson.Group; +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hamcrest.TypeSafeMatcher; + +/** + * Implementation of {@link Matcher} to match a ResourcePolicy. + * + * @author Luca Giamminonni (luca.giamminonni at 4science.it) + * + */ +public class ResourcePolicyMatcher extends TypeSafeMatcher { + + private final Matcher actionId; + + private final Matcher ePerson; + + private final Matcher group; + + private final Matcher rptype; + + private final Matcher rpName; + + private final Matcher description; + + private final Matcher startDate; + + private final Matcher endDate; + + public ResourcePolicyMatcher(Matcher actionId, Matcher ePerson, Matcher group, + Matcher rpName, Matcher rptype, Matcher startDate, + Matcher endDate, Matcher description) { + this.actionId = actionId; + this.ePerson = ePerson; + this.group = group; + this.rptype = rptype; + this.rpName = rpName; + this.description = description; + this.startDate = startDate; + this.endDate = endDate; + } + + @Override + public void describeTo(Description description) { + description.appendText("Resource policy with action id ").appendDescriptionOf(actionId) + .appendText(" and EPerson ").appendDescriptionOf(ePerson) + .appendText(" and Group ").appendDescriptionOf(group) + .appendText(" and rpType ").appendDescriptionOf(rptype) + .appendText(" and rpName ").appendDescriptionOf(rpName) + .appendText(" and description ").appendDescriptionOf(this.description) + .appendText(" and start date ").appendDescriptionOf(startDate) + .appendText(" and end date ").appendDescriptionOf(endDate); + } + + public static ResourcePolicyMatcher matches(int actionId, EPerson ePerson, String rptype) { + return new ResourcePolicyMatcher(is(actionId), is(ePerson), nullValue(Group.class), + any(String.class), is(rptype), any(Date.class), any(Date.class), any(String.class)); + } + + public static ResourcePolicyMatcher matches(int actionId, EPerson ePerson, String rpName, String rptype) { + return new ResourcePolicyMatcher(is(actionId), is(ePerson), nullValue(Group.class), + is(rpName), is(rptype), any(Date.class), any(Date.class), any(String.class)); + } + + public static ResourcePolicyMatcher matches(int actionId, Group group, String rptype) { + return new ResourcePolicyMatcher(is(actionId), nullValue(EPerson.class), is(group), + any(String.class), is(rptype), any(Date.class), any(Date.class), any(String.class)); + } + + public static ResourcePolicyMatcher matches(int actionId, Group group, String rpName, String rptype) { + return new ResourcePolicyMatcher(is(actionId), nullValue(EPerson.class), is(group), is(rpName), + is(rptype), any(Date.class), any(Date.class), any(String.class)); + } + + public static ResourcePolicyMatcher matches(int actionId, Group group, String rpName, String rptype, + String description) { + return new ResourcePolicyMatcher(is(actionId), nullValue(EPerson.class), is(group), is(rpName), + is(rptype), any(Date.class), any(Date.class), is(description)); + } + + public static ResourcePolicyMatcher matches(int actionId, Group group, String rpName, String rpType, Date startDate, + Date endDate, String description) { + return new ResourcePolicyMatcher(is(actionId), nullValue(EPerson.class), is(group), is(rpName), + is(rpType), is(startDate), is(endDate), is(description)); + } + + public static ResourcePolicyMatcher matches(int actionId, Group group, String rpName, String rpType, + String startDate, String endDate, String description) { + return matches(actionId, group, rpName, rpType, startDate != null ? parse(startDate) : null, + endDate != null ? parse(endDate) : null, description); + } + + @Override + protected boolean matchesSafely(ResourcePolicy resourcePolicy) { + return actionId.matches(resourcePolicy.getAction()) + && ePerson.matches(resourcePolicy.getEPerson()) + && group.matches(resourcePolicy.getGroup()) + && rptype.matches(resourcePolicy.getRpType()) + && rpName.matches(resourcePolicy.getRpName()) + && description.matches(resourcePolicy.getRpDescription()) + && startDate.matches(resourcePolicy.getStartDate()) + && endDate.matches(resourcePolicy.getEndDate()); + } + + private static Matcher any(Class clazz) { + return LambdaMatcher.matches((obj) -> true, "any value"); + } + +} diff --git a/dspace-server-webapp/src/test/data/dspaceFolder/config/spring/rest/scripts.xml b/dspace-server-webapp/src/test/data/dspaceFolder/config/spring/rest/scripts.xml index fd218aa77a8d..79927833d26e 100644 --- a/dspace-server-webapp/src/test/data/dspaceFolder/config/spring/rest/scripts.xml +++ b/dspace-server-webapp/src/test/data/dspaceFolder/config/spring/rest/scripts.xml @@ -43,4 +43,9 @@ + + + + + diff --git a/dspace/config/spring/api/scripts.xml b/dspace/config/spring/api/scripts.xml index 19f558dab6d2..56eacbfff29c 100644 --- a/dspace/config/spring/api/scripts.xml +++ b/dspace/config/spring/api/scripts.xml @@ -86,4 +86,9 @@ + + + + + diff --git a/dspace/config/spring/rest/scripts.xml b/dspace/config/spring/rest/scripts.xml index 1748c0fb4516..eda8c579a89c 100644 --- a/dspace/config/spring/rest/scripts.xml +++ b/dspace/config/spring/rest/scripts.xml @@ -63,4 +63,10 @@ + + + + + + From 0528b4dd30876c790bd81eda12e7dcb36ac9f602 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Thu, 11 May 2023 11:16:16 +0200 Subject: [PATCH 149/510] 100414: Missing search result statistics code cleanup + extra test --- .../rest/converter/SearchEventConverter.java | 31 ++++++++-- .../app/rest/model/SearchEventRest.java | 10 +-- .../app/rest/utils/DSpaceObjectResolver.java | 61 ------------------- .../app/rest/SearchEventRestRepositoryIT.java | 61 ++++++++++++++++++- 4 files changed, 88 insertions(+), 75 deletions(-) delete mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/DSpaceObjectResolver.java diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SearchEventConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SearchEventConverter.java index a8203e272f29..126d37ba1ace 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SearchEventConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SearchEventConverter.java @@ -7,16 +7,20 @@ */ package org.dspace.app.rest.converter; +import java.sql.SQLException; +import java.util.Arrays; import java.util.LinkedList; import java.util.List; import javax.servlet.http.HttpServletRequest; +import org.apache.log4j.Logger; import org.dspace.app.rest.model.PageRest; import org.dspace.app.rest.model.SearchEventRest; import org.dspace.app.rest.model.SearchResultsRest; -import org.dspace.app.rest.utils.DSpaceObjectResolver; import org.dspace.app.rest.utils.ScopeResolver; +import org.dspace.app.util.service.DSpaceObjectUtils; import org.dspace.content.DSpaceObject; +import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.discovery.IndexableObject; import org.dspace.usage.UsageEvent; @@ -26,22 +30,37 @@ @Component public class SearchEventConverter { + /* Log4j logger */ + private static final Logger log = Logger.getLogger(SearchEventConverter.class); @Autowired private ScopeResolver scopeResolver; @Autowired - private DSpaceObjectResolver dSpaceObjectResolver; + private DSpaceObjectUtils dSpaceObjectUtils; + + private final Integer[] allowedClickedObjectTypes = + new Integer[]{Constants.COMMUNITY, Constants.COLLECTION, Constants.ITEM}; public UsageSearchEvent convert(Context context, HttpServletRequest request, SearchEventRest searchEventRest) { UsageSearchEvent usageSearchEvent = new UsageSearchEvent(UsageEvent.Action.SEARCH, request, context, null); usageSearchEvent.setQuery(searchEventRest.getQuery()); usageSearchEvent.setDsoType(searchEventRest.getDsoType()); - if (searchEventRest.getObject() != null) { - IndexableObject object = dSpaceObjectResolver.resolveObject(context, searchEventRest.getObject()); - if (object != null && object.getIndexedObject() instanceof DSpaceObject) { - usageSearchEvent.setObject((DSpaceObject) object.getIndexedObject()); + if (searchEventRest.getClickedObject() != null) { + try { + DSpaceObject clickedObject = + dSpaceObjectUtils.findDSpaceObject(context, searchEventRest.getClickedObject()); + if (clickedObject != null && + Arrays.asList(allowedClickedObjectTypes).contains(clickedObject.getType())) { + usageSearchEvent.setObject(clickedObject); + } else { + throw new IllegalArgumentException("UUID " + searchEventRest.getClickedObject() + + " was expected to resolve to a Community, Collection or Item, but didn't resolve to any"); + } + } catch (SQLException e) { + log.warn("Unable to retrieve DSpace Object with ID " + searchEventRest.getClickedObject() + + " from the database", e); } } if (searchEventRest.getScope() != null) { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SearchEventRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SearchEventRest.java index 637acb9bfd68..46827711f2ea 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SearchEventRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SearchEventRest.java @@ -25,7 +25,7 @@ public class SearchEventRest extends BaseObjectRest { private UUID scope; private String configuration; private String dsoType; - private UUID object; + private UUID clickedObject; private List appliedFilters; private SearchResultsRest.Sorting sort; private PageRest page; @@ -99,11 +99,11 @@ public void setDsoType(String dsoType) { this.dsoType = dsoType; } - public UUID getObject() { - return object; + public UUID getClickedObject() { + return clickedObject; } - public void setObject(UUID object) { - this.object = object; + public void setClickedObject(UUID clickedObject) { + this.clickedObject = clickedObject; } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/DSpaceObjectResolver.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/DSpaceObjectResolver.java deleted file mode 100644 index 7ded06bdf52a..000000000000 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/DSpaceObjectResolver.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.dspace.app.rest.utils; - -import java.sql.SQLException; -import java.util.UUID; - -import org.apache.log4j.Logger; -import org.dspace.content.service.CollectionService; -import org.dspace.content.service.CommunityService; -import org.dspace.content.service.ItemService; -import org.dspace.core.Context; -import org.dspace.discovery.IndexableObject; -import org.dspace.discovery.indexobject.IndexableCollection; -import org.dspace.discovery.indexobject.IndexableCommunity; -import org.dspace.discovery.indexobject.IndexableItem; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -@Component -public class DSpaceObjectResolver { - /* Log4j logger */ - private static final Logger log = Logger.getLogger(DSpaceObjectResolver.class); - - @Autowired - ItemService itemService; - - @Autowired - CollectionService collectionService; - - @Autowired - CommunityService communityService; - - public IndexableObject resolveObject(Context context, UUID uuid) { - IndexableObject object = null; - if (uuid != null) { - try { - object = new IndexableCommunity(communityService.find(context, uuid)); - if (object.getIndexedObject() == null) { - object = new IndexableCollection(collectionService.find(context, uuid)); - } - if (object.getIndexedObject() == null) { - object = new IndexableItem(itemService.find(context, uuid)); - } - if (object.getIndexedObject() == null) { - throw new IllegalArgumentException("UUID " + uuid + " is expected to resolve to a Community, " + - "Collection or Item, but didn't resolve to any"); - } - } catch (SQLException e) { - log.warn("Unable to retrieve DSpace Object with ID " + uuid + " from the database", e); - } - } - return object; - } - -} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SearchEventRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SearchEventRestRepositoryIT.java index 63ca4b735dae..978d8feb58b9 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SearchEventRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SearchEventRestRepositoryIT.java @@ -413,7 +413,7 @@ public void postTestSuccesEmptyQuery() throws Exception { } @Test - public void postTestWithObjectSuccess() throws Exception { + public void postTestWithClickedObjectSuccess() throws Exception { context.turnOffAuthorisationSystem(); @@ -441,10 +441,10 @@ public void postTestWithObjectSuccess() throws Exception { SearchEventRest searchEventRest = new SearchEventRest(); searchEventRest.setQuery("test"); - searchEventRest.setScope(publicItem1.getID()); + searchEventRest.setScope(col1.getID()); searchEventRest.setConfiguration("default"); searchEventRest.setDsoType("item"); - searchEventRest.setObject(publicItem1.getID()); + searchEventRest.setClickedObject(publicItem1.getID()); SearchResultsRest.Sorting sort = new SearchResultsRest.Sorting("title", "desc"); searchEventRest.setSort(sort); @@ -466,4 +466,59 @@ public void postTestWithObjectSuccess() throws Exception { .andExpect(status().isCreated()); } + + @Test + public void postTestWithClickedObjectNotExisting() 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(); + + context.restoreAuthSystemState(); + + SearchEventRest searchEventRest = new SearchEventRest(); + + searchEventRest.setQuery("test"); + searchEventRest.setScope(col1.getID()); + searchEventRest.setConfiguration("default"); + searchEventRest.setDsoType("item"); + searchEventRest.setClickedObject(UUID.randomUUID()); + + SearchResultsRest.Sorting sort = new SearchResultsRest.Sorting("title", "desc"); + searchEventRest.setSort(sort); + + PageRest pageRest = new PageRest(5, 20, 4, 1); + searchEventRest.setPage(pageRest); + + SearchResultsRest.AppliedFilter appliedFilter = + new SearchResultsRest.AppliedFilter("author", "contains", "test","test"); + List appliedFilterList = new LinkedList<>(); + appliedFilterList.add(appliedFilter); + searchEventRest.setAppliedFilters(appliedFilterList); + + ObjectMapper mapper = new ObjectMapper(); + + getClient().perform(post("/api/statistics/searchevents") + .content(mapper.writeValueAsBytes(searchEventRest)) + .contentType(contentType)) + .andExpect(status().isBadRequest()); + + } } From 9e013ef97f40d0840435c289133bc4c51e8eeeb4 Mon Sep 17 00:00:00 2001 From: Mohamed Saber Eskander Date: Thu, 11 May 2023 14:10:34 +0300 Subject: [PATCH 150/510] refactoring BulkAccessControl --- .../bulkaccesscontrol/BulkAccessControl.java | 191 ++++++++++-------- .../model/AccessConditionBitstream.java | 4 + .../model/AccessConditionItem.java | 4 + 3 files changed, 116 insertions(+), 83 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/BulkAccessControl.java b/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/BulkAccessControl.java index dc3a6da56ba4..af9d4e6c1f0e 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 @@ -25,10 +25,12 @@ import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.commons.cli.ParseException; +import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.dspace.app.bulkaccesscontrol.exception.BulkAccessControlException; import org.dspace.app.bulkaccesscontrol.model.AccessCondition; import org.dspace.app.bulkaccesscontrol.model.AccessConditionBitstream; +import org.dspace.app.bulkaccesscontrol.model.AccessConditionItem; import org.dspace.app.bulkaccesscontrol.model.AccessControl; import org.dspace.app.bulkaccesscontrol.model.BulkAccessConditionConfiguration; import org.dspace.app.bulkaccesscontrol.service.BulkAccessConditionConfigurationService; @@ -36,16 +38,11 @@ import org.dspace.app.util.service.DSpaceObjectUtils; import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.factory.AuthorizeServiceFactory; -import org.dspace.authorize.service.AuthorizeService; import org.dspace.authorize.service.ResourcePolicyService; import org.dspace.content.Bitstream; import org.dspace.content.DSpaceObject; import org.dspace.content.Item; import org.dspace.content.factory.ContentServiceFactory; -import org.dspace.content.service.BitstreamService; -import org.dspace.content.service.BundleService; -import org.dspace.content.service.CollectionService; -import org.dspace.content.service.CommunityService; import org.dspace.content.service.ItemService; import org.dspace.core.Constants; import org.dspace.core.Context; @@ -54,16 +51,12 @@ import org.dspace.discovery.SearchServiceException; import org.dspace.discovery.SearchUtils; import org.dspace.discovery.indexobject.IndexableItem; -import org.dspace.discovery.indexobject.factory.IndexObjectFactoryFactory; import org.dspace.eperson.EPerson; import org.dspace.eperson.factory.EPersonServiceFactory; -import org.dspace.handle.factory.HandleServiceFactory; -import org.dspace.handle.service.HandleService; import org.dspace.scripts.DSpaceRunnable; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; import org.dspace.submit.model.AccessConditionOption; -import org.dspace.util.MultiFormatDateParser; import org.dspace.utils.DSpace; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -82,30 +75,16 @@ public class BulkAccessControl extends DSpaceRunnable uploadAccessConditions; + private final String ADD_MODE = "add"; + + private final String REPLACE_MODE = "replace"; + @Override @SuppressWarnings("unchecked") public void setup() throws ParseException { this.searchService = SearchUtils.getSearchService(); - this.indexObjectServiceFactory = IndexObjectFactoryFactory.getInstance(); - this.communityService = ContentServiceFactory.getInstance().getCommunityService(); - this.collectionService = ContentServiceFactory.getInstance().getCollectionService(); this.itemService = ContentServiceFactory.getInstance().getItemService(); - this.authorizeService = AuthorizeServiceFactory.getInstance().getAuthorizeService(); - this.bundleService = ContentServiceFactory.getInstance().getBundleService(); - this.bitstreamService = ContentServiceFactory.getInstance().getBitstreamService(); this.resourcePolicyService = AuthorizeServiceFactory.getInstance().getResourcePolicyService(); this.configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); this.bulkAccessConditionConfigurationService = new DSpace().getServiceManager().getServiceByName( @@ -190,33 +167,80 @@ public void internalRun() throws Exception { } } - private void validate(AccessControl accessControl) { + private void validate(AccessControl accessControl) throws SQLException { - if (Objects.isNull(accessControl.getItem()) && Objects.isNull(accessControl.getBitstream())) { - handler.logError("item or bitstream nodes must be provided"); + AccessConditionItem item = accessControl.getItem(); + AccessConditionBitstream bitstream = accessControl.getBitstream(); + + if (Objects.isNull(item) && Objects.isNull(bitstream)) { + handler.logError("item or bitstream node must be provided"); + throw new BulkAccessControlException("item or bitstream node must be provided"); } - if (Objects.nonNull(accessControl.getItem())) { - if (StringUtils.isNotEmpty(accessControl.getItem().getMode()) && - !(accessControl.getItem().getMode().equals("add") || - accessControl.getItem().getMode().equals("replace"))) { - handler.logError("wrong value for item mode<" + accessControl.getItem().getMode() + ">"); - } + if (Objects.nonNull(item)) { + validateItemNode(item); + } - for (AccessCondition accessCondition : accessControl.getItem().getAccessConditions()) { - validateAccessCondition(accessCondition); - } + if (Objects.nonNull(bitstream)) { + validateBitstreamNode(bitstream); } + } - if (Objects.nonNull(accessControl.getBitstream())) { - if (StringUtils.isNotEmpty(accessControl.getBitstream().getMode()) && - !(accessControl.getBitstream().getMode().equals("add") || - accessControl.getBitstream().getMode().equals("replace"))) { - handler.logError("wrong value for bitstream mode<" + accessControl.getBitstream().getMode() + ">"); - } + private void validateItemNode(AccessConditionItem item) { + String mode = item.getMode(); + List accessConditions = item.getAccessConditions(); + + if (StringUtils.isEmpty(mode)) { + handler.logError("item mode node must be provided"); + throw new BulkAccessControlException("item mode node must be provided"); + } else if (!(StringUtils.equalsAny(mode, ADD_MODE, REPLACE_MODE))) { + handler.logError("wrong value for item mode<" + mode + ">"); + throw new BulkAccessControlException("wrong value for item mode<" + mode + ">"); + } else if (ADD_MODE.equals(mode) && CollectionUtils.isEmpty(accessConditions)) { + handler.logError("accessConditions of item must be provided with mode<" + ADD_MODE + ">"); + throw new BulkAccessControlException( + "accessConditions of item must be provided with mode<" + ADD_MODE + ">"); + } + + for (AccessCondition accessCondition : accessConditions) { + validateAccessCondition(accessCondition); + } + } + + private void validateBitstreamNode(AccessConditionBitstream bitstream) throws SQLException { + String mode = bitstream.getMode(); + List accessConditions = bitstream.getAccessConditions(); + + if (StringUtils.isEmpty(mode)) { + handler.logError("bitstream mode node must be provided"); + throw new BulkAccessControlException("bitstream mode node must be provided"); + } else if (!(StringUtils.equalsAny(mode, ADD_MODE, REPLACE_MODE))) { + handler.logError("wrong value for bitstream mode<" + mode + ">"); + throw new BulkAccessControlException("wrong value for bitstream mode<" + mode + ">"); + } else if (ADD_MODE.equals(mode) && CollectionUtils.isEmpty(accessConditions)) { + handler.logError("accessConditions of bitstream must be provided with mode<" + ADD_MODE + ">"); + throw new BulkAccessControlException( + "accessConditions of bitstream must be provided with mode<" + ADD_MODE + ">"); + } + + validateConstraint(bitstream); + + for (AccessCondition accessCondition : bitstream.getAccessConditions()) { + validateAccessCondition(accessCondition); + } + } + + private void validateConstraint(AccessConditionBitstream bitstream) throws SQLException { + if (uuids.length > 1 && containsConstraints(bitstream)) { + handler.logError("constraint isn't supported when multiple uuids are provided"); + throw new BulkAccessControlException("constraint isn't supported when multiple uuids are provided"); + } else { + DSpaceObject dso = + dSpaceObjectUtils.findDSpaceObject(context, UUID.fromString(uuids[0])); - for (AccessCondition accessCondition : accessControl.getBitstream().getAccessConditions()) { - validateAccessCondition(accessCondition); + if (Objects.nonNull(dso) && dso.getType() != Constants.ITEM) { + handler.logError("constraint is not supported when uuid isn't an Item"); + throw new BulkAccessControlException("constraint is not supported when uuid isn't an Item"); } } } @@ -225,14 +249,14 @@ private void validateAccessCondition(AccessCondition accessCondition) { if (!itemAccessConditions.containsKey(accessCondition.getName())) { handler.logError("wrong access condition <" + accessCondition.getName() + ">"); - throw new IllegalArgumentException("wrong access condition <" + accessCondition.getName() + ">"); + throw new BulkAccessControlException("wrong access condition <" + accessCondition.getName() + ">"); } try { itemAccessConditions.get(accessCondition.getName()).validateResourcePolicy( context, accessCondition.getName(), accessCondition.getStartDate(), accessCondition.getEndDate()); } catch (Exception e) { - handler.logError("invalid access condition"); + handler.logError("invalid access condition" + e.getMessage()); handler.handleException(e); } } @@ -262,10 +286,10 @@ public void updateItemsAndBitstreamsPolices(AccessControl accessControl) context.commit(); context.uncacheEntity(item); - counter++; if (counter == limit) { + counter = 0; start += limit; itemIterator = findItems(query, start, limit); } @@ -273,10 +297,11 @@ public void updateItemsAndBitstreamsPolices(AccessControl accessControl) } private String buildSolrQuery(String[] uuids) throws SQLException { - HandleService handleService = HandleServiceFactory.getInstance().getHandleService(); String [] query = new String[uuids.length]; + for (int i = 0 ; i < query.length ; i++) { DSpaceObject dso = dSpaceObjectUtils.findDSpaceObject(context, UUID.fromString(uuids[i])); + if (dso.getType() == Constants.COMMUNITY) { query[i] = "location.comm:" + dso.getID(); } else if (dso.getType() == Constants.COLLECTION) { @@ -314,7 +339,7 @@ private DiscoverQuery buildDiscoveryQuery(String query, int start, int limit) { private void updateItemPolicies(Item item, AccessControl accessControl) throws SQLException, AuthorizeException { - if ("replace".equals(accessControl.getItem().getMode())) { + if (REPLACE_MODE.equals(accessControl.getItem().getMode())) { removeReadPolicies(item, TYPE_CUSTOM); removeReadPolicies(item, TYPE_INHERITED); } @@ -323,8 +348,10 @@ private void updateItemPolicies(Item item, AccessControl accessControl) throws S } private void setItemPolicies(Item item, AccessControl accessControl) throws SQLException, AuthorizeException { - if (isAppendModeDisabled() && item.isArchived()) { - // change to add + + AccessConditionItem itemControl = accessControl.getItem(); + + if (isAdjustPoliciesNeeded(item, itemControl.getMode(), itemControl.getAccessConditions())) { itemService.adjustItemPolicies(context, item, item.getOwningCollection()); } @@ -335,27 +362,27 @@ private void setItemPolicies(Item item, AccessControl accessControl) throws SQLE itemAccessConditions.get(accessCondition.getName()))); } + private boolean isAdjustPoliciesNeeded(Item item, String mode, List accessConditions) { + return (isAppendModeDisabled() && item.isArchived()) || + (REPLACE_MODE.equals(mode) && CollectionUtils.isEmpty(accessConditions)); + } + private void updateBitstreamsPolicies(Item item, AccessControl accessControl) { - if (containsConstraints(accessControl)) { + if (containsConstraints(accessControl.getBitstream())) { findMatchedBitstreams(item, accessControl.getBitstream().getConstraint().getUuids()) - .forEach(bitstream -> - updateBitstreamPolicies(bitstream, item, accessControl)); + .forEach(bitstream -> updateBitstreamPolicies(bitstream, item, accessControl)); } else { - item.getBundles() - .stream() - .flatMap(bundle -> bundle.getBitstreams().stream()) + findAllBitstreams(item) .forEach(bitstream -> updateBitstreamPolicies(bitstream, item, accessControl)); } } - private boolean containsConstraints(AccessControl accessControl) { - AccessConditionBitstream controlBitstream = accessControl.getBitstream(); - - return Objects.nonNull(controlBitstream) && - Objects.nonNull(controlBitstream.getConstraint()) && - isNotEmpty(controlBitstream.getConstraint().getUuids()); + private boolean containsConstraints(AccessConditionBitstream bitstream) { + return Objects.nonNull(bitstream) && + Objects.nonNull(bitstream.getConstraint()) && + isNotEmpty(bitstream.getConstraint().getUuids()); } private List findMatchedBitstreams(Item item, List uuids) { @@ -365,9 +392,16 @@ private List findMatchedBitstreams(Item item, List uuids) { .collect(Collectors.toList()); } + private List findAllBitstreams(Item item) { + return item.getBundles() + .stream() + .flatMap(bundle -> bundle.getBitstreams().stream()) + .collect(Collectors.toList()); + } + private void updateBitstreamPolicies(Bitstream bitstream, Item item, AccessControl accessControl) { - if ("replace".equals(accessControl.getBitstream().getMode())) { + if (REPLACE_MODE.equals(accessControl.getBitstream().getMode())) { removeReadPolicies(bitstream, TYPE_CUSTOM); removeReadPolicies(bitstream, TYPE_INHERITED); } @@ -390,7 +424,10 @@ private void removeReadPolicies(DSpaceObject dso, String type) { private void setBitstreamPolicies(Bitstream bitstream, Item item, AccessControl accessControl) throws SQLException, AuthorizeException { - if (isAppendModeDisabled() && item.isArchived()) { + + AccessConditionBitstream bitstreamControl = accessControl.getBitstream(); + + if (isAdjustPoliciesNeeded(item, bitstreamControl.getMode(), bitstreamControl.getAccessConditions())) { itemService.adjustBitstreamPolicies(context, item, item.getOwningCollection(), bitstream); } @@ -411,18 +448,10 @@ private void createResourcePolicy(DSpaceObject obj, AccessCondition accessCondit try { AccessConditionOption.createResourcePolicy(context, obj, name, description, startDate, endDate); } catch (Exception e) { - throw new RuntimeException(e); + throw new BulkAccessControlException(e); } } -// private void rollback() { -// try { -// context.rollback(); -// } catch (SQLException e) { -// throw new SQLRuntimeException(e); -// } -// } - private void assignCurrentUserInContext() throws SQLException { UUID uuid = getEpersonIdentifier(); if (uuid != null) { @@ -437,10 +466,6 @@ private void assignSpecialGroupsInContext() throws SQLException { } } - private Date parseDate(String date) { - return MultiFormatDateParser.parse(date); - } - private boolean isAppendModeDisabled() { return !configurationService.getBooleanProperty( "core.authorization.installitem.inheritance-read.append-mode"); diff --git a/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/model/AccessConditionBitstream.java b/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/model/AccessConditionBitstream.java index 0e5ee47d4ec7..f9f5cbce8de8 100644 --- a/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/model/AccessConditionBitstream.java +++ b/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/model/AccessConditionBitstream.java @@ -7,6 +7,7 @@ */ package org.dspace.app.bulkaccesscontrol.model; +import java.util.ArrayList; import java.util.List; /** @@ -37,6 +38,9 @@ public void setConstraint(Constraint constraint) { } public List getAccessConditions() { + if (accessConditions == null) { + return new ArrayList<>(); + } return accessConditions; } diff --git a/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/model/AccessConditionItem.java b/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/model/AccessConditionItem.java index 6176cc220c52..10f17c1c01bf 100644 --- a/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/model/AccessConditionItem.java +++ b/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/model/AccessConditionItem.java @@ -7,6 +7,7 @@ */ package org.dspace.app.bulkaccesscontrol.model; +import java.util.ArrayList; import java.util.List; /** @@ -27,6 +28,9 @@ public void setMode(String mode) { } public List getAccessConditions() { + if (accessConditions == null) { + return new ArrayList<>(); + } return accessConditions; } From c670251a68433cfaecfa73f65665b9892aef22fe Mon Sep 17 00:00:00 2001 From: Jens Vannerum Date: Thu, 11 May 2023 17:13:13 +0200 Subject: [PATCH 151/510] 94299: Fix minor issues --- .../rest/BitstreamCategoryRestController.java | 1 - .../operation/BitstreamRemoveOperation.java | 15 +- .../app/rest/BitstreamRestRepositoryIT.java | 299 ++++++++++++++++++ 3 files changed, 313 insertions(+), 2 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamCategoryRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamCategoryRestController.java index 13929e5a9a73..6d970eb10961 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamCategoryRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamCategoryRestController.java @@ -51,7 +51,6 @@ public class BitstreamCategoryRestController { * @throws SQLException if an error occurs while accessing the database. * @throws AuthorizeException if the user is not authorized to perform the requested operation. */ - @PreAuthorize("hasAuthority('ADMIN')") @RequestMapping(method = RequestMethod.PATCH) public ResponseEntity> patch(HttpServletRequest request, @RequestBody(required = true) JsonNode jsonNode) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/BitstreamRemoveOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/BitstreamRemoveOperation.java index 93c495a30233..77336002716f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/BitstreamRemoveOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/BitstreamRemoveOperation.java @@ -14,10 +14,13 @@ import org.dspace.app.rest.exception.RESTBitstreamNotFoundException; import org.dspace.app.rest.model.patch.Operation; import org.dspace.authorize.AuthorizeException; +import org.dspace.authorize.service.AuthorizeService; import org.dspace.content.Bitstream; import org.dspace.content.service.BitstreamService; +import org.dspace.core.Constants; import org.dspace.core.Context; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.AccessDeniedException; import org.springframework.stereotype.Component; /** @@ -38,6 +41,8 @@ public class BitstreamRemoveOperation extends PatchOperation { @Autowired BitstreamService bitstreamService; + @Autowired + AuthorizeService authorizeService; public static final String OPERATION_PATH_BITSTREAM_REMOVE = "/bitstreams/"; @Override @@ -47,10 +52,10 @@ public Bitstream perform(Context context, Bitstream resource, Operation operatio if (bitstreamToDelete == null) { throw new RESTBitstreamNotFoundException(bitstreamIDtoDelete); } + authorizeBitstreamRemoveAction(context, bitstreamToDelete, Constants.DELETE); try { bitstreamService.delete(context, bitstreamToDelete); - bitstreamService.update(context, bitstreamToDelete); } catch (AuthorizeException | IOException e) { throw new RuntimeException(e.getMessage(), e); } @@ -62,4 +67,12 @@ public boolean supports(Object objectToMatch, Operation operation) { return objectToMatch == null && operation.getOp().trim().equalsIgnoreCase(OPERATION_REMOVE) && operation.getPath().trim().startsWith(OPERATION_PATH_BITSTREAM_REMOVE); } + + public void authorizeBitstreamRemoveAction(Context context, Bitstream bitstream, int operation) throws SQLException { + try { + authorizeService.authorizeAction(context, bitstream, operation); + } catch (AuthorizeException e) { + throw new AccessDeniedException("The current user is not allowed to remove the bitstream", e); + } + } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java index 3b01b4eac2ab..2d855a06c238 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java @@ -46,6 +46,7 @@ import org.dspace.builder.BundleBuilder; import org.dspace.builder.CollectionBuilder; import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.EPersonBuilder; import org.dspace.builder.ItemBuilder; import org.dspace.builder.ResourcePolicyBuilder; import org.dspace.content.Bitstream; @@ -56,6 +57,8 @@ import org.dspace.content.Item; import org.dspace.content.service.BitstreamFormatService; import org.dspace.content.service.BitstreamService; +import org.dspace.content.service.CollectionService; +import org.dspace.content.service.CommunityService; import org.dspace.content.service.ItemService; import org.dspace.core.Constants; import org.dspace.eperson.EPerson; @@ -86,6 +89,12 @@ public class BitstreamRestRepositoryIT extends AbstractControllerIntegrationTest @Autowired private ItemService itemService; + @Autowired + CollectionService collectionService; + + @Autowired + CommunityService communityService; + @Test public void findAllTest() throws Exception { //We turn off the authorization system in order to create the structure as defined below @@ -2490,6 +2499,296 @@ public void deleteBitstreamsInBulk_invalidRequestSize() throws Exception { Assert.assertTrue(bitstreamExists(token, bitstream1, bitstream2, bitstream3, bitstream4)); } + @Test + public void deleteBitstreamsInBulk_Unauthorized() throws Exception { + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Collection collection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection") + .build(); + Item publicItem1 = ItemBuilder.createItem(context, collection) + .withTitle("Test item 1") + .build(); + Item publicItem2 = ItemBuilder.createItem(context, collection) + .withTitle("Test item 2") + .build(); + + String bitstreamContent = "This is an archived bitstream"; + Bitstream bitstream1 = null; + Bitstream bitstream2 = null; + Bitstream bitstream3 = null; + Bitstream bitstream4 = null; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + bitstream1 = BitstreamBuilder.createBitstream(context, publicItem1, is) + .withName("Bitstream 1") + .withMimeType("text/plain") + .build(); + bitstream2 = BitstreamBuilder.createBitstream(context, publicItem1, is) + .withName("Bitstream 2") + .withMimeType("text/plain") + .build(); + bitstream3 = BitstreamBuilder.createBitstream(context, publicItem2, is) + .withName("Bitstream 3") + .withMimeType("text/plain") + .build(); + bitstream4 = BitstreamBuilder.createBitstream(context, publicItem2, is) + .withName("Bitstream 4") + .withMimeType("text/plain") + .build(); + } + context.restoreAuthSystemState(); + + // Add three out of four bitstreams to the list of bitstreams to be deleted + List ops = new ArrayList<>(); + RemoveOperation removeOp1 = new RemoveOperation(OPERATION_PATH_BITSTREAM_REMOVE + bitstream1.getID()); + ops.add(removeOp1); + RemoveOperation removeOp2 = new RemoveOperation(OPERATION_PATH_BITSTREAM_REMOVE + bitstream2.getID()); + ops.add(removeOp2); + RemoveOperation removeOp3 = new RemoveOperation(OPERATION_PATH_BITSTREAM_REMOVE + bitstream3.getID()); + ops.add(removeOp3); + String patchBody = getPatchContent(ops); + String token = getAuthToken(admin.getEmail(), password); + + Assert.assertTrue(bitstreamExists(token, bitstream1, bitstream2, bitstream3, bitstream4)); + + getClient().perform(patch("/api/core/bitstreams") + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isUnauthorized()); + } + + @Test + public void deleteBitstreamsInBulk_Forbidden() throws Exception { + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Collection collection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection") + .build(); + Item publicItem1 = ItemBuilder.createItem(context, collection) + .withTitle("Test item 1") + .build(); + Item publicItem2 = ItemBuilder.createItem(context, collection) + .withTitle("Test item 2") + .build(); + + String bitstreamContent = "This is an archived bitstream"; + Bitstream bitstream1 = null; + Bitstream bitstream2 = null; + Bitstream bitstream3 = null; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + bitstream1 = BitstreamBuilder.createBitstream(context, publicItem1, is) + .withName("Bitstream 1") + .withMimeType("text/plain") + .build(); + bitstream2 = BitstreamBuilder.createBitstream(context, publicItem1, is) + .withName("Bitstream 2") + .withMimeType("text/plain") + .build(); + bitstream3 = BitstreamBuilder.createBitstream(context, publicItem2, is) + .withName("Bitstream 3") + .withMimeType("text/plain") + .build(); + } + context.restoreAuthSystemState(); + + // Add three out of four bitstreams to the list of bitstreams to be deleted + List ops = new ArrayList<>(); + RemoveOperation removeOp1 = new RemoveOperation(OPERATION_PATH_BITSTREAM_REMOVE + bitstream1.getID()); + ops.add(removeOp1); + RemoveOperation removeOp2 = new RemoveOperation(OPERATION_PATH_BITSTREAM_REMOVE + bitstream2.getID()); + ops.add(removeOp2); + RemoveOperation removeOp3 = new RemoveOperation(OPERATION_PATH_BITSTREAM_REMOVE + bitstream3.getID()); + ops.add(removeOp3); + String patchBody = getPatchContent(ops); + String token = getAuthToken(eperson.getEmail(), password); + + getClient(token).perform(patch("/api/core/bitstreams") + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isForbidden()); + } + + @Test + public void deleteBitstreamsInBulk_collectionAdmin() throws Exception { + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + Collection col2 = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 2") + .build(); + EPerson col1Admin = EPersonBuilder.createEPerson(context) + .withEmail("col1admin@test.com") + .withPassword(password) + .build(); + EPerson col2Admin = EPersonBuilder.createEPerson(context) + .withEmail("col2admin@test.com") + .withPassword(password) + .build(); + Group col1_AdminGroup = collectionService.createAdministrators(context, col1); + Group col2_AdminGroup = collectionService.createAdministrators(context, col2); + groupService.addMember(context, col1_AdminGroup, col1Admin); + groupService.addMember(context, col2_AdminGroup, col2Admin); + Item publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Test item 1") + .build(); + Item publicItem2 = ItemBuilder.createItem(context, col2) + .withTitle("Test item 2") + .build(); + + String bitstreamContent = "This is an archived bitstream"; + Bitstream bitstream1 = null; + Bitstream bitstream2 = null; + Bitstream bitstream3 = null; + Bitstream bitstream4 = null; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + bitstream1 = BitstreamBuilder.createBitstream(context, publicItem1, is) + .withName("Bitstream 1") + .withMimeType("text/plain") + .build(); + bitstream2 = BitstreamBuilder.createBitstream(context, publicItem1, is) + .withName("Bitstream 2") + .withMimeType("text/plain") + .build(); + bitstream3 = BitstreamBuilder.createBitstream(context, publicItem2, is) + .withName("Bitstream 3") + .withMimeType("text/plain") + .build(); + bitstream4 = BitstreamBuilder.createBitstream(context, publicItem2, is) + .withName("Bitstream 4") + .withMimeType("text/plain") + .build(); + } + context.restoreAuthSystemState(); + + // Add three out of four bitstreams to the list of bitstreams to be deleted + List ops = new ArrayList<>(); + RemoveOperation removeOp1 = new RemoveOperation(OPERATION_PATH_BITSTREAM_REMOVE + bitstream1.getID()); + ops.add(removeOp1); + RemoveOperation removeOp2 = new RemoveOperation(OPERATION_PATH_BITSTREAM_REMOVE + bitstream2.getID()); + ops.add(removeOp2); + RemoveOperation removeOp3 = new RemoveOperation(OPERATION_PATH_BITSTREAM_REMOVE + bitstream3.getID()); + ops.add(removeOp3); + String patchBody = getPatchContent(ops); + + String token = getAuthToken(col1Admin.getEmail(), password); + // Should return forbidden since one of the bitstreams does not originate form collection 1 + getClient(token).perform(patch("/api/core/bitstreams") + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isForbidden()); + + // Remove the bitstream that does not originate from the collection we are administrator of, should return OK + ops.remove(2); + patchBody = getPatchContent(ops); + getClient(token).perform(patch("/api/core/bitstreams") + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isNoContent()); + + // Change the token to the admin of collection 2 + token = getAuthToken(col2Admin.getEmail(), password); + + // Add three out of four bitstreams to the list of bitstreams to be deleted + ops = new ArrayList<>(); + removeOp1 = new RemoveOperation(OPERATION_PATH_BITSTREAM_REMOVE + bitstream2.getID()); + ops.add(removeOp1); + removeOp2 = new RemoveOperation(OPERATION_PATH_BITSTREAM_REMOVE + bitstream3.getID()); + ops.add(removeOp2); + removeOp3 = new RemoveOperation(OPERATION_PATH_BITSTREAM_REMOVE + bitstream4.getID()); + ops.add(removeOp3); + patchBody = getPatchContent(ops); + + // Should return forbidden since one of the bitstreams does not originate form collection 2 + getClient(token).perform(patch("/api/core/bitstreams") + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isForbidden()); + // Remove the bitstream that does not originate from the collection we are administrator of, should return OK + ops.remove(0); + patchBody = getPatchContent(ops); + getClient(token).perform(patch("/api/core/bitstreams") + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isNoContent()); + } + + @Test + public void deleteBitstreamsInBulk_communityAdmin() throws Exception { + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + Collection col2 = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 2") + .build(); + EPerson parentCommunityAdmin = EPersonBuilder.createEPerson(context) + .withEmail("parentComAdmin@test.com") + .withPassword(password) + .build(); + Group parentComAdminGroup = communityService.createAdministrators(context, parentCommunity); + groupService.addMember(context, parentComAdminGroup, parentCommunityAdmin); + Item publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Test item 1") + .build(); + Item publicItem2 = ItemBuilder.createItem(context, col2) + .withTitle("Test item 2") + .build(); + + String bitstreamContent = "This is an archived bitstream"; + Bitstream bitstream1 = null; + Bitstream bitstream2 = null; + Bitstream bitstream3 = null; + Bitstream bitstream4 = null; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + bitstream1 = BitstreamBuilder.createBitstream(context, publicItem1, is) + .withName("Bitstream 1") + .withMimeType("text/plain") + .build(); + bitstream2 = BitstreamBuilder.createBitstream(context, publicItem1, is) + .withName("Bitstream 2") + .withMimeType("text/plain") + .build(); + bitstream3 = BitstreamBuilder.createBitstream(context, publicItem2, is) + .withName("Bitstream 3") + .withMimeType("text/plain") + .build(); + bitstream4 = BitstreamBuilder.createBitstream(context, publicItem2, is) + .withName("Bitstream 4") + .withMimeType("text/plain") + .build(); + } + context.restoreAuthSystemState(); + + // Add three out of four bitstreams to the list of bitstreams to be deleted + List ops = new ArrayList<>(); + RemoveOperation removeOp1 = new RemoveOperation(OPERATION_PATH_BITSTREAM_REMOVE + bitstream1.getID()); + ops.add(removeOp1); + RemoveOperation removeOp2 = new RemoveOperation(OPERATION_PATH_BITSTREAM_REMOVE + bitstream2.getID()); + ops.add(removeOp2); + RemoveOperation removeOp3 = new RemoveOperation(OPERATION_PATH_BITSTREAM_REMOVE + bitstream3.getID()); + ops.add(removeOp3); + String patchBody = getPatchContent(ops); + + String token = getAuthToken(parentCommunityAdmin.getEmail(), password); + // Bitstreams originate from two different collections, but those collections live in the same community, so + // a community admin should be able to delete them + getClient(token).perform(patch("/api/core/bitstreams") + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isNoContent()); + } + public boolean bitstreamExists(String token, Bitstream ...bitstreams) throws Exception { for (Bitstream bitstream : bitstreams) { if (getClient(token).perform(get("/api/core/bitstreams/" + bitstream.getID())) From a026a8ced06ba5ec4865d0e46c5dc0f18d20306a Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Thu, 11 May 2023 11:43:54 -0400 Subject: [PATCH 152/510] Fix test broken by new injection plan. --- .../eperson/DeleteEPersonSubmitterIT.java | 31 +++++++++++-------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/eperson/DeleteEPersonSubmitterIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/eperson/DeleteEPersonSubmitterIT.java index e020c04b1a25..d1679ae1d20b 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/eperson/DeleteEPersonSubmitterIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/eperson/DeleteEPersonSubmitterIT.java @@ -24,9 +24,11 @@ import java.util.concurrent.atomic.AtomicReference; import javax.ws.rs.core.MediaType; +import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.dspace.app.requestitem.RequestItemAuthor; import org.dspace.app.requestitem.RequestItemAuthorExtractor; +import org.dspace.app.requestitem.RequestItemHelpdeskStrategy; import org.dspace.app.rest.model.patch.Operation; import org.dspace.app.rest.model.patch.ReplaceOperation; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; @@ -46,6 +48,7 @@ import org.dspace.eperson.EPerson; import org.dspace.eperson.factory.EPersonServiceFactory; import org.dspace.eperson.service.EPersonService; +import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; import org.dspace.versioning.Version; import org.dspace.versioning.factory.VersionServiceFactory; @@ -76,7 +79,7 @@ public class DeleteEPersonSubmitterIT extends AbstractControllerIntegrationTest protected RequestItemAuthorExtractor requestItemAuthorExtractor = DSpaceServicesFactory.getInstance() .getServiceManager() - .getServiceByName("org.dspace.app.requestitem.RequestItemAuthorExtractor", + .getServiceByName(RequestItemHelpdeskStrategy.class.getName(), RequestItemAuthorExtractor.class); @@ -85,15 +88,8 @@ public class DeleteEPersonSubmitterIT extends AbstractControllerIntegrationTest private EPerson submitterForVersion2; private EPerson workflowUser; - private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(DeleteEPersonSubmitterIT.class); + private static final Logger log = LogManager.getLogger(); - /** - * This method will be run before every test as per @Before. It will - * initialize resources required for the tests. - * - * Other methods can be annotated with @Before here or in subclasses but no - * execution order is guaranteed - */ @Before @Override public void setUp() throws Exception { @@ -114,8 +110,8 @@ public void setUp() throws Exception { /** - * This test verifies that when the submitter Eperson is deleted, the delete succeeds and the item will have - * 'null' as submitter + * This test verifies that when the submitter Eperson is deleted, the delete + * succeeds and the item will have 'null' as submitter. * * @throws Exception */ @@ -140,12 +136,21 @@ public void testArchivedItemSubmitterDelete() throws Exception { assertNull(retrieveItemSubmitter(installItem.getID())); + // Don't depend on external configuration; set up helpdesk as needed. + final String HELPDESK_EMAIL = "dspace-help@example.com"; + final String HELPDESK_NAME = "Help Desk"; + ConfigurationService configurationService + = DSpaceServicesFactory.getInstance().getConfigurationService(); + configurationService.setProperty("mail.helpdesk", HELPDESK_EMAIL); + configurationService.setProperty("mail.helpdesk.name", HELPDESK_NAME); + configurationService.setProperty("request.item.helpdesk.override", "true"); + // Test it. Item item = itemService.find(context, installItem.getID()); List requestItemAuthor = requestItemAuthorExtractor.getRequestItemAuthor(context, item); - assertEquals("Help Desk", requestItemAuthor.get(0).getFullName()); - assertEquals("dspace-help@myu.edu", requestItemAuthor.get(0).getEmail()); + assertEquals(HELPDESK_NAME, requestItemAuthor.get(0).getFullName()); + assertEquals(HELPDESK_EMAIL, requestItemAuthor.get(0).getEmail()); } /** From 7c7824f913c5db9cffafb5afed8394cc441d5c8c Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Thu, 11 May 2023 17:57:45 +0200 Subject: [PATCH 153/510] Implement community feedback --- .../DiscoveryConfigurationService.java | 3 +- .../DiscoveryScopeBasedRestControllerIT.java | 52 +++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) 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 da23b87a3594..1a1ed95a2965 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 @@ -13,6 +13,7 @@ import java.util.List; import java.util.Map; import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; @@ -42,7 +43,7 @@ public class DiscoveryConfigurationService { * own configuration, we take the one of the first parent that does. * This cache ensures we do not have to go up the hierarchy every time. */ - private final Map comColToDiscoveryConfigurationMap = new HashMap<>(); + private final Map comColToDiscoveryConfigurationMap = new ConcurrentHashMap<>(); public Map getMap() { return map; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryScopeBasedRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryScopeBasedRestControllerIT.java index 0c8735545e8b..a3408a7736df 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryScopeBasedRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryScopeBasedRestControllerIT.java @@ -58,6 +58,9 @@ * * The tests will verify that for each object, the correct facets are provided and that all the necessary fields to * power these facets are indexed properly. + * + * This file requires the discovery configuration in the following test file: + * src/test/data/dspaceFolder/config/spring/api/test-discovery.xml */ public class DiscoveryScopeBasedRestControllerIT extends AbstractControllerIntegrationTest { @@ -263,6 +266,9 @@ public void setUp() throws Exception { } @Test + /** + * Verify that the custom configuration "discovery-parent-community-1" is correctly used for Parent Community 1. + */ public void ScopeBasedIndexingAndSearchTestParentCommunity1() throws Exception { getClient().perform(get("/api/discover/facets").param("scope", String.valueOf(parentCommunity1.getID()))) @@ -301,6 +307,9 @@ public void ScopeBasedIndexingAndSearchTestParentCommunity1() throws Exception { } @Test + /** + * Verify that the custom configuration "discovery-sub-community-1-1" is correctly used for Subcommunity 11. + */ public void ScopeBasedIndexingAndSearchTestSubCommunity11() throws Exception { getClient().perform(get("/api/discover/facets").param("scope", String.valueOf(subcommunity11.getID()))) @@ -330,6 +339,9 @@ public void ScopeBasedIndexingAndSearchTestSubCommunity11() throws Exception { } @Test + /** + * Verify that the custom configuration "discovery-collection-1-1-1" is correctly used for Collection 111. + */ public void ScopeBasedIndexingAndSearchTestCollection111() throws Exception { getClient().perform(get("/api/discover/facets").param("scope", String.valueOf(collection111.getID()))) @@ -357,6 +369,10 @@ public void ScopeBasedIndexingAndSearchTestCollection111() throws Exception { } @Test + /** + * Verify that the first encountered custom parent configuration "discovery-sub-community-1-1" is inherited + * correctly for Collection 112. + */ public void ScopeBasedIndexingAndSearchTestCollection112() throws Exception { getClient().perform(get("/api/discover/facets").param("scope", String.valueOf(collection112.getID()))) @@ -382,6 +398,10 @@ public void ScopeBasedIndexingAndSearchTestCollection112() throws Exception { } @Test + /** + * Verify that the first encountered custom parent configuration "discovery-parent-community-1" is inherited + * correctly for Subcommunity 12. + */ public void ScopeBasedIndexingAndSearchTestSubcommunity12() throws Exception { getClient().perform(get("/api/discover/facets").param("scope", String.valueOf(subcommunity12.getID()))) @@ -411,6 +431,9 @@ public void ScopeBasedIndexingAndSearchTestSubcommunity12() throws Exception { } @Test + /** + * Verify that the custom configuration "discovery-collection-1-2-1" is correctly used for Collection 121. + */ public void ScopeBasedIndexingAndSearchTestCollection121() throws Exception { getClient().perform(get("/api/discover/facets").param("scope", String.valueOf(collection121.getID()))) @@ -436,6 +459,10 @@ public void ScopeBasedIndexingAndSearchTestCollection121() throws Exception { } @Test + /** + * Verify that the first encountered custom parent configuration "discovery-parent-community-1" is inherited + * correctly for Collection 122. + */ public void ScopeBasedIndexingAndSearchTestCollection122() throws Exception { getClient().perform(get("/api/discover/facets").param("scope", String.valueOf(collection122.getID()))) @@ -463,6 +490,10 @@ public void ScopeBasedIndexingAndSearchTestCollection122() throws Exception { } @Test + /** + * Verify that the default configuration is inherited correctly when no other custom configuration can be inherited + * for Parent Community 2. + */ public void ScopeBasedIndexingAndSearchTestParentCommunity2() throws Exception { getClient().perform(get("/api/discover/facets").param("scope", String.valueOf(parentCommunity2.getID()))) @@ -481,6 +512,9 @@ public void ScopeBasedIndexingAndSearchTestParentCommunity2() throws Exception { } @Test + /** + * Verify that the custom configuration "discovery-sub-community-2-1" is correctly used for Subcommunity 21. + */ public void ScopeBasedIndexingAndSearchTestSubCommunity21() throws Exception { getClient().perform(get("/api/discover/facets").param("scope", String.valueOf(subcommunity21.getID()))) @@ -510,6 +544,9 @@ public void ScopeBasedIndexingAndSearchTestSubCommunity21() throws Exception { } @Test + /** + * Verify that the custom configuration "discovery-collection-2-1-1" is correctly used for Collection 211. + */ public void ScopeBasedIndexingAndSearchTestCollection211() throws Exception { getClient().perform(get("/api/discover/facets").param("scope", String.valueOf(collection211.getID()))) @@ -537,6 +574,10 @@ public void ScopeBasedIndexingAndSearchTestCollection211() throws Exception { } @Test + /** + * Verify that the first encountered custom parent configuration "discovery-sub-community-2-1" is inherited + * correctly for Collection 212. + */ public void ScopeBasedIndexingAndSearchTestCollection212() throws Exception { getClient().perform(get("/api/discover/facets").param("scope", String.valueOf(collection212.getID()))) @@ -562,6 +603,10 @@ public void ScopeBasedIndexingAndSearchTestCollection212() throws Exception { } @Test + /** + * Verify that the default configuration is inherited correctly when no other custom configuration can be inherited + * for Subcommunity 22. + */ public void ScopeBasedIndexingAndSearchTestSubcommunity22() throws Exception { getClient().perform(get("/api/discover/facets").param("scope", String.valueOf(subcommunity22.getID()))) @@ -579,6 +624,9 @@ public void ScopeBasedIndexingAndSearchTestSubcommunity22() throws Exception { } @Test + /** + * Verify that the custom configuration "discovery-collection-2-2-1" is correctly used for Collection 221. + */ public void ScopeBasedIndexingAndSearchTestCollection221() throws Exception { getClient().perform(get("/api/discover/facets").param("scope", String.valueOf(collection221.getID()))) @@ -604,6 +652,10 @@ public void ScopeBasedIndexingAndSearchTestCollection221() throws Exception { } @Test + /** + * Verify that the default configuration is inherited correctly when no other custom configuration can be inherited + * for Collection 222. + */ public void ScopeBasedIndexingAndSearchTestCollection222() throws Exception { getClient().perform(get("/api/discover/facets").param("scope", String.valueOf(collection222.getID()))) From 0231af5867eeaa2e9a17b27935e9ca505df5e88c Mon Sep 17 00:00:00 2001 From: damian Date: Thu, 11 May 2023 20:08:32 +0200 Subject: [PATCH 154/510] Item counters added to Communities and Collections REST API --- .../main/java/org/dspace/content/Collection.java | 9 +++++++++ .../org/dspace/content/CollectionServiceImpl.java | 10 ++++++++++ .../main/java/org/dspace/content/Community.java | 8 ++++++++ .../org/dspace/content/CommunityServiceImpl.java | 11 ++++++++++- .../java/org/dspace/content/dao/CollectionDAO.java | 3 +++ .../dspace/content/dao/impl/CollectionDAOImpl.java | 14 ++++++++++---- .../dspace/content/service/CollectionService.java | 2 ++ .../dspace/content/service/CommunityService.java | 3 +++ .../app/rest/converter/CollectionConverter.java | 8 ++++++++ .../app/rest/converter/CommunityConverter.java | 7 +++++++ .../org/dspace/app/rest/model/CollectionRest.java | 12 ++++++++++++ .../org/dspace/app/rest/model/CommunityRest.java | 10 ++++++++++ 12 files changed, 92 insertions(+), 5 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/Collection.java b/dspace-api/src/main/java/org/dspace/content/Collection.java index ffec3b45cc87..33fbb51e3fa7 100644 --- a/dspace-api/src/main/java/org/dspace/content/Collection.java +++ b/dspace-api/src/main/java/org/dspace/content/Collection.java @@ -29,6 +29,7 @@ import javax.persistence.Transient; import org.dspace.authorize.AuthorizeException; +import org.dspace.browse.ItemCountException; import org.dspace.content.comparator.NameAscendingComparator; import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.CollectionService; @@ -336,4 +337,12 @@ private CollectionService getCollectionService() { return collectionService; } + public int getAllItems() { + try { + return collectionService.countArchivedItem(this); + } catch (ItemCountException e) { + throw new RuntimeException(e); + } + } + } 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 ddfd38694f23..961ad4cbcf0e 100644 --- a/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java @@ -31,6 +31,8 @@ import org.dspace.authorize.ResourcePolicy; import org.dspace.authorize.service.AuthorizeService; import org.dspace.authorize.service.ResourcePolicyService; +import org.dspace.browse.ItemCountException; +import org.dspace.browse.ItemCounter; import org.dspace.content.dao.CollectionDAO; import org.dspace.content.service.BitstreamService; import org.dspace.content.service.CollectionService; @@ -1047,4 +1049,12 @@ public int countCollectionsWithSubmit(String q, Context context, Community commu return (int) resp.getTotalSearchResults(); } + @Override + public int countArchivedItem(Collection collection) throws ItemCountException { + //TODO load ItemCounter on bean creation + Context context = new Context(); + return new ItemCounter(context).getCount(collection); + } + + } diff --git a/dspace-api/src/main/java/org/dspace/content/Community.java b/dspace-api/src/main/java/org/dspace/content/Community.java index fa99da33091a..5b8baba8e498 100644 --- a/dspace-api/src/main/java/org/dspace/content/Community.java +++ b/dspace-api/src/main/java/org/dspace/content/Community.java @@ -25,6 +25,7 @@ import javax.persistence.Transient; import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.dspace.browse.ItemCountException; import org.dspace.content.comparator.NameAscendingComparator; import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.CommunityService; @@ -264,4 +265,11 @@ private CommunityService getCommunityService() { return communityService; } + public int getAllItems() { + try { + return communityService.getArchivedItems(this); + } catch (ItemCountException e) { + throw new RuntimeException(e); + } + } } diff --git a/dspace-api/src/main/java/org/dspace/content/CommunityServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/CommunityServiceImpl.java index 923745f761bb..7fe8dfd28951 100644 --- a/dspace-api/src/main/java/org/dspace/content/CommunityServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/CommunityServiceImpl.java @@ -24,6 +24,8 @@ import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.ResourcePolicy; import org.dspace.authorize.service.AuthorizeService; +import org.dspace.browse.ItemCountException; +import org.dspace.browse.ItemCounter; import org.dspace.content.dao.CommunityDAO; import org.dspace.content.service.BitstreamService; import org.dspace.content.service.CollectionService; @@ -76,9 +78,9 @@ public class CommunityServiceImpl extends DSpaceObjectServiceImpl imp protected IdentifierService identifierService; @Autowired(required = true) protected SubscribeService subscribeService; + protected CommunityServiceImpl() { super(); - } @Override @@ -708,4 +710,11 @@ public Community findByLegacyId(Context context, int id) throws SQLException { public int countTotal(Context context) throws SQLException { return communityDAO.countRows(context); } + + @Override + public int getArchivedItems(Community community) throws ItemCountException { + //TODO load ItemCounter on bean creation + Context context = new Context(); + return new ItemCounter(context).getCount(community); + } } diff --git a/dspace-api/src/main/java/org/dspace/content/dao/CollectionDAO.java b/dspace-api/src/main/java/org/dspace/content/dao/CollectionDAO.java index 6bb65bbb46d8..c8014fb52097 100644 --- a/dspace-api/src/main/java/org/dspace/content/dao/CollectionDAO.java +++ b/dspace-api/src/main/java/org/dspace/content/dao/CollectionDAO.java @@ -10,6 +10,7 @@ import java.sql.SQLException; import java.util.List; import java.util.Map; +import java.util.UUID; import org.dspace.content.Collection; import org.dspace.content.Item; @@ -53,4 +54,6 @@ public List findAll(Context context, MetadataField order, Integer li int countRows(Context context) throws SQLException; List> getCollectionsWithBitstreamSizesTotal(Context context) throws SQLException; + + int countArchivedItems(Context context, UUID id) throws SQLException; } diff --git a/dspace-api/src/main/java/org/dspace/content/dao/impl/CollectionDAOImpl.java b/dspace-api/src/main/java/org/dspace/content/dao/impl/CollectionDAOImpl.java index c0ef6ea42fce..dc8bad7e19c4 100644 --- a/dspace-api/src/main/java/org/dspace/content/dao/impl/CollectionDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/dao/impl/CollectionDAOImpl.java @@ -7,11 +7,9 @@ */ package org.dspace.content.dao.impl; +import java.math.BigInteger; import java.sql.SQLException; -import java.util.AbstractMap; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; +import java.util.*; import javax.persistence.Query; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; @@ -182,4 +180,12 @@ public List> getCollectionsWithBitstreamSizesTotal(C } return returnList; } + + @Override + public int countArchivedItems(Context context, UUID collectionId) throws SQLException { + return ((BigInteger) getHibernateSession(context) + .createNativeQuery("select count(*) from collection2item ci left join item i on i.uuid = ci.item_id where i.in_archive and uuid(ci.collection_id)=uuid(:collectionId)") + .setParameter("collectionId", collectionId.toString()) + .getSingleResult()).intValue(); + } } \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/content/service/CollectionService.java b/dspace-api/src/main/java/org/dspace/content/service/CollectionService.java index a5b2b7d8d891..d1ad6dcef2e0 100644 --- a/dspace-api/src/main/java/org/dspace/content/service/CollectionService.java +++ b/dspace-api/src/main/java/org/dspace/content/service/CollectionService.java @@ -15,6 +15,7 @@ import java.util.UUID; import org.dspace.authorize.AuthorizeException; +import org.dspace.browse.ItemCountException; import org.dspace.content.Bitstream; import org.dspace.content.Collection; import org.dspace.content.Community; @@ -455,4 +456,5 @@ public int countCollectionsWithSubmit(String q, Context context, Community commu public int countCollectionsWithSubmit(String q, Context context, Community community, String entityType) throws SQLException, SearchServiceException; + int countArchivedItem(Collection collection) throws ItemCountException; } diff --git a/dspace-api/src/main/java/org/dspace/content/service/CommunityService.java b/dspace-api/src/main/java/org/dspace/content/service/CommunityService.java index e7b62126650c..65454f26ea72 100644 --- a/dspace-api/src/main/java/org/dspace/content/service/CommunityService.java +++ b/dspace-api/src/main/java/org/dspace/content/service/CommunityService.java @@ -14,6 +14,7 @@ import java.util.UUID; import org.dspace.authorize.AuthorizeException; +import org.dspace.browse.ItemCountException; import org.dspace.content.Bitstream; import org.dspace.content.Collection; import org.dspace.content.Community; @@ -292,4 +293,6 @@ public void removeSubcommunity(Context context, Community parentCommunity, Commu public List findAuthorizedGroupMapped(Context context, List actions) throws SQLException; int countTotal(Context context) throws SQLException; + + int getArchivedItems(Community community) throws ItemCountException; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/CollectionConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/CollectionConverter.java index fc5d99b05924..9a5000852df5 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/CollectionConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/CollectionConverter.java @@ -8,7 +8,9 @@ package org.dspace.app.rest.converter; import org.dspace.app.rest.model.CollectionRest; +import org.dspace.app.rest.projection.Projection; import org.dspace.content.Collection; +import org.dspace.core.Context; import org.dspace.discovery.IndexableObject; import org.springframework.stereotype.Component; @@ -22,6 +24,12 @@ public class CollectionConverter extends DSpaceObjectConverter implements IndexableObjectConverter { + @Override + public CollectionRest convert(Collection collection, Projection projection) { + CollectionRest resource = super.convert(collection, projection); + resource.setArchivedItems(collection.getAllItems()); + return resource; + } @Override protected CollectionRest newInstance() { return new CollectionRest(); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/CommunityConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/CommunityConverter.java index d4c06470ce86..ef1f114ccbf9 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/CommunityConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/CommunityConverter.java @@ -8,6 +8,7 @@ package org.dspace.app.rest.converter; import org.dspace.app.rest.model.CommunityRest; +import org.dspace.app.rest.projection.Projection; import org.dspace.content.Community; import org.dspace.discovery.IndexableObject; import org.springframework.stereotype.Component; @@ -23,6 +24,12 @@ public class CommunityConverter extends DSpaceObjectConverter implements IndexableObjectConverter { + public CommunityRest convert(Community community, Projection projection) { + CommunityRest resource = super.convert(community, projection); + resource.setArchivedItems(community.getAllItems()); + return resource; + } + @Override protected CommunityRest newInstance() { return new CommunityRest(); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/CollectionRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/CollectionRest.java index 1de4ec632cff..7405a1525dc1 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/CollectionRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/CollectionRest.java @@ -74,4 +74,16 @@ public String getCategory() { public String getType() { return NAME; } + + private int archivedItems; + + public int getArchivedItems() { + return archivedItems; + } + + public void setArchivedItems(int archivedItems) { + this.archivedItems = archivedItems; + } + + } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/CommunityRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/CommunityRest.java index f8ccbad10e62..8ffdcd2e8ae9 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/CommunityRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/CommunityRest.java @@ -58,4 +58,14 @@ public String getCategory() { public String getType() { return NAME; } + + private int archivedItems; + + public int getArchivedItems() { + return archivedItems; + } + + public void setArchivedItems(int archivedItems) { + this.archivedItems = archivedItems; + } } From 78fba6b579c3af233c80861c6efb90491cb8d925 Mon Sep 17 00:00:00 2001 From: Jens Vannerum Date: Thu, 11 May 2023 21:24:31 +0200 Subject: [PATCH 155/510] 94299: checkstyle --- .../org/dspace/app/rest/BitstreamCategoryRestController.java | 1 - .../repository/patch/operation/BitstreamRemoveOperation.java | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamCategoryRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamCategoryRestController.java index 6d970eb10961..aa511bcb9282 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamCategoryRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamCategoryRestController.java @@ -20,7 +20,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.hateoas.RepresentationModel; import org.springframework.http.ResponseEntity; -import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/BitstreamRemoveOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/BitstreamRemoveOperation.java index 77336002716f..b0e2a45c9d23 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/BitstreamRemoveOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/BitstreamRemoveOperation.java @@ -68,7 +68,8 @@ public boolean supports(Object objectToMatch, Operation operation) { operation.getPath().trim().startsWith(OPERATION_PATH_BITSTREAM_REMOVE); } - public void authorizeBitstreamRemoveAction(Context context, Bitstream bitstream, int operation) throws SQLException { + public void authorizeBitstreamRemoveAction(Context context, Bitstream bitstream, int operation) + throws SQLException { try { authorizeService.authorizeAction(context, bitstream, operation); } catch (AuthorizeException e) { From 50e816f718b96b2cf2c3ebd40f4bcaea55cecdfc Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Thu, 11 May 2023 23:39:34 +0200 Subject: [PATCH 156/510] CST-9634 fix issues with date timezone and inheritance of default policies --- .../bulkaccesscontrol/BulkAccessControl.java | 81 ++++++------------- .../model/AccessCondition.java | 9 ++- .../util/MultiFormatDateDeserializer.java | 41 ++++++++++ .../test/data/dspaceFolder/config/local.cfg | 2 +- .../BulkAccessControlIT.java | 54 ++++--------- 5 files changed, 89 insertions(+), 98 deletions(-) create mode 100644 dspace-api/src/main/java/org/dspace/util/MultiFormatDateDeserializer.java 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 af9d4e6c1f0e..ce0e63039c0f 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 @@ -14,11 +14,15 @@ import java.io.IOException; import java.io.InputStream; import java.sql.SQLException; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Arrays; import java.util.Date; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.TimeZone; import java.util.UUID; import java.util.function.Function; import java.util.stream.Collectors; @@ -81,7 +85,7 @@ public class BulkAccessControl extends DSpaceRunnable uuids; private Context context; @@ -124,12 +128,13 @@ public void setup() throws ParseException { .collect(Collectors.toMap(AccessConditionOption::getName, Function.identity())); filename = commandLine.getOptionValue('f'); - uuids = commandLine.getOptionValues('u'); + uuids = commandLine.hasOption('u') ? Arrays.asList(commandLine.getOptionValues('u')) : null; } @Override public void internalRun() throws Exception { ObjectMapper mapper = new ObjectMapper(); + mapper.setTimeZone(TimeZone.getTimeZone("UTC")); AccessControl accessControl; context = new Context(Context.Mode.BATCH_EDIT); assignCurrentUserInContext(); @@ -137,12 +142,9 @@ public void internalRun() throws Exception { context.turnOffAuthorisationSystem(); - if (uuids == null) { - handler.logError("A target uuid must be provided (run with -h flag for details)"); - throw new IllegalArgumentException("A target uuid must be provided"); - } else if (uuids.length <= 0) { - handler.logError("A target uuid must be provided with at least on uuid"); - throw new IllegalArgumentException("A target uuid must be provided with at least on uuid"); + if (uuids == null || uuids.size() == 0) { + handler.logError("A target uuid must be provided with at least on uuid (run with -h flag for details)"); + throw new IllegalArgumentException("At least one target uuid must be provided"); } InputStream inputStream = handler.getFileStream(context, filename) @@ -152,10 +154,9 @@ public void internalRun() throws Exception { try { accessControl = mapper.readValue(inputStream, AccessControl.class); } catch (IOException e) { - handler.logError("Error parsing json file"); + handler.logError("Error parsing json file " + e.getMessage()); throw new IllegalArgumentException("Error parsing json file", e); } - try { validate(accessControl); updateItemsAndBitstreamsPolices(accessControl); @@ -231,12 +232,12 @@ private void validateBitstreamNode(AccessConditionBitstream bitstream) throws SQ } private void validateConstraint(AccessConditionBitstream bitstream) throws SQLException { - if (uuids.length > 1 && containsConstraints(bitstream)) { + if (uuids.size() > 1 && containsConstraints(bitstream)) { handler.logError("constraint isn't supported when multiple uuids are provided"); throw new BulkAccessControlException("constraint isn't supported when multiple uuids are provided"); } else { DSpaceObject dso = - dSpaceObjectUtils.findDSpaceObject(context, UUID.fromString(uuids[0])); + dSpaceObjectUtils.findDSpaceObject(context, UUID.fromString(uuids.get(0))); if (Objects.nonNull(dso) && dso.getType() != Constants.ITEM) { handler.logError("constraint is not supported when uuid isn't an Item"); @@ -296,11 +297,11 @@ public void updateItemsAndBitstreamsPolices(AccessControl accessControl) } } - private String buildSolrQuery(String[] uuids) throws SQLException { - String [] query = new String[uuids.length]; + private String buildSolrQuery(List uuids) throws SQLException { + String [] query = new String[uuids.size()]; for (int i = 0 ; i < query.length ; i++) { - DSpaceObject dso = dSpaceObjectUtils.findDSpaceObject(context, UUID.fromString(uuids[i])); + DSpaceObject dso = dSpaceObjectUtils.findDSpaceObject(context, UUID.fromString(uuids.get(i))); if (dso.getType() == Constants.COMMUNITY) { query[i] = "location.comm:" + dso.getID(); @@ -348,35 +349,23 @@ private void updateItemPolicies(Item item, AccessControl accessControl) throws S } private void setItemPolicies(Item item, AccessControl accessControl) throws SQLException, AuthorizeException { - AccessConditionItem itemControl = accessControl.getItem(); - - if (isAdjustPoliciesNeeded(item, itemControl.getMode(), itemControl.getAccessConditions())) { - itemService.adjustItemPolicies(context, item, item.getOwningCollection()); - } - accessControl .getItem() .getAccessConditions() .forEach(accessCondition -> createResourcePolicy(item, accessCondition, itemAccessConditions.get(accessCondition.getName()))); - } - private boolean isAdjustPoliciesNeeded(Item item, String mode, List accessConditions) { - return (isAppendModeDisabled() && item.isArchived()) || - (REPLACE_MODE.equals(mode) && CollectionUtils.isEmpty(accessConditions)); + itemService.adjustItemPolicies(context, item, item.getOwningCollection()); } private void updateBitstreamsPolicies(Item item, AccessControl accessControl) { - - if (containsConstraints(accessControl.getBitstream())) { - findMatchedBitstreams(item, accessControl.getBitstream().getConstraint().getUuids()) + item.getBundles(Constants.CONTENT_BUNDLE_NAME).stream() + .flatMap(bundle -> bundle.getBitstreams().stream()) + .filter(bitstream -> uuids == null || + uuids.size() == 0 || + uuids.contains(bitstream.getID().toString())) .forEach(bitstream -> updateBitstreamPolicies(bitstream, item, accessControl)); - } else { - findAllBitstreams(item) - .forEach(bitstream -> - updateBitstreamPolicies(bitstream, item, accessControl)); - } } private boolean containsConstraints(AccessConditionBitstream bitstream) { @@ -385,20 +374,6 @@ private boolean containsConstraints(AccessConditionBitstream bitstream) { isNotEmpty(bitstream.getConstraint().getUuids()); } - private List findMatchedBitstreams(Item item, List uuids) { - return item.getBundles().stream() - .flatMap(bundle -> bundle.getBitstreams().stream()) - .filter(bitstream -> uuids.contains(bitstream.getID().toString())) - .collect(Collectors.toList()); - } - - private List findAllBitstreams(Item item) { - return item.getBundles() - .stream() - .flatMap(bundle -> bundle.getBitstreams().stream()) - .collect(Collectors.toList()); - } - private void updateBitstreamPolicies(Bitstream bitstream, Item item, AccessControl accessControl) { if (REPLACE_MODE.equals(accessControl.getBitstream().getMode())) { @@ -424,17 +399,12 @@ private void removeReadPolicies(DSpaceObject dso, String type) { private void setBitstreamPolicies(Bitstream bitstream, Item item, AccessControl accessControl) throws SQLException, AuthorizeException { - AccessConditionBitstream bitstreamControl = accessControl.getBitstream(); - - if (isAdjustPoliciesNeeded(item, bitstreamControl.getMode(), bitstreamControl.getAccessConditions())) { - itemService.adjustBitstreamPolicies(context, item, item.getOwningCollection(), bitstream); - } - accessControl.getBitstream() .getAccessConditions() .forEach(accessCondition -> createResourcePolicy(bitstream, accessCondition, uploadAccessConditions.get(accessCondition.getName()))); + itemService.adjustBitstreamPolicies(context, item, item.getOwningCollection(), bitstream); } private void createResourcePolicy(DSpaceObject obj, AccessCondition accessCondition, @@ -466,11 +436,6 @@ private void assignSpecialGroupsInContext() throws SQLException { } } - private boolean isAppendModeDisabled() { - return !configurationService.getBooleanProperty( - "core.authorization.installitem.inheritance-read.append-mode"); - } - @Override @SuppressWarnings("unchecked") public BulkAccessControlScriptConfiguration getScriptConfiguration() { diff --git a/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/model/AccessCondition.java b/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/model/AccessCondition.java index 40ea2d0e25f4..6cf95e0e2179 100644 --- a/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/model/AccessCondition.java +++ b/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/model/AccessCondition.java @@ -9,9 +9,12 @@ import java.util.Date; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import org.dspace.app.bulkaccesscontrol.BulkAccessControl; +import org.dspace.util.MultiFormatDateDeserializer; + /** - * Class that model the value of ACCESS_CONDITION_CELL - * of sheet BITSTREAM_METADATA of the Bulk import excel. + * Class that model the values of an Access Condition as expressed in the {@link BulkAccessControl} input file * * @author Mohamed Eskander (mohamed.eskander at 4science.it) */ @@ -21,8 +24,10 @@ public class AccessCondition { private String description; + @JsonDeserialize(using = MultiFormatDateDeserializer.class) private Date startDate; + @JsonDeserialize(using = MultiFormatDateDeserializer.class) private Date endDate; public AccessCondition() { diff --git a/dspace-api/src/main/java/org/dspace/util/MultiFormatDateDeserializer.java b/dspace-api/src/main/java/org/dspace/util/MultiFormatDateDeserializer.java new file mode 100644 index 000000000000..2b6f37beb2e1 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/util/MultiFormatDateDeserializer.java @@ -0,0 +1,41 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.util; + +import java.io.IOException; +import java.util.Date; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; + +/** + * This is a custom date deserializer for jackson that make use of our + * {@link MultiFormatDateParser} + * + * Dates are parsed as being in the UTC zone. + * + */ +public class MultiFormatDateDeserializer extends StdDeserializer { + + public MultiFormatDateDeserializer() { + this(null); + } + + public MultiFormatDateDeserializer(Class vc) { + super(vc); + } + + @Override + public Date deserialize(JsonParser jsonparser, DeserializationContext context) + throws IOException, JsonProcessingException { + String date = jsonparser.getText(); + return MultiFormatDateParser.parse(date); + } +} \ No newline at end of file diff --git a/dspace-api/src/test/data/dspaceFolder/config/local.cfg b/dspace-api/src/test/data/dspaceFolder/config/local.cfg index c009acb30e1d..5111b963a0e1 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/local.cfg +++ b/dspace-api/src/test/data/dspaceFolder/config/local.cfg @@ -43,7 +43,7 @@ dspace.server.url = http://localhost db.driver = org.h2.Driver db.dialect=org.hibernate.dialect.H2Dialect # Use a 10 second database lock timeout to avoid occasional JDBC lock timeout errors -db.url = jdbc:h2:mem:test;LOCK_TIMEOUT=10000;NON_KEYWORDS=VALUE +db.url = jdbc:h2:mem:test;LOCK_TIMEOUT=10000;NON_KEYWORDS=VALUE;TIME ZONE=UTC db.username = sa db.password = # H2's default schema is PUBLIC 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 b63d31f52464..26963c845e1d 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 @@ -276,7 +276,7 @@ public void performBulkAccessWithInvalidAccessConditionDateTest() throws Excepti " \"accessConditions\": [\n" + " {\n" + " \"name\": \"embargo\",\n" + - " \"endDate\": \"2024-06-24T23:59:59.999+0000\"\n" + + " \"endDate\": \"2024-06-24T00:00:00Z\"\n" + " }\n" + " ]\n" + " }}\n"; @@ -296,7 +296,7 @@ public void performBulkAccessWithInvalidAccessConditionDateTest() throws Excepti " \"accessConditions\": [\n" + " {\n" + " \"name\": \"lease\",\n" + - " \"startDate\": \"2024-06-24T23:59:59.999+0000\"\n" + + " \"startDate\": \"2024-06-24T00:00:00Z\"\n" + " }\n" + " ]\n" + " }}\n"; @@ -329,7 +329,7 @@ public void performBulkAccessWithValidJsonTest() throws Exception { .build(); Community subCommunityThree = CommunityBuilder.createSubCommunity(context, parentCommunity) - .withName("sub community two") + .withName("sub community three") .build(); Collection collectionOne = CollectionBuilder.createCollection(context, subCommunityOne) @@ -344,9 +344,9 @@ public void performBulkAccessWithValidJsonTest() throws Exception { .withName("collection three") .build(); - ItemBuilder.createItem(context, collectionOne).build(); + Item itemOne = ItemBuilder.createItem(context, collectionOne).build(); - ItemBuilder.createItem(context, collectionTwo).build(); + Item itemTwo = ItemBuilder.createItem(context, collectionTwo).build(); Item itemThree = ItemBuilder.createItem(context, collectionThree).withTitle("item three title").build(); @@ -359,7 +359,7 @@ public void performBulkAccessWithValidJsonTest() throws Exception { " \"accessConditions\": [\n" + " {\n" + " \"name\": \"embargo\",\n" + - " \"startDate\": \"2024-06-24T00:00:00.000Z\"\n" + + " \"startDate\": \"2024-06-24\"\n" + " }\n" + " ]\n" + " }}\n"; @@ -380,28 +380,26 @@ public void performBulkAccessWithValidJsonTest() throws Exception { assertThat(testDSpaceRunnableHandler.getErrorMessages(), empty()); assertThat(testDSpaceRunnableHandler.getWarningMessages(), empty()); - Iterator itemIteratorOne = itemService.findByCollection(context, collectionOne); - Iterator itemIteratorTwo = itemService.findByCollection(context, collectionTwo); + itemOne = context.reloadEntity(itemOne); + itemTwo = context.reloadEntity(itemTwo); itemThree = context.reloadEntity(itemThree); itemFour = context.reloadEntity(itemFour); Group anonymousGroup = groupService.findByName(context, Group.ANONYMOUS); + assertThat(itemOne.getResourcePolicies(), hasSize(1)); + assertThat(itemOne.getResourcePolicies(), hasItem( + matches(Constants.READ, anonymousGroup, "embargo", TYPE_CUSTOM, "2024-06-24", null, null) + )); -// matchItemsResourcePolicies(itemIteratorOne, anonymousGroup, "embargo", TYPE_CUSTOM, "2024-06-24", null); -// matchItemsResourcePolicies(itemIteratorTwo, anonymousGroup, "embargo", TYPE_CUSTOM, "2024-06-24", null); -// matchItemResourcePolicies(itemThree, anonymousGroup, "embargo", TYPE_CUSTOM, "2024-06-24", null); - - assertThat(itemThree.getResourcePolicies(), hasSize(2)); - assertThat(itemThree.getResourcePolicies(), containsInAnyOrder( - matches(Constants.READ, anonymousGroup, ResourcePolicy.TYPE_INHERITED), - matches(READ, anonymousGroup, "embargo", TYPE_CUSTOM, "2024-06-24T00:00:00.000Z", null, null) + assertThat(itemTwo.getResourcePolicies(), hasSize(1)); + assertThat(itemTwo.getResourcePolicies(), hasItem( + matches(Constants.READ, anonymousGroup, "embargo", TYPE_CUSTOM, "2024-06-24", null, null) )); - // just a note here is working fine + assertThat(itemThree.getResourcePolicies(), hasSize(1)); assertThat(itemThree.getResourcePolicies(), hasItem( - matches(READ, anonymousGroup, "embargo", TYPE_CUSTOM, - itemThree.getResourcePolicies().get(0).getStartDate(), null, null) + matches(Constants.READ, anonymousGroup, "embargo", TYPE_CUSTOM, "2024-06-24", null, null) )); assertThat(itemFour.getResourcePolicies().size(), is(1)); @@ -409,24 +407,6 @@ public void performBulkAccessWithValidJsonTest() throws Exception { matches(Constants.READ, anonymousGroup, ResourcePolicy.TYPE_INHERITED) )); - - - - } - - private void matchItemsResourcePolicies( - Iterator itemIterator, Group group, String rpName, String rpType, String startDate, String endDate) { - while (itemIterator.hasNext()) { - Item item = itemIterator.next(); - matchItemResourcePolicies(item, group, rpName, rpType, startDate, endDate); - } - } - - private void matchItemResourcePolicies( - Item item, Group group, String rpName, String rpType, String startDate, String endDate) { - - assertThat(item.getResourcePolicies(), hasItem( - matches(READ, group, rpName, rpType, startDate, endDate, null))); } private void buildJsonFile(String json) throws IOException { From 2cf5b290b624efca51c3d2f35f1257bc828bfaba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ad=C3=A1n=20Rom=C3=A1n=20Ruiz?= <116575333+aroman-arvo@users.noreply.github.com> Date: Thu, 11 May 2023 23:59:38 +0200 Subject: [PATCH 157/510] Fix "Simultaneous deletion of multiple bitstreams from the same bundle often compromises the state of the bundle" (#8778) * DS-8694 * clean imports * cleaning code * DS-8694 - re-deletion of bitstream throw exception * ds-8694 - clean code --- .../dspace/app/rest/repository/DSpaceRestRepository.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/DSpaceRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/DSpaceRestRepository.java index 01f127eca5ac..a93f5e55dc02 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/DSpaceRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/DSpaceRestRepository.java @@ -195,7 +195,11 @@ public long count() { /** * Delete the object identified by its ID */ - public void deleteById(ID id) { + /** + * Method should be synchronized to avoid hibernate partial deletion bug when deleting multiple bitstreams: + * https://github.com/DSpace/DSpace/issues/8694 + */ + public synchronized void deleteById(ID id) { Context context = obtainContext(); try { getThisRepository().delete(context, id); From 03c39cf6f1439e5393f9df5028fc64b3ba1cc837 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Mon, 17 Apr 2023 14:40:43 +1200 Subject: [PATCH 158/510] Bitstreams should inherit DEFAULT_BITSTREAM_READ, then DEFAULT_ITEM_READ When generating automatic policies, bitstream DSOs have always inherited from DEFAULT_ITEM_READ, even if the collection's DEFAULT_BITSTREAM_READ policy differs. This bugfix checks DEFAULT_BITSTREAM_READ first, and uses DEFAULT_ITEM_READ as a fallback. --- .../org/dspace/authorize/AuthorizeServiceImpl.java | 13 +++++++++++-- 1 file changed, 11 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 34543c078ae2..e8e639a0b20f 100644 --- a/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java @@ -674,8 +674,17 @@ public void generateAutomaticPolicies(Context context, Date embargoDate, throws SQLException, AuthorizeException { if (embargoDate != null || (embargoDate == null && dso instanceof Bitstream)) { - - List authorizedGroups = getAuthorizedGroups(context, owningCollection, Constants.DEFAULT_ITEM_READ); + // Get DEFAULT_BITSTREAM_READ policy from the collection + List defaultBitstreamReadGroups = + getAuthorizedGroups(context, owningCollection, Constants.DEFAULT_BITSTREAM_READ); + // Get DEFAULT_ITEM_READ policy from the collection + List defaultItemReadGroups = + getAuthorizedGroups(context, owningCollection, Constants.DEFAULT_ITEM_READ); + // By default, use DEFAULT_BITSTREAM_READ. Otherwise, use DEFAULT_ITEM_READ + List authorizedGroups = defaultBitstreamReadGroups; + if (defaultBitstreamReadGroups.isEmpty()) { + authorizedGroups = defaultItemReadGroups; + } removeAllPoliciesByDSOAndType(context, dso, ResourcePolicy.TYPE_CUSTOM); From 019d0319dc8174611f5a37b9b1c4960baf2612f2 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Fri, 12 May 2023 14:01:14 +1200 Subject: [PATCH 159/510] #8783 New bitstreams inherit DEFAULT_BITSTREAM_READ from owning collection Otherwise will keep inherited READ from bundle. Does not affect embargos set during submission or workflow, these will always be applied afterwards. --- .../org/dspace/content/BundleServiceImpl.java | 23 +++++++ .../app/rest/BitstreamRestControllerIT.java | 60 ++++++++++++++++++- 2 files changed, 81 insertions(+), 2 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/BundleServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/BundleServiceImpl.java index 485f1d645130..01bee702bb58 100644 --- a/dspace-api/src/main/java/org/dspace/content/BundleServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/BundleServiceImpl.java @@ -34,6 +34,7 @@ import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.core.LogHelper; +import org.dspace.eperson.Group; import org.dspace.event.Event; import org.springframework.beans.factory.annotation.Autowired; @@ -173,6 +174,28 @@ public void addBitstream(Context context, Bundle bundle, Bitstream bitstream) // copy authorization policies from bundle to bitstream // FIXME: multiple inclusion is affected by this... authorizeService.inheritPolicies(context, bundle, bitstream); + if (owningItem != null) { + // Resolve owning collection + Collection owningCollection = owningItem.getOwningCollection(); + if (owningCollection != null) { + // Get DEFAULT_BITSTREAM_READ policy from the collection + List defaultBitstreamReadGroups = + authorizeService.getAuthorizedGroups(context, owningCollection, + Constants.DEFAULT_BITSTREAM_READ); + log.info(defaultBitstreamReadGroups.size()); + // If this collection is configured with a DEFAULT_BITSTREAM_READ group, overwrite the READ policy + // inherited from the bundle with this policy. + if (!defaultBitstreamReadGroups.isEmpty()) { + // Remove read policies from the bitstream + authorizeService.removePoliciesActionFilter(context, bitstream, Constants.READ); + for (Group defaultBitstreamReadGroup : defaultBitstreamReadGroups) { + // Inherit this policy as READ, directly from the collection roles + authorizeService.addPolicy(context, bitstream, + Constants.READ, defaultBitstreamReadGroup, ResourcePolicy.TYPE_INHERITED); + } + } + } + } bitstreamService.update(context, bitstream); } 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 09dbdca505fe..79b7bd22c1ab 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 @@ -17,8 +17,7 @@ import static org.dspace.builder.ResourcePolicyBuilder.createResourcePolicy; import static org.dspace.content.BitstreamFormat.KNOWN; import static org.dspace.content.BitstreamFormat.SUPPORTED; -import static org.dspace.core.Constants.READ; -import static org.dspace.core.Constants.WRITE; +import static org.dspace.core.Constants.*; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.Matchers.equalTo; @@ -56,6 +55,8 @@ import org.apache.pdfbox.text.PDFTextStripper; import org.apache.solr.client.solrj.SolrServerException; 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; @@ -70,6 +71,7 @@ import org.dspace.content.Item; import org.dspace.content.service.BitstreamFormatService; import org.dspace.content.service.BitstreamService; +import org.dspace.content.service.CollectionService; import org.dspace.core.Constants; import org.dspace.disseminate.CitationDocumentServiceImpl; import org.dspace.eperson.EPerson; @@ -112,6 +114,12 @@ public class BitstreamRestControllerIT extends AbstractControllerIntegrationTest @Autowired private BitstreamFormatService bitstreamFormatService; + @Autowired + private AuthorizeService authorizeService; + + @Autowired + private CollectionService collectionService; + private Bitstream bitstream; private BitstreamFormat supportedFormat; private BitstreamFormat knownFormat; @@ -626,6 +634,54 @@ public void testPrivateBitstream() throws Exception { } + @Test + public void testBitstreamDefaultReadInheritanceFromCollection() throws Exception { + context.turnOffAuthorisationSystem(); + + //** GIVEN ** + //1. A community-collection structure with one parent community and one collections. + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Group internalGroup = GroupBuilder.createGroup(context) + .withName("Internal Group") + .build(); + // Explicitly create a restrictive default bitstream read policy on the collection + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build(); + authorizeService.removePoliciesActionFilter(context, col1, DEFAULT_BITSTREAM_READ); + authorizeService.addPolicy(context, col1, DEFAULT_BITSTREAM_READ, internalGroup); + + //2. A public item with a new bitstream that is not explicitly restricted + // but should instead inherit + Item publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Public item 1") + .withIssueDate("2017-10-17") + .withAuthor("Smith, Donald").withAuthor("Doe, John") + .build(); + // make sure this item has no default policies for a new bundle to inherit + authorizeService.removePoliciesActionFilter(context, publicItem1, DEFAULT_BITSTREAM_READ); + + String bitstreamContent = "Private!"; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + bitstream = BitstreamBuilder + .createBitstream(context, publicItem1, is) + .withName("Test Restricted Bitstream") + .withDescription("This bitstream is restricted") + .withMimeType("text/plain") + .build(); + } + context.restoreAuthSystemState(); + //** WHEN ** + //We download the bitstream + getClient().perform(get("/api/core/bitstreams/" + bitstream.getID() + "/content")) + //** THEN ** + .andExpect(status().isUnauthorized()); + + //An unauthorized request should not log statistics + checkNumberOfStatsRecords(bitstream, 0); + } + @Test public void restrictedGroupBitstreamForbiddenTest() throws Exception { context.turnOffAuthorisationSystem(); From 4a88573dc1462080e92735b21f3729e7281c766b Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Fri, 12 May 2023 14:18:08 +1200 Subject: [PATCH 160/510] #8783 Strip out unused generateAutomaticPolicies method from AuthorizeService --- .../authorize/AuthorizeServiceImpl.java | 63 ------------------- .../authorize/service/AuthorizeService.java | 20 +----- 2 files changed, 1 insertion(+), 82 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 e8e639a0b20f..8058caf6696c 100644 --- a/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java @@ -654,69 +654,6 @@ public ResourcePolicy findByTypeGroupAction(Context c, DSpaceObject dso, Group g } } - /** - * Generate Policies policies READ for the date in input adding reason. New policies are assigned automatically - * at the groups that - * have right on the collection. E.g., if the anonymous can access the collection policies are assigned to - * anonymous. - * - * @param context The relevant DSpace Context. - * @param embargoDate embargo end date - * @param reason embargo reason - * @param dso DSpace object - * @param owningCollection collection to get group policies from - * @throws SQLException if database error - * @throws AuthorizeException if authorization error - */ - @Override - public void generateAutomaticPolicies(Context context, Date embargoDate, - String reason, DSpaceObject dso, Collection owningCollection) - throws SQLException, AuthorizeException { - - if (embargoDate != null || (embargoDate == null && dso instanceof Bitstream)) { - // Get DEFAULT_BITSTREAM_READ policy from the collection - List defaultBitstreamReadGroups = - getAuthorizedGroups(context, owningCollection, Constants.DEFAULT_BITSTREAM_READ); - // Get DEFAULT_ITEM_READ policy from the collection - List defaultItemReadGroups = - getAuthorizedGroups(context, owningCollection, Constants.DEFAULT_ITEM_READ); - // By default, use DEFAULT_BITSTREAM_READ. Otherwise, use DEFAULT_ITEM_READ - List authorizedGroups = defaultBitstreamReadGroups; - if (defaultBitstreamReadGroups.isEmpty()) { - authorizedGroups = defaultItemReadGroups; - } - - removeAllPoliciesByDSOAndType(context, dso, ResourcePolicy.TYPE_CUSTOM); - - // look for anonymous - boolean isAnonymousInPlace = false; - for (Group g : authorizedGroups) { - if (StringUtils.equals(g.getName(), Group.ANONYMOUS)) { - isAnonymousInPlace = true; - } - } - if (!isAnonymousInPlace) { - // add policies for all the groups - for (Group g : authorizedGroups) { - ResourcePolicy rp = createOrModifyPolicy(null, context, null, g, null, embargoDate, Constants.READ, - reason, dso); - if (rp != null) { - resourcePolicyService.update(context, rp); - } - } - - } else { - // add policy just for anonymous - ResourcePolicy rp = createOrModifyPolicy(null, context, null, - groupService.findByName(context, Group.ANONYMOUS), null, - embargoDate, Constants.READ, reason, dso); - if (rp != null) { - resourcePolicyService.update(context, rp); - } - } - } - } - @Override public ResourcePolicy createResourcePolicy(Context context, DSpaceObject dso, Group group, EPerson eperson, int type, String rpType) throws SQLException, AuthorizeException { diff --git a/dspace-api/src/main/java/org/dspace/authorize/service/AuthorizeService.java b/dspace-api/src/main/java/org/dspace/authorize/service/AuthorizeService.java index 36679f94c6a4..14a7ff5c82bb 100644 --- a/dspace-api/src/main/java/org/dspace/authorize/service/AuthorizeService.java +++ b/dspace-api/src/main/java/org/dspace/authorize/service/AuthorizeService.java @@ -470,24 +470,6 @@ public boolean isAnIdenticalPolicyAlreadyInPlace(Context c, DSpaceObject o, Grou public ResourcePolicy findByTypeGroupAction(Context c, DSpaceObject dso, Group group, int action) throws SQLException; - - /** - * Generate Policies policies READ for the date in input adding reason. New policies are assigned automatically - * at the groups that - * have right on the collection. E.g., if the anonymous can access the collection policies are assigned to - * anonymous. - * - * @param context current context - * @param embargoDate date - * @param reason reason - * @param dso DSpaceObject - * @param owningCollection collection - * @throws SQLException if database error - * @throws AuthorizeException if authorization error - */ - public void generateAutomaticPolicies(Context context, Date embargoDate, String reason, DSpaceObject dso, - Collection owningCollection) throws SQLException, AuthorizeException; - public ResourcePolicy createResourcePolicy(Context context, DSpaceObject dso, Group group, EPerson eperson, int type, String rpType) throws SQLException, AuthorizeException; @@ -603,7 +585,7 @@ long countAdminAuthorizedCollection(Context context, String query) /** * Replace all the policies in the target object with exactly the same policies that exist in the source object - * + * * @param context DSpace Context * @param source source of policies * @param dest destination of inherited policies From c2575b05b2f957adc7e2096f625c7d50264bf657 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Fri, 12 May 2023 14:19:53 +1200 Subject: [PATCH 161/510] #8783 checkstyle fixes for integration test --- .../java/org/dspace/app/rest/BitstreamRestControllerIT.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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 79b7bd22c1ab..7a4aa0c0077a 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 @@ -17,7 +17,9 @@ import static org.dspace.builder.ResourcePolicyBuilder.createResourcePolicy; import static org.dspace.content.BitstreamFormat.KNOWN; import static org.dspace.content.BitstreamFormat.SUPPORTED; -import static org.dspace.core.Constants.*; +import static org.dspace.core.Constants.DEFAULT_BITSTREAM_READ; +import static org.dspace.core.Constants.READ; +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; @@ -55,7 +57,6 @@ import org.apache.pdfbox.text.PDFTextStripper; import org.apache.solr.client.solrj.SolrServerException; 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; From b69517da7444d23df76a54873a55b4936d15e2e3 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Fri, 12 May 2023 17:47:56 +1200 Subject: [PATCH 162/510] #8783 don't inherit DEFAULT_BITSTREAM_READ if item has current embargo --- .../org/dspace/content/BundleServiceImpl.java | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/BundleServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/BundleServiceImpl.java index 01bee702bb58..3fb78ee6c8ac 100644 --- a/dspace-api/src/main/java/org/dspace/content/BundleServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/BundleServiceImpl.java @@ -7,13 +7,12 @@ */ package org.dspace.content; -import static org.dspace.core.Constants.ADD; -import static org.dspace.core.Constants.REMOVE; -import static org.dspace.core.Constants.WRITE; +import static org.dspace.core.Constants.*; import java.io.IOException; import java.sql.SQLException; import java.util.ArrayList; +import java.util.Date; import java.util.Iterator; import java.util.LinkedList; import java.util.List; @@ -174,7 +173,18 @@ public void addBitstream(Context context, Bundle bundle, Bitstream bitstream) // copy authorization policies from bundle to bitstream // FIXME: multiple inclusion is affected by this... authorizeService.inheritPolicies(context, bundle, bitstream); - if (owningItem != null) { + // The next logic is a bit overly cautious but ensures that if there are any future start dates + // on the item or bitstream read policies, that we'll skip inheriting anything from the owning collection + // just in case. In practice, the item install process would overwrite these anyway but it may satisfy + // some other bitstream creation methods and integration tests + boolean isEmbargoed = false; + for (ResourcePolicy resourcePolicy : authorizeService.getPoliciesActionFilter(context, owningItem, READ)) { + if (!resourcePolicyService.isDateValid(resourcePolicy)) { + isEmbargoed = true; + break; + } + } + if (owningItem != null && !isEmbargoed) { // Resolve owning collection Collection owningCollection = owningItem.getOwningCollection(); if (owningCollection != null) { From 694339993840a4378207c7846bf78f69fe7d318a Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Fri, 12 May 2023 18:03:02 +1200 Subject: [PATCH 163/510] #8783 checkstyle fixes --- .../org/dspace/content/BundleServiceImpl.java | 66 ++++++++++--------- 1 file changed, 34 insertions(+), 32 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/BundleServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/BundleServiceImpl.java index 3fb78ee6c8ac..20c43e4bfc73 100644 --- a/dspace-api/src/main/java/org/dspace/content/BundleServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/BundleServiceImpl.java @@ -7,12 +7,14 @@ */ package org.dspace.content; -import static org.dspace.core.Constants.*; +import static org.dspace.core.Constants.ADD; +import static org.dspace.core.Constants.READ; +import static org.dspace.core.Constants.REMOVE; +import static org.dspace.core.Constants.WRITE; import java.io.IOException; import java.sql.SQLException; import java.util.ArrayList; -import java.util.Date; import java.util.Iterator; import java.util.LinkedList; import java.util.List; @@ -74,14 +76,14 @@ public Bundle find(Context context, UUID id) throws SQLException { if (bundle == null) { if (log.isDebugEnabled()) { log.debug(LogHelper.getHeader(context, "find_bundle", - "not_found,bundle_id=" + id)); + "not_found,bundle_id=" + id)); } return null; } else { if (log.isDebugEnabled()) { log.debug(LogHelper.getHeader(context, "find_bundle", - "bundle_id=" + id)); + "bundle_id=" + id)); } return bundle; @@ -106,7 +108,7 @@ public Bundle create(Context context, Item item, String name) throws SQLExceptio log.info(LogHelper.getHeader(context, "create_bundle", "bundle_id=" - + bundle.getID())); + + bundle.getID())); // if we ever use the identifier service for bundles, we should // create the bundle before we create the Event and should add all @@ -132,12 +134,12 @@ public Bitstream getBitstreamByName(Bundle bundle, String name) { @Override public void addBitstream(Context context, Bundle bundle, Bitstream bitstream) - throws SQLException, AuthorizeException { + throws SQLException, AuthorizeException { // Check authorisation authorizeService.authorizeAction(context, bundle, Constants.ADD); log.info(LogHelper.getHeader(context, "add_bitstream", "bundle_id=" - + bundle.getID() + ",bitstream_id=" + bitstream.getID())); + + bundle.getID() + ",bitstream_id=" + bitstream.getID())); // First check that the bitstream isn't already in the list List bitstreams = bundle.getBitstreams(); @@ -167,8 +169,8 @@ public void addBitstream(Context context, Bundle bundle, Bitstream bitstream) context.addEvent(new Event(Event.ADD, Constants.BUNDLE, bundle.getID(), - Constants.BITSTREAM, bitstream.getID(), String.valueOf(bitstream.getSequenceID()), - getIdentifiers(context, bundle))); + Constants.BITSTREAM, bitstream.getID(), String.valueOf(bitstream.getSequenceID()), + getIdentifiers(context, bundle))); // copy authorization policies from bundle to bitstream // FIXME: multiple inclusion is affected by this... @@ -211,17 +213,17 @@ public void addBitstream(Context context, Bundle bundle, Bitstream bitstream) @Override public void removeBitstream(Context context, Bundle bundle, Bitstream bitstream) - throws AuthorizeException, SQLException, IOException { + throws AuthorizeException, SQLException, IOException { // Check authorisation authorizeService.authorizeAction(context, bundle, Constants.REMOVE); log.info(LogHelper.getHeader(context, "remove_bitstream", - "bundle_id=" + bundle.getID() + ",bitstream_id=" + bitstream.getID())); + "bundle_id=" + bundle.getID() + ",bitstream_id=" + bitstream.getID())); context.addEvent(new Event(Event.REMOVE, Constants.BUNDLE, bundle.getID(), - Constants.BITSTREAM, bitstream.getID(), String.valueOf(bitstream.getSequenceID()), - getIdentifiers(context, bundle))); + Constants.BITSTREAM, bitstream.getID(), String.valueOf(bitstream.getSequenceID()), + getIdentifiers(context, bundle))); //Ensure that the last modified from the item is triggered ! Item owningItem = (Item) getParentObject(context, bundle); @@ -254,9 +256,9 @@ public void removeBitstream(Context context, Bundle bundle, Bitstream bitstream) @Override public void inheritCollectionDefaultPolicies(Context context, Bundle bundle, Collection collection) - throws SQLException, AuthorizeException { + throws SQLException, AuthorizeException { List policies = authorizeService.getPoliciesActionFilter(context, collection, - Constants.DEFAULT_BITSTREAM_READ); + Constants.DEFAULT_BITSTREAM_READ); // change the action to just READ // just don't call update on the resourcepolicies!!! @@ -264,7 +266,7 @@ public void inheritCollectionDefaultPolicies(Context context, Bundle bundle, Col if (!i.hasNext()) { throw new java.sql.SQLException("Collection " + collection.getID() - + " has no default bitstream READ policies"); + + " has no default bitstream READ policies"); } List newPolicies = new ArrayList(); @@ -279,7 +281,7 @@ public void inheritCollectionDefaultPolicies(Context context, Bundle bundle, Col @Override public void replaceAllBitstreamPolicies(Context context, Bundle bundle, List newpolicies) - throws SQLException, AuthorizeException { + throws SQLException, AuthorizeException { List bitstreams = bundle.getBitstreams(); if (CollectionUtils.isNotEmpty(bitstreams)) { for (Bitstream bs : bitstreams) { @@ -401,16 +403,16 @@ public void setOrder(Context context, Bundle bundle, UUID[] bitstreamIds) throws if (bitstream == null) { //This should never occur but just in case log.warn(LogHelper.getHeader(context, "Invalid bitstream id while changing bitstream order", - "Bundle: " + bundle.getID() + ", bitstream id: " + bitstreamId)); + "Bundle: " + bundle.getID() + ", bitstream id: " + bitstreamId)); continue; } // If we have a Bitstream not in the current list, log a warning & exit immediately if (!currentBitstreams.contains(bitstream)) { log.warn(LogHelper.getHeader(context, - "Encountered a bitstream not in this bundle while changing bitstream " + - "order. Bitstream order will not be changed.", - "Bundle: " + bundle.getID() + ", bitstream id: " + bitstreamId)); + "Encountered a bitstream not in this bundle while changing bitstream " + + "order. Bitstream order will not be changed.", + "Bundle: " + bundle.getID() + ", bitstream id: " + bitstreamId)); return; } updatedBitstreams.add(bitstream); @@ -419,9 +421,9 @@ public void setOrder(Context context, Bundle bundle, UUID[] bitstreamIds) throws // If our lists are different sizes, exit immediately if (updatedBitstreams.size() != currentBitstreams.size()) { log.warn(LogHelper.getHeader(context, - "Size of old list and new list do not match. Bitstream order will not be " + - "changed.", - "Bundle: " + bundle.getID())); + "Size of old list and new list do not match. Bitstream order will not be " + + "changed.", + "Bundle: " + bundle.getID())); return; } @@ -467,7 +469,7 @@ public DSpaceObject getAdminObject(Context context, Bundle bundle, int action) t } else if (AuthorizeConfiguration.canCollectionAdminPerformBitstreamDeletion()) { adminObject = collection; } else if (AuthorizeConfiguration - .canCommunityAdminPerformBitstreamDeletion()) { + .canCommunityAdminPerformBitstreamDeletion()) { adminObject = community; } break; @@ -475,10 +477,10 @@ public DSpaceObject getAdminObject(Context context, Bundle bundle, int action) t if (AuthorizeConfiguration.canItemAdminPerformBitstreamCreation()) { adminObject = item; } else if (AuthorizeConfiguration - .canCollectionAdminPerformBitstreamCreation()) { + .canCollectionAdminPerformBitstreamCreation()) { adminObject = collection; } else if (AuthorizeConfiguration - .canCommunityAdminPerformBitstreamCreation()) { + .canCommunityAdminPerformBitstreamCreation()) { adminObject = community; } break; @@ -510,7 +512,7 @@ public void update(Context context, Bundle bundle) throws SQLException, Authoriz // Check authorisation //AuthorizeManager.authorizeAction(ourContext, this, Constants.WRITE); log.info(LogHelper.getHeader(context, "update_bundle", "bundle_id=" - + bundle.getID())); + + bundle.getID())); super.update(context, bundle); bundleDAO.save(context, bundle); @@ -518,10 +520,10 @@ public void update(Context context, Bundle bundle) throws SQLException, Authoriz if (bundle.isModified() || bundle.isMetadataModified()) { if (bundle.isMetadataModified()) { context.addEvent(new Event(Event.MODIFY_METADATA, bundle.getType(), bundle.getID(), bundle.getDetails(), - getIdentifiers(context, bundle))); + getIdentifiers(context, bundle))); } context.addEvent(new Event(Event.MODIFY, Constants.BUNDLE, bundle.getID(), - null, getIdentifiers(context, bundle))); + null, getIdentifiers(context, bundle))); bundle.clearModified(); bundle.clearDetails(); } @@ -530,12 +532,12 @@ public void update(Context context, Bundle bundle) throws SQLException, Authoriz @Override public void delete(Context context, Bundle bundle) throws SQLException, AuthorizeException, IOException { log.info(LogHelper.getHeader(context, "delete_bundle", "bundle_id=" - + bundle.getID())); + + bundle.getID())); authorizeService.authorizeAction(context, bundle, Constants.DELETE); context.addEvent(new Event(Event.DELETE, Constants.BUNDLE, bundle.getID(), - bundle.getName(), getIdentifiers(context, bundle))); + bundle.getName(), getIdentifiers(context, bundle))); // Remove bitstreams List bitstreams = bundle.getBitstreams(); From 44c066d07aec07e7ec3796a60d6214ec29c57ec3 Mon Sep 17 00:00:00 2001 From: eskander Date: Fri, 12 May 2023 09:12:05 +0300 Subject: [PATCH 164/510] refactoring and adding new test cases --- .../bulkaccesscontrol/BulkAccessControl.java | 14 +- .../model/AccessConditionBitstream.java | 20 +- .../BulkAccessControlIT.java | 476 +++++++++++++++++- 3 files changed, 466 insertions(+), 44 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/BulkAccessControl.java b/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/BulkAccessControl.java index af9d4e6c1f0e..88accf2dba43 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 @@ -152,7 +152,7 @@ public void internalRun() throws Exception { try { accessControl = mapper.readValue(inputStream, AccessControl.class); } catch (IOException e) { - handler.logError("Error parsing json file"); + handler.logError("Error parsing json file " + e.getMessage()); throw new IllegalArgumentException("Error parsing json file", e); } @@ -234,7 +234,7 @@ private void validateConstraint(AccessConditionBitstream bitstream) throws SQLEx if (uuids.length > 1 && containsConstraints(bitstream)) { handler.logError("constraint isn't supported when multiple uuids are provided"); throw new BulkAccessControlException("constraint isn't supported when multiple uuids are provided"); - } else { + } else if (uuids.length == 1 && containsConstraints(bitstream)) { DSpaceObject dso = dSpaceObjectUtils.findDSpaceObject(context, UUID.fromString(uuids[0])); @@ -256,7 +256,7 @@ private void validateAccessCondition(AccessCondition accessCondition) { itemAccessConditions.get(accessCondition.getName()).validateResourcePolicy( context, accessCondition.getName(), accessCondition.getStartDate(), accessCondition.getEndDate()); } catch (Exception e) { - handler.logError("invalid access condition" + e.getMessage()); + handler.logError("invalid access condition, " + e.getMessage()); handler.handleException(e); } } @@ -274,7 +274,7 @@ public void updateItemsAndBitstreamsPolices(AccessControl accessControl) while (itemIterator.hasNext()) { - Item item = itemIterator.next(); + Item item = context.reloadEntity(itemIterator.next()); if (Objects.nonNull(accessControl.getItem())) { updateItemPolicies(item, accessControl); @@ -370,7 +370,7 @@ private boolean isAdjustPoliciesNeeded(Item item, String mode, List updateBitstreamPolicies(bitstream, item, accessControl)); } else { findAllBitstreams(item) @@ -381,8 +381,8 @@ private void updateBitstreamsPolicies(Item item, AccessControl accessControl) { private boolean containsConstraints(AccessConditionBitstream bitstream) { return Objects.nonNull(bitstream) && - Objects.nonNull(bitstream.getConstraint()) && - isNotEmpty(bitstream.getConstraint().getUuids()); + Objects.nonNull(bitstream.getConstraints()) && + isNotEmpty(bitstream.getConstraints().getUuid()); } private List findMatchedBitstreams(Item item, List uuids) { diff --git a/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/model/AccessConditionBitstream.java b/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/model/AccessConditionBitstream.java index f9f5cbce8de8..46354ddff41c 100644 --- a/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/model/AccessConditionBitstream.java +++ b/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/model/AccessConditionBitstream.java @@ -17,7 +17,7 @@ public class AccessConditionBitstream { private String mode; - private Constraint constraint; + private Constraint constraints; private List accessConditions; @@ -29,12 +29,12 @@ public void setMode(String mode) { this.mode = mode; } - public Constraint getConstraint() { - return constraint; + public Constraint getConstraints() { + return constraints; } - public void setConstraint(Constraint constraint) { - this.constraint = constraint; + public void setConstraints(Constraint constraints) { + this.constraints = constraints; } public List getAccessConditions() { @@ -50,14 +50,14 @@ public void setAccessConditions(List accessConditions) { public class Constraint { - private List uuids; + private List uuid; - public List getUuids() { - return uuids; + public List getUuid() { + return uuid; } - public void setUuids(List uuids) { - this.uuids = uuids; + public void setUuid(List uuid) { + this.uuid = uuid; } } 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 b63d31f52464..88062b1f664b 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 @@ -14,6 +14,7 @@ import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasItem; @@ -22,6 +23,7 @@ import java.io.File; import java.io.IOException; +import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; @@ -30,25 +32,37 @@ import java.util.Date; import java.util.Iterator; import java.util.List; +import java.util.UUID; import java.util.stream.Collectors; import javax.validation.constraints.AssertTrue; +import org.apache.commons.codec.CharEncoding; +import org.apache.commons.io.IOUtils; import org.apache.commons.io.file.PathUtils; import org.dspace.AbstractIntegrationTestWithDatabase; import org.dspace.app.launcher.ScriptLauncher; import org.dspace.app.matcher.ResourcePolicyMatcher; import org.dspace.app.scripts.handler.impl.TestDSpaceRunnableHandler; import org.dspace.authorize.ResourcePolicy; +import org.dspace.builder.BitstreamBuilder; +import org.dspace.builder.BundleBuilder; import org.dspace.builder.CollectionBuilder; import org.dspace.builder.CommunityBuilder; import org.dspace.builder.ItemBuilder; +import org.dspace.content.Bitstream; +import org.dspace.content.Bundle; import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.Item; import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.ItemService; import org.dspace.core.Constants; +import org.dspace.discovery.DiscoverQuery; +import org.dspace.discovery.SearchService; +import org.dspace.discovery.SearchServiceException; +import org.dspace.discovery.SearchUtils; +import org.dspace.discovery.indexobject.IndexableItem; import org.dspace.eperson.Group; import org.dspace.eperson.GroupTest; import org.dspace.eperson.factory.EPersonServiceFactory; @@ -70,10 +84,10 @@ public class BulkAccessControlIT extends AbstractIntegrationTestWithDatabase { private Path tempDir; private String tempFilePath; - private Collection collection; private ItemService itemService = ContentServiceFactory.getInstance().getItemService(); private GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); + private SearchService searchService = SearchUtils.getSearchService(); @Before @Override @@ -81,19 +95,6 @@ public void setUp() throws Exception { super.setUp(); - context.turnOffAuthorisationSystem(); - - parentCommunity = CommunityBuilder.createCommunity(context) - .withName("Parent Community") - .build(); - - collection = CollectionBuilder.createCollection(context, parentCommunity) - .withName("Collection") - .withEntityType("Publication") - .build(); - - context.restoreAuthSystemState(); - tempDir = Files.createTempDirectory("bulkAccessTest"); tempFilePath = tempDir + "/bulk-access.json"; } @@ -117,6 +118,10 @@ public void performBulkAccessWithoutRequiredParamTest() throws Exception { assertThat(testDSpaceRunnableHandler.getErrorMessages(), hasSize(1)); assertThat(testDSpaceRunnableHandler.getWarningMessages(), empty()); + + assertThat(testDSpaceRunnableHandler.getErrorMessages(), hasItem( + containsString("A target uuid must be provided") + )); } @Test @@ -144,6 +149,10 @@ public void performBulkAccessWithEmptyJsonTest() throws Exception { assertThat(testDSpaceRunnableHandler.getErrorMessages(), hasSize(1)); assertThat(testDSpaceRunnableHandler.getWarningMessages(), empty()); + + assertThat(testDSpaceRunnableHandler.getErrorMessages(), hasItem( + containsString("Error parsing json file") + )); } @Test @@ -180,6 +189,32 @@ public void performBulkAccessWithWrongModeOfItemValueTest() throws Exception { assertThat(testDSpaceRunnableHandler.getErrorMessages(), hasSize(1)); assertThat(testDSpaceRunnableHandler.getWarningMessages(), empty()); + + assertThat(testDSpaceRunnableHandler.getErrorMessages(), hasItem( + containsString("wrong value for item mode") + )); + + json = "{ \"item\": {\n" + + " \"accessConditions\": [\n" + + " {\n" + + " \"name\": \"openaccess\"\n" + + " }\n" + + " ]\n" + + " }}\n"; + + buildJsonFile(json); + + args = new String[] {"bulk-access-control", "-u", item.getID().toString(), "-f", tempFilePath}; + + testDSpaceRunnableHandler = new TestDSpaceRunnableHandler(); + ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl), testDSpaceRunnableHandler, kernelImpl); + + assertThat(testDSpaceRunnableHandler.getErrorMessages(), hasSize(1)); + assertThat(testDSpaceRunnableHandler.getWarningMessages(), empty()); + + assertThat(testDSpaceRunnableHandler.getErrorMessages(), hasItem( + containsString("item mode node must be provided") + )); } @Test @@ -216,6 +251,32 @@ public void performBulkAccessWithWrongModeOfBitstreamValueTest() throws Exceptio assertThat(testDSpaceRunnableHandler.getErrorMessages(), hasSize(1)); assertThat(testDSpaceRunnableHandler.getWarningMessages(), empty()); + + assertThat(testDSpaceRunnableHandler.getErrorMessages(), hasItem( + containsString("wrong value for bitstream mode") + )); + + json = "{ \"bitstream\": {\n" + + " \"accessConditions\": [\n" + + " {\n" + + " \"name\": \"openaccess\"\n" + + " }\n" + + " ]\n" + + " }}\n"; + + buildJsonFile(json); + + args = new String[] {"bulk-access-control", "-u", item.getID().toString(), "-f", tempFilePath}; + + testDSpaceRunnableHandler = new TestDSpaceRunnableHandler(); + ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl), testDSpaceRunnableHandler, kernelImpl); + + assertThat(testDSpaceRunnableHandler.getErrorMessages(), hasSize(1)); + assertThat(testDSpaceRunnableHandler.getWarningMessages(), empty()); + + assertThat(testDSpaceRunnableHandler.getErrorMessages(), hasItem( + containsString("bitstream mode node must be provided") + )); } @Test @@ -252,7 +313,9 @@ public void performBulkAccessWithNotFoundAccessConditionNameTest() throws Except assertThat(testDSpaceRunnableHandler.getErrorMessages(), hasSize(1)); assertThat(testDSpaceRunnableHandler.getWarningMessages(), empty()); - context.restoreAuthSystemState(); + assertThat(testDSpaceRunnableHandler.getErrorMessages(), hasItem( + containsString("wrong access condition ") + )); } @Test @@ -291,6 +354,10 @@ public void performBulkAccessWithInvalidAccessConditionDateTest() throws Excepti assertThat(testDSpaceRunnableHandler.getErrorMessages(), hasSize(1)); assertThat(testDSpaceRunnableHandler.getWarningMessages(), empty()); + assertThat(testDSpaceRunnableHandler.getErrorMessages(), hasItem( + containsString("invalid access condition, The access condition embargo requires a start date.") + )); + String jsonTwo = "{ \"item\": {\n" + " \"mode\": \"add\",\n" + " \"accessConditions\": [\n" + @@ -310,6 +377,127 @@ public void performBulkAccessWithInvalidAccessConditionDateTest() throws Excepti assertThat(testDSpaceRunnableHandler.getErrorMessages(), hasSize(1)); assertThat(testDSpaceRunnableHandler.getWarningMessages(), empty()); + + assertThat(testDSpaceRunnableHandler.getErrorMessages(), hasItem( + containsString("invalid access condition, The access condition lease requires an end date.") + )); + } + + @Test + public void performBulkAccessForCommunityItemsWithBitstreamConstraintsTest() throws Exception { + context.turnOffAuthorisationSystem(); + + Community communityOne = CommunityBuilder.createCommunity(context) + .withName("community one") + .build(); + + context.restoreAuthSystemState(); + + String jsonOne = "{ \"bitstream\": {\n" + + " \"constraints\": {\n" + + " \"uuid\": [\"" + UUID.randomUUID() + "\"]\n" + + " },\n" + + " \"mode\": \"add\",\n" + + " \"accessConditions\": [\n" + + " {\n" + + " \"name\": \"embargo\",\n" + + " \"startDate\": \"2024-06-24T00:00:00.000Z\"\n" + + " }\n" + + " ]\n" + + " }\n" + + "}\n"; + + buildJsonFile(jsonOne); + + String[] args = + new String[] {"bulk-access-control", + "-u", communityOne.getID().toString(), + "-f", tempFilePath}; + + TestDSpaceRunnableHandler testDSpaceRunnableHandler = new TestDSpaceRunnableHandler(); + ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl), testDSpaceRunnableHandler, kernelImpl); + + assertThat(testDSpaceRunnableHandler.getWarningMessages(), empty()); + assertThat(testDSpaceRunnableHandler.getErrorMessages(), hasSize(1)); + assertThat(testDSpaceRunnableHandler.getErrorMessages(), hasItem( + containsString("constraint is not supported when uuid isn't an Item") + )); + } + + @Test + public void performBulkAccessForMultipleItemsWithBitstreamConstraintsTest() throws Exception { + context.turnOffAuthorisationSystem(); + + Community communityOne = CommunityBuilder.createCommunity(context) + .withName("community one") + .build(); + + Community communityTwo = CommunityBuilder.createCommunity(context) + .withName("community two") + .build(); + + context.restoreAuthSystemState(); + + String jsonOne = "{ \"bitstream\": {\n" + + " \"constraints\": {\n" + + " \"uuid\": [\"" + UUID.randomUUID() + "\"]\n" + + " },\n" + + " \"mode\": \"add\",\n" + + " \"accessConditions\": [\n" + + " {\n" + + " \"name\": \"embargo\",\n" + + " \"startDate\": \"2024-06-24T00:00:00.000Z\"\n" + + " }\n" + + " ]\n" + + " }\n" + + "}\n"; + + buildJsonFile(jsonOne); + + String[] args = + new String[] {"bulk-access-control", + "-u", communityOne.getID().toString(), + "-u", communityTwo.getID().toString(), + "-f", tempFilePath}; + + TestDSpaceRunnableHandler testDSpaceRunnableHandler = new TestDSpaceRunnableHandler(); + ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl), testDSpaceRunnableHandler, kernelImpl); + + assertThat(testDSpaceRunnableHandler.getWarningMessages(), empty()); + assertThat(testDSpaceRunnableHandler.getErrorMessages(), hasSize(1)); + assertThat(testDSpaceRunnableHandler.getErrorMessages(), hasItem( + containsString("constraint isn't supported when multiple uuids are provided") + )); + } + + @Test + public void performBulkAccessWithAddModeAndEmptyAccessConditionsTest() throws Exception { + context.turnOffAuthorisationSystem(); + + Community parentCommunity = CommunityBuilder.createCommunity(context) + .withName("parent community") + .build(); + + context.restoreAuthSystemState(); + + String jsonOne = "{ \"item\": {\n" + + " \"mode\": \"add\"\n" + + " }\n" + + "}\n"; + + buildJsonFile(jsonOne); + + String[] args = + new String[] {"bulk-access-control", "-u", parentCommunity.getID().toString(), "-f", tempFilePath}; + + TestDSpaceRunnableHandler testDSpaceRunnableHandler = new TestDSpaceRunnableHandler(); + ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl), testDSpaceRunnableHandler, kernelImpl); + + assertThat(testDSpaceRunnableHandler.getWarningMessages(), empty()); + assertThat(testDSpaceRunnableHandler.getErrorMessages(), hasSize(1)); + assertThat(testDSpaceRunnableHandler.getErrorMessages(), hasItem( + containsString("accessConditions of item must be provided with mode") + )); } @Test @@ -317,32 +505,32 @@ public void performBulkAccessWithValidJsonTest() throws Exception { context.turnOffAuthorisationSystem(); Community parentCommunity = CommunityBuilder.createCommunity(context) - .withName("parent community") - .build(); + .withName("parent community") + .build(); Community subCommunityOne = CommunityBuilder.createSubCommunity(context, parentCommunity) - .withName("sub community one") - .build(); + .withName("sub community one") + .build(); Community subCommunityTwo = CommunityBuilder.createSubCommunity(context, parentCommunity) .withName("sub community two") .build(); Community subCommunityThree = CommunityBuilder.createSubCommunity(context, parentCommunity) - .withName("sub community two") - .build(); + .withName("sub community two") + .build(); Collection collectionOne = CollectionBuilder.createCollection(context, subCommunityOne) - .withName("collection one") - .build(); + .withName("collection one") + .build(); Collection collectionTwo = CollectionBuilder.createCollection(context, subCommunityTwo) - .withName("collection two") - .build(); + .withName("collection two") + .build(); Collection collectionThree = CollectionBuilder.createCollection(context, subCommunityThree) - .withName("collection three") - .build(); + .withName("collection three") + .build(); ItemBuilder.createItem(context, collectionOne).build(); @@ -412,6 +600,240 @@ public void performBulkAccessWithValidJsonTest() throws Exception { + } + + @Test + public void performBulkAccessWithReplaceModeAndEmptyAccessConditionsTest() throws Exception { + context.turnOffAuthorisationSystem(); + + Group anonymousGroup = groupService.findByName(context, Group.ANONYMOUS); + + Community parentCommunity = CommunityBuilder.createCommunity(context) + .withName("parent community") + .build(); + + Community subCommunityOne = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("sub community one") + .build(); + + Community subCommunityTwo = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("sub community two") + .build(); + + Collection collectionOne = CollectionBuilder.createCollection(context, subCommunityOne) + .withName("collection one") + .build(); + + Collection collectionTwo = CollectionBuilder.createCollection(context, subCommunityTwo) + .withName("collection two") + .build(); + + for (int i = 0; i < 20 ; i++) { + ItemBuilder.createItem(context, collectionOne).build(); + } + + for (int i = 0; i < 5 ; i++) { + Item item = ItemBuilder.createItem(context, collectionTwo).build(); + + Bundle bundle = BundleBuilder.createBundle(context, item) + .withName("ORIGINAL") + .build(); + + String bitstreamContent = "Dummy content"; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + BitstreamBuilder.createBitstream(context, bundle, is) + .withName("bistream") + .build(); + } + } + + context.restoreAuthSystemState(); + + String jsonOne = "{ \"item\": {\n" + + " \"mode\": \"replace\"\n" + + " },\n" + + " \"bitstream\": {\n" + + " \"mode\": \"replace\"\n" + + " }\n" + + "}\n"; + + buildJsonFile(jsonOne); + + String[] args = new String[] { + "bulk-access-control", + "-u", subCommunityOne.getID().toString(), + "-u", collectionTwo.getID().toString(), + "-f", tempFilePath + }; + + TestDSpaceRunnableHandler testDSpaceRunnableHandler = new TestDSpaceRunnableHandler(); + ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl), testDSpaceRunnableHandler, kernelImpl); + + assertThat(testDSpaceRunnableHandler.getErrorMessages(), empty()); + assertThat(testDSpaceRunnableHandler.getWarningMessages(), empty()); + + List itemsOfSubCommOne = findItems("location.comm:" + subCommunityOne.getID()); + List itemsOfSubCommTwo = findItems("location.comm:" + subCommunityTwo.getID()); + + assertThat(itemsOfSubCommOne, hasSize(10)); + assertThat(itemsOfSubCommTwo, hasSize(5)); + + assertThat(itemsOfSubCommOne.stream() + .flatMap(item -> findAllBitstreams(item).stream()) + .count(), is(0L)); + + assertThat(itemsOfSubCommTwo.stream() + .flatMap(item -> findAllBitstreams(item).stream()) + .count(), is(5L)); + + for (Item item : itemsOfSubCommOne) { + assertThat(item.getResourcePolicies(), hasSize(1)); + assertThat(item.getResourcePolicies(), hasItem( + matches(Constants.READ, anonymousGroup, ResourcePolicy.TYPE_INHERITED) + )); + } + + for (Item item : itemsOfSubCommTwo) { + assertThat(item.getResourcePolicies(), hasSize(1)); + assertThat(item.getResourcePolicies(), hasItem( + matches(Constants.READ, anonymousGroup, ResourcePolicy.TYPE_INHERITED) + )); + + List bitstreams = findAllBitstreams(item); + + for (Bitstream bitstream : bitstreams) { + assertThat(bitstream.getResourcePolicies(), hasSize(1)); + assertThat(bitstream.getResourcePolicies(), hasItem( + matches(Constants.READ, anonymousGroup, ResourcePolicy.TYPE_INHERITED) + )); + } + } + } + + @Test + public void performBulkAccessWithAddModeTest() throws Exception { + context.turnOffAuthorisationSystem(); + + Group anonymousGroup = groupService.findByName(context, Group.ANONYMOUS); + + Community parentCommunity = CommunityBuilder.createCommunity(context) + .withName("parent community") + .build(); + + Community subCommunityOne = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("sub community one") + .build(); + + Collection collectionOne = CollectionBuilder.createCollection(context, subCommunityOne) + .withName("collection one") + .build(); + + for (int i = 0; i < 5 ; i++) { + + Item item = ItemBuilder.createItem(context, collectionOne).build(); + + Bundle bundle = BundleBuilder.createBundle(context, item) + .withName("ORIGINAL") + .build(); + + String bitstreamContent = "Dummy content"; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + BitstreamBuilder.createBitstream(context, bundle, is) + .withName("bistream") + .build(); + } + } + + context.restoreAuthSystemState(); + + String jsonOne = "{ \"item\": {\n" + + " \"mode\": \"add\",\n" + + " \"accessConditions\": [\n" + + " {\n" + + " \"name\": \"openaccess\"\n" + + " },\n" + + " {\n" + + " \"name\": \"embargo\",\n" + + " \"startDate\": \"2024-06-24T00:00:00.000Z\"\n" + + " }\n" + + " ]\n" + + " },\n" + + " \"bitstream\": {\n" + + " \"mode\": \"add\",\n" + + " \"accessConditions\": [\n" + + " {\n" + + " \"name\": \"openaccess\"\n" + + " },\n" + + " {\n" + + " \"name\": \"lease\",\n" + + " \"endDate\": \"2023-06-24T00:00:00.000Z\"\n" + + " }\n" + + " ]\n" + + " }\n" + + "}\n"; + + buildJsonFile(jsonOne); + + String[] args = new String[] { + "bulk-access-control", + "-u", subCommunityOne.getID().toString(), + "-f", tempFilePath + }; + + TestDSpaceRunnableHandler testDSpaceRunnableHandler = new TestDSpaceRunnableHandler(); + ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl), testDSpaceRunnableHandler, kernelImpl); + + assertThat(testDSpaceRunnableHandler.getErrorMessages(), empty()); + assertThat(testDSpaceRunnableHandler.getWarningMessages(), empty()); + + List itemsOfSubCommOne = findItems("location.comm:" + subCommunityOne.getID()); + + assertThat(itemsOfSubCommOne, hasSize(5)); + + assertThat(itemsOfSubCommOne.stream() + .flatMap(item -> findAllBitstreams(item).stream()) + .count(), is(5L)); + + for (Item item : itemsOfSubCommOne) { + assertThat(item.getResourcePolicies(), hasSize(3)); + assertThat(item.getResourcePolicies(), containsInAnyOrder( + matches(Constants.READ, anonymousGroup, ResourcePolicy.TYPE_INHERITED), + matches(READ, anonymousGroup, "openaccess", TYPE_CUSTOM) + //TODO add also the third resource policy embargo + )); + + List bitstreams = findAllBitstreams(item); + + for (Bitstream bitstream : bitstreams) { + assertThat(bitstream.getResourcePolicies(), hasSize(3)); + assertThat(bitstream.getResourcePolicies(), containsInAnyOrder( + matches(Constants.READ, anonymousGroup, ResourcePolicy.TYPE_INHERITED), + matches(READ, anonymousGroup, "openaccess", TYPE_CUSTOM) + //TODO add also the third resource policy lease + )); + } + } + } + + private List findItems(String query) throws SearchServiceException { + + DiscoverQuery discoverQuery = new DiscoverQuery(); + discoverQuery.setDSpaceObjectFilter(IndexableItem.TYPE); + discoverQuery.setQuery(query); + + return searchService.search(context, discoverQuery) + .getIndexableObjects() + .stream() + .map(indexableObject -> + ((IndexableItem) indexableObject).getIndexedObject()) + .collect(Collectors.toList()); + } + + private List findAllBitstreams(Item item) { + return item.getBundles() + .stream() + .flatMap(bundle -> bundle.getBitstreams().stream()) + .collect(Collectors.toList()); } private void matchItemsResourcePolicies( From 27f874a08b5bd0429fc3132671738c64cabdf095 Mon Sep 17 00:00:00 2001 From: eskander Date: Fri, 12 May 2023 10:58:17 +0300 Subject: [PATCH 165/510] refactoring and adding new test cases --- .../bulkaccesscontrol/BulkAccessControl.java | 21 +- .../BulkAccessControlIT.java | 237 ++++++++++++++---- 2 files changed, 209 insertions(+), 49 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/BulkAccessControl.java b/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/BulkAccessControl.java index a9ca39f9fa06..bb44473ebc81 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 @@ -10,12 +10,11 @@ import static org.apache.commons.collections4.CollectionUtils.isNotEmpty; import static org.dspace.authorize.ResourcePolicy.TYPE_CUSTOM; import static org.dspace.authorize.ResourcePolicy.TYPE_INHERITED; +import static org.dspace.core.Constants.CONTENT_BUNDLE_NAME; import java.io.IOException; import java.io.InputStream; import java.sql.SQLException; -import java.text.DateFormat; -import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.Date; import java.util.Iterator; @@ -363,12 +362,13 @@ private void updateBitstreamsPolicies(Item item, AccessControl accessControl) { if (containsConstraints(accessControl.getBitstream())) { findMatchedBitstreams(item, accessControl.getBitstream().getConstraints().getUuid()) - item.getBundles(Constants.CONTENT_BUNDLE_NAME).stream() - .flatMap(bundle -> bundle.getBitstreams().stream()) - .filter(bitstream -> uuids == null || - uuids.size() == 0 || - uuids.contains(bitstream.getID().toString())) .forEach(bitstream -> updateBitstreamPolicies(bitstream, item, accessControl)); + } else { + item.getBundles(CONTENT_BUNDLE_NAME).stream() + .flatMap(bundle -> bundle.getBitstreams().stream()) + .forEach(bitstream -> + updateBitstreamPolicies(bitstream, item, accessControl)); + } } private boolean containsConstraints(AccessConditionBitstream bitstream) { @@ -377,6 +377,13 @@ private boolean containsConstraints(AccessConditionBitstream bitstream) { isNotEmpty(bitstream.getConstraints().getUuid()); } + private List findMatchedBitstreams(Item item, List uuids) { + return item.getBundles(CONTENT_BUNDLE_NAME).stream() + .flatMap(bundle -> bundle.getBitstreams().stream()) + .filter(bitstream -> uuids.contains(bitstream.getID().toString())) + .collect(Collectors.toList()); + } + private void updateBitstreamPolicies(Bitstream bitstream, Item item, AccessControl accessControl) { if (REPLACE_MODE.equals(accessControl.getBitstream().getMode())) { 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 0e07284c68ae..21efe0420f3a 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 @@ -9,14 +9,12 @@ import static org.dspace.app.matcher.ResourcePolicyMatcher.matches; import static org.dspace.authorize.ResourcePolicy.TYPE_CUSTOM; +import static org.dspace.core.Constants.CONTENT_BUNDLE_NAME; import static org.dspace.core.Constants.READ; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.allOf; -import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.empty; -import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasItem; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.is; @@ -28,21 +26,17 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.text.SimpleDateFormat; -import java.util.Date; import java.util.Iterator; import java.util.List; import java.util.UUID; import java.util.stream.Collectors; -import javax.validation.constraints.AssertTrue; - import org.apache.commons.codec.CharEncoding; import org.apache.commons.io.IOUtils; import org.apache.commons.io.file.PathUtils; +import org.apache.commons.lang3.StringUtils; import org.dspace.AbstractIntegrationTestWithDatabase; import org.dspace.app.launcher.ScriptLauncher; -import org.dspace.app.matcher.ResourcePolicyMatcher; import org.dspace.app.scripts.handler.impl.TestDSpaceRunnableHandler; import org.dspace.authorize.ResourcePolicy; import org.dspace.builder.BitstreamBuilder; @@ -64,17 +58,11 @@ import org.dspace.discovery.SearchUtils; import org.dspace.discovery.indexobject.IndexableItem; import org.dspace.eperson.Group; -import org.dspace.eperson.GroupTest; import org.dspace.eperson.factory.EPersonServiceFactory; import org.dspace.eperson.service.GroupService; -import org.dspace.matcher.DateMatcher; -import org.dspace.util.MultiFormatDateParser; -import org.dspace.utils.DSpace; import org.junit.After; -import org.junit.Assert; import org.junit.Before; import org.junit.Test; -import org.springframework.beans.factory.annotation.Autowired; /** * Basic integration testing for the Bulk Access conditions Feature{@link BulkAccessControl}. @@ -120,7 +108,7 @@ public void performBulkAccessWithoutRequiredParamTest() throws Exception { assertThat(testDSpaceRunnableHandler.getWarningMessages(), empty()); assertThat(testDSpaceRunnableHandler.getErrorMessages(), hasItem( - containsString("A target uuid must be provided") + containsString("A target uuid must be provided with at least on uuid") )); } @@ -401,7 +389,7 @@ public void performBulkAccessForCommunityItemsWithBitstreamConstraintsTest() thr " \"accessConditions\": [\n" + " {\n" + " \"name\": \"embargo\",\n" + - " \"startDate\": \"2024-06-24T00:00:00.000Z\"\n" + + " \"startDate\": \"2024-06-24\"\n" + " }\n" + " ]\n" + " }\n" + @@ -446,7 +434,7 @@ public void performBulkAccessForMultipleItemsWithBitstreamConstraintsTest() thro " \"accessConditions\": [\n" + " {\n" + " \"name\": \"embargo\",\n" + - " \"startDate\": \"2024-06-24T00:00:00.000Z\"\n" + + " \"startDate\": \"2024-06-24\"\n" + " }\n" + " ]\n" + " }\n" + @@ -470,6 +458,85 @@ public void performBulkAccessForMultipleItemsWithBitstreamConstraintsTest() thro )); } + @Test + public void performBulkAccessForSingleItemWithBitstreamConstraintsTest() throws Exception { + context.turnOffAuthorisationSystem(); + + Community community = CommunityBuilder.createCommunity(context) + .withName("community one") + .build(); + + Collection collection = CollectionBuilder.createCollection(context, community) + .withName("collection") + .build(); + + Item item = ItemBuilder.createItem(context, collection).build(); + + Bundle bundle = BundleBuilder.createBundle(context, item) + .withName("ORIGINAL") + .build(); + + String bitstreamOneContent = "Dummy content one"; + Bitstream bitstreamOne; + try (InputStream is = IOUtils.toInputStream(bitstreamOneContent, CharEncoding.UTF_8)) { + bitstreamOne = BitstreamBuilder.createBitstream(context, bundle, is) + .withName("bistream one") + .build(); + } + + String bitstreamTwoContent = "Dummy content of bitstream two"; + Bitstream bitstreamTwo; + try (InputStream is = IOUtils.toInputStream(bitstreamTwoContent, CharEncoding.UTF_8)) { + bitstreamTwo = BitstreamBuilder.createBitstream(context, bundle, is) + .withName("bistream two") + .build(); + } + + context.restoreAuthSystemState(); + + String jsonOne = "{ \"bitstream\": {\n" + + " \"constraints\": {\n" + + " \"uuid\": [\"" + bitstreamOne.getID().toString() + "\"]\n" + + " },\n" + + " \"mode\": \"replace\",\n" + + " \"accessConditions\": [\n" + + " {\n" + + " \"name\": \"embargo\",\n" + + " \"startDate\": \"2024-06-24\"\n" + + " }\n" + + " ]\n" + + " }\n" + + "}\n"; + + buildJsonFile(jsonOne); + + String[] args = + new String[] {"bulk-access-control", + "-u", item.getID().toString(), + "-f", tempFilePath}; + + TestDSpaceRunnableHandler testDSpaceRunnableHandler = new TestDSpaceRunnableHandler(); + ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl), testDSpaceRunnableHandler, kernelImpl); + + assertThat(testDSpaceRunnableHandler.getWarningMessages(), empty()); + assertThat(testDSpaceRunnableHandler.getErrorMessages(), empty()); + + bitstreamOne = context.reloadEntity(bitstreamOne); + bitstreamTwo = context.reloadEntity(bitstreamTwo); + + Group anonymousGroup = groupService.findByName(context, Group.ANONYMOUS); + + assertThat(bitstreamOne.getResourcePolicies(), hasSize(1)); + assertThat(bitstreamOne.getResourcePolicies(), hasItem( + matches(Constants.READ, anonymousGroup, "embargo", TYPE_CUSTOM, "2024-06-24", null, null) + )); + + assertThat(bitstreamTwo.getResourcePolicies(), hasSize(1)); + assertThat(bitstreamTwo.getResourcePolicies(), hasItem( + matches(Constants.READ, anonymousGroup, ResourcePolicy.TYPE_INHERITED) + )); + } + @Test public void performBulkAccessWithAddModeAndEmptyAccessConditionsTest() throws Exception { context.turnOffAuthorisationSystem(); @@ -517,10 +584,8 @@ public void performBulkAccessWithValidJsonTest() throws Exception { .build(); Community subCommunityThree = CommunityBuilder.createSubCommunity(context, parentCommunity) - .withName("sub community two") + .withName("sub community three") .build(); - .withName("sub community three") - .build(); Collection collectionOne = CollectionBuilder.createCollection(context, subCommunityOne) .withName("collection one") @@ -754,7 +819,7 @@ public void performBulkAccessWithAddModeTest() throws Exception { " },\n" + " {\n" + " \"name\": \"embargo\",\n" + - " \"startDate\": \"2024-06-24T00:00:00.000Z\"\n" + + " \"startDate\": \"2024-06-24\"\n" + " }\n" + " ]\n" + " },\n" + @@ -766,7 +831,7 @@ public void performBulkAccessWithAddModeTest() throws Exception { " },\n" + " {\n" + " \"name\": \"lease\",\n" + - " \"endDate\": \"2023-06-24T00:00:00.000Z\"\n" + + " \"endDate\": \"2023-06-24\"\n" + " }\n" + " ]\n" + " }\n" + @@ -798,8 +863,8 @@ public void performBulkAccessWithAddModeTest() throws Exception { assertThat(item.getResourcePolicies(), hasSize(3)); assertThat(item.getResourcePolicies(), containsInAnyOrder( matches(Constants.READ, anonymousGroup, ResourcePolicy.TYPE_INHERITED), - matches(READ, anonymousGroup, "openaccess", TYPE_CUSTOM) - //TODO add also the third resource policy embargo + matches(READ, anonymousGroup, "openaccess", TYPE_CUSTOM), + matches(Constants.READ, anonymousGroup, "embargo", TYPE_CUSTOM, "2024-06-24", null, null) )); List bitstreams = findAllBitstreams(item); @@ -808,8 +873,111 @@ public void performBulkAccessWithAddModeTest() throws Exception { assertThat(bitstream.getResourcePolicies(), hasSize(3)); assertThat(bitstream.getResourcePolicies(), containsInAnyOrder( matches(Constants.READ, anonymousGroup, ResourcePolicy.TYPE_INHERITED), - matches(READ, anonymousGroup, "openaccess", TYPE_CUSTOM) - //TODO add also the third resource policy lease + matches(READ, anonymousGroup, "openaccess", TYPE_CUSTOM), + matches(Constants.READ, anonymousGroup, "lease", TYPE_CUSTOM, null, "2023-06-24", null) + )); + } + } + } + + @Test + public void performBulkAccessWithReplaceModeTest() throws Exception { + context.turnOffAuthorisationSystem(); + + Group anonymousGroup = groupService.findByName(context, Group.ANONYMOUS); + + Community parentCommunity = CommunityBuilder.createCommunity(context) + .withName("parent community") + .build(); + + Community subCommunityOne = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("sub community one") + .build(); + + Collection collectionOne = CollectionBuilder.createCollection(context, subCommunityOne) + .withName("collection one") + .build(); + + for (int i = 0; i < 3 ; i++) { + + Item item = ItemBuilder.createItem(context, collectionOne).build(); + + Bundle bundle = BundleBuilder.createBundle(context, item) + .withName("ORIGINAL") + .build(); + + String bitstreamContent = "Dummy content"; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + BitstreamBuilder.createBitstream(context, bundle, is) + .withName("bistream") + .build(); + } + } + + context.restoreAuthSystemState(); + + String jsonOne = "{ \"item\": {\n" + + " \"mode\": \"replace\",\n" + + " \"accessConditions\": [\n" + + " {\n" + + " \"name\": \"openaccess\"\n" + + " },\n" + + " {\n" + + " \"name\": \"embargo\",\n" + + " \"startDate\": \"2024-06-24\"\n" + + " }\n" + + " ]\n" + + " },\n" + + " \"bitstream\": {\n" + + " \"mode\": \"replace\",\n" + + " \"accessConditions\": [\n" + + " {\n" + + " \"name\": \"openaccess\"\n" + + " },\n" + + " {\n" + + " \"name\": \"lease\",\n" + + " \"endDate\": \"2023-06-24\"\n" + + " }\n" + + " ]\n" + + " }\n" + + "}\n"; + + buildJsonFile(jsonOne); + + String[] args = new String[] { + "bulk-access-control", + "-u", subCommunityOne.getID().toString(), + "-f", tempFilePath + }; + + TestDSpaceRunnableHandler testDSpaceRunnableHandler = new TestDSpaceRunnableHandler(); + ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl), testDSpaceRunnableHandler, kernelImpl); + + assertThat(testDSpaceRunnableHandler.getErrorMessages(), empty()); + assertThat(testDSpaceRunnableHandler.getWarningMessages(), empty()); + + List itemsOfSubCommOne = findItems("location.comm:" + subCommunityOne.getID()); + + assertThat(itemsOfSubCommOne, hasSize(3)); + + assertThat(itemsOfSubCommOne.stream() + .flatMap(item -> findAllBitstreams(item).stream()) + .count(), is(3L)); + + for (Item item : itemsOfSubCommOne) { + assertThat(item.getResourcePolicies(), hasSize(2)); + assertThat(item.getResourcePolicies(), containsInAnyOrder( + matches(READ, anonymousGroup, "openaccess", TYPE_CUSTOM), + matches(Constants.READ, anonymousGroup, "embargo", TYPE_CUSTOM, "2024-06-24", null, null) + )); + + List bitstreams = findAllBitstreams(item); + + for (Bitstream bitstream : bitstreams) { + assertThat(bitstream.getResourcePolicies(), hasSize(2)); + assertThat(bitstream.getResourcePolicies(), containsInAnyOrder( + matches(READ, anonymousGroup, "openaccess", TYPE_CUSTOM), + matches(Constants.READ, anonymousGroup, "lease", TYPE_CUSTOM, null, "2023-06-24", null) )); } } @@ -830,27 +998,12 @@ private List findItems(String query) throws SearchServiceException { } private List findAllBitstreams(Item item) { - return item.getBundles() + return item.getBundles(CONTENT_BUNDLE_NAME) .stream() .flatMap(bundle -> bundle.getBitstreams().stream()) .collect(Collectors.toList()); } - private void matchItemsResourcePolicies( - Iterator itemIterator, Group group, String rpName, String rpType, String startDate, String endDate) { - while (itemIterator.hasNext()) { - Item item = itemIterator.next(); - matchItemResourcePolicies(item, group, rpName, rpType, startDate, endDate); - } - } - - private void matchItemResourcePolicies( - Item item, Group group, String rpName, String rpType, String startDate, String endDate) { - - assertThat(item.getResourcePolicies(), hasItem( - matches(READ, group, rpName, rpType, startDate, endDate, null))); - } - private void buildJsonFile(String json) throws IOException { File file = new File(tempDir + "/bulk-access.json"); Path path = Paths.get(file.getAbsolutePath()); From b24f121c767c1803882f1ae03d91d49e5a751eed Mon Sep 17 00:00:00 2001 From: Jens Vannerum Date: Fri, 12 May 2023 10:11:09 +0200 Subject: [PATCH 166/510] 94299: checkstyle issue after main merge --- .../java/org/dspace/app/rest/BitstreamRestRepositoryIT.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java index 0b65f3e4b9e4..2a1044c28a04 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java @@ -37,10 +37,10 @@ import org.dspace.app.rest.matcher.BitstreamMatcher; import org.dspace.app.rest.matcher.BundleMatcher; import org.dspace.app.rest.matcher.HalMatcher; +import org.dspace.app.rest.matcher.MetadataMatcher; import org.dspace.app.rest.model.patch.Operation; import org.dspace.app.rest.model.patch.RemoveOperation; import org.dspace.app.rest.model.patch.ReplaceOperation; -import org.dspace.app.rest.matcher.MetadataMatcher; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.app.rest.test.MetadataPatchSuite; import org.dspace.authorize.service.ResourcePolicyService; From 1cc6fb2f58018e73e0dc429c33a6d5fedbba5fcd Mon Sep 17 00:00:00 2001 From: damian Date: Fri, 12 May 2023 12:29:22 +0200 Subject: [PATCH 167/510] Item counters refactor. Removing unnecessary code. --- .../main/java/org/dspace/browse/ItemCounter.java | 10 ++++++++++ .../main/java/org/dspace/content/Collection.java | 2 +- .../org/dspace/content/CollectionServiceImpl.java | 6 +----- .../main/java/org/dspace/content/Community.java | 4 ++-- .../org/dspace/content/CommunityServiceImpl.java | 4 +--- .../java/org/dspace/content/dao/CollectionDAO.java | 3 --- .../dspace/content/dao/impl/CollectionDAOImpl.java | 14 ++++---------- .../app/rest/converter/CollectionConverter.java | 3 +-- .../app/rest/converter/CommunityConverter.java | 2 +- 9 files changed, 21 insertions(+), 27 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/browse/ItemCounter.java b/dspace-api/src/main/java/org/dspace/browse/ItemCounter.java index c9c140fb0b5b..27e29ad9fa1c 100644 --- a/dspace-api/src/main/java/org/dspace/browse/ItemCounter.java +++ b/dspace-api/src/main/java/org/dspace/browse/ItemCounter.java @@ -18,6 +18,7 @@ import org.dspace.core.Context; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; +import org.dspace.web.ContextUtil; /** * This class provides a standard interface to all item counting @@ -49,6 +50,8 @@ public class ItemCounter { */ private Context context; + private static ItemCounter instance; + protected ItemService itemService; protected ConfigurationService configurationService; @@ -65,6 +68,13 @@ public ItemCounter(Context context) throws ItemCountException { this.configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); } + public static ItemCounter getInstance() throws ItemCountException { + if (instance == null) { + instance = new ItemCounter(ContextUtil.obtainCurrentRequestContext()); + } + return instance; + } + /** * Get the count of the items in the given container. If the configuration * value webui.strengths.cache is equal to 'true' this will return the diff --git a/dspace-api/src/main/java/org/dspace/content/Collection.java b/dspace-api/src/main/java/org/dspace/content/Collection.java index 33fbb51e3fa7..6ee61abf5720 100644 --- a/dspace-api/src/main/java/org/dspace/content/Collection.java +++ b/dspace-api/src/main/java/org/dspace/content/Collection.java @@ -337,7 +337,7 @@ private CollectionService getCollectionService() { return collectionService; } - public int getAllItems() { + public int countArchivedItem() { try { return collectionService.countArchivedItem(this); } catch (ItemCountException e) { 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 961ad4cbcf0e..092451296e8d 100644 --- a/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java @@ -1051,10 +1051,6 @@ public int countCollectionsWithSubmit(String q, Context context, Community commu @Override public int countArchivedItem(Collection collection) throws ItemCountException { - //TODO load ItemCounter on bean creation - Context context = new Context(); - return new ItemCounter(context).getCount(collection); + return ItemCounter.getInstance().getCount(collection); } - - } diff --git a/dspace-api/src/main/java/org/dspace/content/Community.java b/dspace-api/src/main/java/org/dspace/content/Community.java index 5b8baba8e498..d0477d116f3a 100644 --- a/dspace-api/src/main/java/org/dspace/content/Community.java +++ b/dspace-api/src/main/java/org/dspace/content/Community.java @@ -265,9 +265,9 @@ private CommunityService getCommunityService() { return communityService; } - public int getAllItems() { + public int countArchivedItem() { try { - return communityService.getArchivedItems(this); + return communityService.getArchivedItems(this); } catch (ItemCountException e) { throw new RuntimeException(e); } diff --git a/dspace-api/src/main/java/org/dspace/content/CommunityServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/CommunityServiceImpl.java index 7fe8dfd28951..496a06fd33a5 100644 --- a/dspace-api/src/main/java/org/dspace/content/CommunityServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/CommunityServiceImpl.java @@ -713,8 +713,6 @@ public int countTotal(Context context) throws SQLException { @Override public int getArchivedItems(Community community) throws ItemCountException { - //TODO load ItemCounter on bean creation - Context context = new Context(); - return new ItemCounter(context).getCount(community); + return ItemCounter.getInstance().getCount(community); } } diff --git a/dspace-api/src/main/java/org/dspace/content/dao/CollectionDAO.java b/dspace-api/src/main/java/org/dspace/content/dao/CollectionDAO.java index c8014fb52097..6bb65bbb46d8 100644 --- a/dspace-api/src/main/java/org/dspace/content/dao/CollectionDAO.java +++ b/dspace-api/src/main/java/org/dspace/content/dao/CollectionDAO.java @@ -10,7 +10,6 @@ import java.sql.SQLException; import java.util.List; import java.util.Map; -import java.util.UUID; import org.dspace.content.Collection; import org.dspace.content.Item; @@ -54,6 +53,4 @@ public List findAll(Context context, MetadataField order, Integer li int countRows(Context context) throws SQLException; List> getCollectionsWithBitstreamSizesTotal(Context context) throws SQLException; - - int countArchivedItems(Context context, UUID id) throws SQLException; } diff --git a/dspace-api/src/main/java/org/dspace/content/dao/impl/CollectionDAOImpl.java b/dspace-api/src/main/java/org/dspace/content/dao/impl/CollectionDAOImpl.java index dc8bad7e19c4..c0ef6ea42fce 100644 --- a/dspace-api/src/main/java/org/dspace/content/dao/impl/CollectionDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/dao/impl/CollectionDAOImpl.java @@ -7,9 +7,11 @@ */ package org.dspace.content.dao.impl; -import java.math.BigInteger; import java.sql.SQLException; -import java.util.*; +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; import javax.persistence.Query; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; @@ -180,12 +182,4 @@ public List> getCollectionsWithBitstreamSizesTotal(C } return returnList; } - - @Override - public int countArchivedItems(Context context, UUID collectionId) throws SQLException { - return ((BigInteger) getHibernateSession(context) - .createNativeQuery("select count(*) from collection2item ci left join item i on i.uuid = ci.item_id where i.in_archive and uuid(ci.collection_id)=uuid(:collectionId)") - .setParameter("collectionId", collectionId.toString()) - .getSingleResult()).intValue(); - } } \ No newline at end of file diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/CollectionConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/CollectionConverter.java index 9a5000852df5..836af81ef016 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/CollectionConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/CollectionConverter.java @@ -10,7 +10,6 @@ import org.dspace.app.rest.model.CollectionRest; import org.dspace.app.rest.projection.Projection; import org.dspace.content.Collection; -import org.dspace.core.Context; import org.dspace.discovery.IndexableObject; import org.springframework.stereotype.Component; @@ -27,7 +26,7 @@ public class CollectionConverter extends DSpaceObjectConverter Date: Fri, 12 May 2023 13:13:38 +0200 Subject: [PATCH 168/510] [CST-9634] Fix exception message --- .../org/dspace/app/rest/repository/ScriptRestRepository.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ScriptRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ScriptRestRepository.java index 2fc996a327cc..2942539f9f79 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ScriptRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ScriptRestRepository.java @@ -110,7 +110,7 @@ public ProcessRest startProcess(Context context, String scriptName, List Date: Fri, 12 May 2023 13:15:48 +0200 Subject: [PATCH 169/510] [CST-9634] add administrativeBulkAccess discovery configuration --- dspace/config/spring/api/discovery.xml | 143 +++++++++++++++++++++++++ 1 file changed, 143 insertions(+) diff --git a/dspace/config/spring/api/discovery.xml b/dspace/config/spring/api/discovery.xml index 611e77b27b1c..04b2fa761845 100644 --- a/dspace/config/spring/api/discovery.xml +++ b/dspace/config/spring/api/discovery.xml @@ -75,6 +75,7 @@ + @@ -725,6 +726,147 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + (search.resourcetype:Item AND latestVersion:true) OR search.resourcetype:Collection OR search.resourcetype:Community + -withdrawn:true AND -discoverable:false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + dc.title + dc.contributor.author + dc.creator + dc.subject + + + + + + + + + + + + + + + @@ -2524,6 +2666,7 @@ + placeholder.placeholder.placeholder From f844ca347b90feba504b10b6a1f15e05c0b81879 Mon Sep 17 00:00:00 2001 From: Agustina Martinez Date: Fri, 12 May 2023 13:24:28 +0100 Subject: [PATCH 170/510] Improved fix: override missing register method in VersionedDOIIdentifierProvider --- .../identifier/VersionedDOIIdentifierProvider.java | 9 +++++++++ .../app/rest/repository/IdentifierRestRepository.java | 3 +-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/identifier/VersionedDOIIdentifierProvider.java b/dspace-api/src/main/java/org/dspace/identifier/VersionedDOIIdentifierProvider.java index a933e85d30dd..4374a2549ea9 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/VersionedDOIIdentifierProvider.java +++ b/dspace-api/src/main/java/org/dspace/identifier/VersionedDOIIdentifierProvider.java @@ -153,6 +153,15 @@ public void register(Context context, DSpaceObject dso, String identifier) throw register(context, dso, identifier, this.filter); } + @Override + public String register(Context context, DSpaceObject dso, Filter filter) + throws IdentifierException { + String doi = mint(context, dso, filter); + register(context, dso, doi, filter); + + return doi; + } + @Override public void register(Context context, DSpaceObject dso, String identifier, Filter filter) throws IdentifierException { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/IdentifierRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/IdentifierRestRepository.java index b4a9688942c1..1be569d18e5d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/IdentifierRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/IdentifierRestRepository.java @@ -234,8 +234,7 @@ private IdentifierRest registerDOI(Context context, Item item) DOIIdentifierProvider doiIdentifierProvider = DSpaceServicesFactory.getInstance().getServiceManager() .getServiceByName("org.dspace.identifier.DOIIdentifierProvider", DOIIdentifierProvider.class); if (doiIdentifierProvider != null) { - String doiValue = doiIdentifierProvider.mint(context, item, new TrueFilter()); - doiIdentifierProvider.register(context, item, doiValue, new TrueFilter()); + String doiValue = doiIdentifierProvider.register(context, item, new TrueFilter()); identifierRest.setValue(doiValue); // Get new status DOI doi = doiService.findByDoi(context, doiValue); From 07fbe151260e5952c523bad7d8384c5c4792e052 Mon Sep 17 00:00:00 2001 From: Mohamed Saber Eskander Date: Fri, 12 May 2023 15:35:22 +0300 Subject: [PATCH 171/510] refactoring and added new IT class --- .../bulkaccesscontrol/BulkAccessControl.java | 37 +- .../BulkAccessControlScriptConfiguration.java | 7 +- .../BulkAccessControlIT.java | 2 - .../config/spring/rest/scripts.xml | 5 - .../BulkAccessControlScriptIT.java | 502 ++++++++++++++++++ dspace/config/spring/rest/scripts.xml | 5 - 6 files changed, 514 insertions(+), 44 deletions(-) create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/bulkaccesscontrol/BulkAccessControlScriptIT.java 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 bb44473ebc81..5c32484ec774 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 @@ -57,12 +57,8 @@ import org.dspace.eperson.EPerson; import org.dspace.eperson.factory.EPersonServiceFactory; import org.dspace.scripts.DSpaceRunnable; -import org.dspace.services.ConfigurationService; -import org.dspace.services.factory.DSpaceServicesFactory; import org.dspace.submit.model.AccessConditionOption; import org.dspace.utils.DSpace; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * Implementation of {@link DSpaceRunnable} to perform a bulk access control via json file. @@ -72,14 +68,10 @@ */ public class BulkAccessControl extends DSpaceRunnable> { - private static final Logger LOGGER = LoggerFactory.getLogger(BulkAccessControl.class); - private DSpaceObjectUtils dSpaceObjectUtils; private SearchService searchService; - private ConfigurationService configurationService; - private ItemService itemService; private String filename; @@ -107,7 +99,6 @@ public void setup() throws ParseException { this.searchService = SearchUtils.getSearchService(); this.itemService = ContentServiceFactory.getInstance().getItemService(); this.resourcePolicyService = AuthorizeServiceFactory.getInstance().getResourcePolicyService(); - this.configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); this.bulkAccessConditionConfigurationService = new DSpace().getServiceManager().getServiceByName( "bulkAccessConditionConfigurationService", BulkAccessConditionConfigurationService.class); this.dSpaceObjectUtils = new DSpace().getServiceManager().getServiceByName( @@ -359,16 +350,15 @@ private void setItemPolicies(Item item, AccessControl accessControl) throws SQLE } private void updateBitstreamsPolicies(Item item, AccessControl accessControl) { - - if (containsConstraints(accessControl.getBitstream())) { - findMatchedBitstreams(item, accessControl.getBitstream().getConstraints().getUuid()) - .forEach(bitstream -> updateBitstreamPolicies(bitstream, item, accessControl)); - } else { - item.getBundles(CONTENT_BUNDLE_NAME).stream() - .flatMap(bundle -> bundle.getBitstreams().stream()) - .forEach(bitstream -> - updateBitstreamPolicies(bitstream, item, accessControl)); - } + AccessConditionBitstream.Constraint constraints = accessControl.getBitstream().getConstraints(); + + item.getBundles(CONTENT_BUNDLE_NAME).stream() + .flatMap(bundle -> bundle.getBitstreams().stream()) + .filter(bitstream -> constraints == null || + constraints.getUuid() == null || + constraints.getUuid().size() == 0 || + constraints.getUuid().contains(bitstream.getID().toString())) + .forEach(bitstream -> updateBitstreamPolicies(bitstream, item, accessControl)); } private boolean containsConstraints(AccessConditionBitstream bitstream) { @@ -377,13 +367,6 @@ private boolean containsConstraints(AccessConditionBitstream bitstream) { isNotEmpty(bitstream.getConstraints().getUuid()); } - private List findMatchedBitstreams(Item item, List uuids) { - return item.getBundles(CONTENT_BUNDLE_NAME).stream() - .flatMap(bundle -> bundle.getBitstreams().stream()) - .filter(bitstream -> uuids.contains(bitstream.getID().toString())) - .collect(Collectors.toList()); - } - private void updateBitstreamPolicies(Bitstream bitstream, Item item, AccessControl accessControl) { if (REPLACE_MODE.equals(accessControl.getBitstream().getMode())) { @@ -440,7 +423,7 @@ private void assignCurrentUserInContext() throws SQLException { } } - private void assignSpecialGroupsInContext() throws SQLException { + private void assignSpecialGroupsInContext() { for (UUID uuid : handler.getSpecialGroups()) { context.setSpecialGroup(uuid); } diff --git a/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/BulkAccessControlScriptConfiguration.java b/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/BulkAccessControlScriptConfiguration.java index edda394c8237..1fa6e12abd33 100644 --- a/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/BulkAccessControlScriptConfiguration.java +++ b/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/BulkAccessControlScriptConfiguration.java @@ -39,11 +39,8 @@ public boolean isAllowedToExecute(Context context, List dspaceObjectIDs = commandLineParameters.stream() 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 21efe0420f3a..c1d6a0db64ca 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 @@ -26,7 +26,6 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.Iterator; import java.util.List; import java.util.UUID; import java.util.stream.Collectors; @@ -34,7 +33,6 @@ import org.apache.commons.codec.CharEncoding; import org.apache.commons.io.IOUtils; import org.apache.commons.io.file.PathUtils; -import org.apache.commons.lang3.StringUtils; import org.dspace.AbstractIntegrationTestWithDatabase; import org.dspace.app.launcher.ScriptLauncher; import org.dspace.app.scripts.handler.impl.TestDSpaceRunnableHandler; diff --git a/dspace-server-webapp/src/test/data/dspaceFolder/config/spring/rest/scripts.xml b/dspace-server-webapp/src/test/data/dspaceFolder/config/spring/rest/scripts.xml index 79927833d26e..fd218aa77a8d 100644 --- a/dspace-server-webapp/src/test/data/dspaceFolder/config/spring/rest/scripts.xml +++ b/dspace-server-webapp/src/test/data/dspaceFolder/config/spring/rest/scripts.xml @@ -43,9 +43,4 @@ - - - - - diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/bulkaccesscontrol/BulkAccessControlScriptIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/bulkaccesscontrol/BulkAccessControlScriptIT.java new file mode 100644 index 000000000000..da0f90ca97c7 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/bulkaccesscontrol/BulkAccessControlScriptIT.java @@ -0,0 +1,502 @@ +/** + * 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.bulkaccesscontrol; + +import static com.jayway.jsonpath.JsonPath.read; +import static org.dspace.app.matcher.ResourcePolicyMatcher.matches; +import static org.dspace.authorize.ResourcePolicy.TYPE_CUSTOM; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.hasSize; +import static org.junit.Assert.assertTrue; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Collectors; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.dspace.app.rest.converter.DSpaceRunnableParameterConverter; +import org.dspace.app.rest.model.ParameterValueRest; +import org.dspace.app.rest.model.ProcessRest; +import org.dspace.app.rest.model.ScriptRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.app.rest.test.AbstractEntityIntegrationTest; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.ItemBuilder; +import org.dspace.builder.ProcessBuilder; +import org.dspace.content.Collection; +import org.dspace.content.Community; +import org.dspace.content.Item; +import org.dspace.core.Constants; +import org.dspace.eperson.Group; +import org.dspace.eperson.service.GroupService; +import org.dspace.scripts.DSpaceCommandLineParameter; +import org.dspace.scripts.Process; +import org.dspace.scripts.service.ProcessService; +import org.junit.After; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.mock.web.MockMultipartFile; + +/** + * Basic integration testing for the bulk access Import feature via UI {@link BulkAccessControl}. + * + * @author Mohamed Eskander (mohamed.eskander at 4science.com) + */ +public class BulkAccessControlScriptIT extends AbstractEntityIntegrationTest { + + @Autowired + private DSpaceRunnableParameterConverter dSpaceRunnableParameterConverter; + + @Autowired + private GroupService groupService; + + @Autowired + private ProcessService processService; + + private final static String SCRIPTS_ENDPOINT = "/api/" + ScriptRest.CATEGORY + "/" + ScriptRest.PLURAL_NAME; + private final static String CURATE_SCRIPT_ENDPOINT = SCRIPTS_ENDPOINT + "/bulk-access-control/" + + ProcessRest.PLURAL_NAME; + + @After + @Override + public void destroy() throws Exception { + List processes = processService.findAll(context); + for (Process process : processes) { + ProcessBuilder.deleteProcess(process.getID()); + } + + super.destroy(); + } + + @Test + public void bulkAccessScriptWithAdminUserTest() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Community subCommunity = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + + Collection collection = CollectionBuilder.createCollection(context, subCommunity) + .withName("collection") + .build(); + + Item item = ItemBuilder.createItem(context, collection) + .withTitle("Public item") + .withSubject("ExtraEntry") + .build(); + + String json = "{ \"item\": {\n" + + " \"mode\": \"replace\",\n" + + " \"accessConditions\": [\n" + + " {\n" + + " \"name\": \"openaccess\"\n" + + " }\n" + + " ]\n" + + " }}\n"; + + InputStream inputStream = new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8)); + + MockMultipartFile bitstreamFile = + new MockMultipartFile("file", "test.json", MediaType.TEXT_PLAIN_VALUE, inputStream); + + AtomicReference idRef = new AtomicReference<>(); + + LinkedList parameters = new LinkedList<>(); + + parameters.add(new DSpaceCommandLineParameter("-u", item.getID().toString())); + parameters.add(new DSpaceCommandLineParameter("-f", "test.json")); + + + List list = + parameters.stream() + .map(dSpaceCommandLineParameter -> dSpaceRunnableParameterConverter + .convert(dSpaceCommandLineParameter, Projection.DEFAULT)) + .collect(Collectors.toList()); + + context.restoreAuthSystemState(); + + try { + String token = getAuthToken(admin.getEmail(), password); + + getClient(token) + .perform( + multipart(CURATE_SCRIPT_ENDPOINT) + .file(bitstreamFile) + .param("properties", new ObjectMapper().writeValueAsString(list))) + .andExpect(status().isAccepted()) + .andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), "$.processId"))); + } finally { + ProcessBuilder.deleteProcess(idRef.get()); + } + } + + @Test + public void bulkAccessScriptWithAdminUserOfTargetCommunityTest() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .withAdminGroup(eperson) + .build(); + + String json = "{ \"item\": {\n" + + " \"mode\": \"replace\",\n" + + " \"accessConditions\": [\n" + + " {\n" + + " \"name\": \"openaccess\"\n" + + " }\n" + + " ]\n" + + " }}\n"; + + InputStream inputStream = new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8)); + + MockMultipartFile bitstreamFile = + new MockMultipartFile("file", "test.json", MediaType.TEXT_PLAIN_VALUE, inputStream); + + AtomicReference idRef = new AtomicReference<>(); + + LinkedList parameters = new LinkedList<>(); + + parameters.add(new DSpaceCommandLineParameter("-u", parentCommunity.getID().toString())); + parameters.add(new DSpaceCommandLineParameter("-f", "test.json")); + + + List list = + parameters.stream() + .map(dSpaceCommandLineParameter -> dSpaceRunnableParameterConverter + .convert(dSpaceCommandLineParameter, Projection.DEFAULT)) + .collect(Collectors.toList()); + + context.restoreAuthSystemState(); + + try { + String token = getAuthToken(eperson.getEmail(), password); + + getClient(token) + .perform( + multipart(CURATE_SCRIPT_ENDPOINT) + .file(bitstreamFile) + .param("properties", new ObjectMapper().writeValueAsString(list))) + .andExpect(status().isAccepted()) + .andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), "$.processId"))); + } finally { + ProcessBuilder.deleteProcess(idRef.get()); + } + } + + @Test + public void bulkAccessScriptWithAdminUserOfTargetCollectionTest() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Community subCommunity = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + + Collection collection = CollectionBuilder.createCollection(context, subCommunity) + .withName("collection") + .withAdminGroup(eperson) + .build(); + + String json = "{ \"item\": {\n" + + " \"mode\": \"replace\",\n" + + " \"accessConditions\": [\n" + + " {\n" + + " \"name\": \"openaccess\"\n" + + " }\n" + + " ]\n" + + " }}\n"; + + InputStream inputStream = new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8)); + + MockMultipartFile bitstreamFile = + new MockMultipartFile("file", "test.json", MediaType.TEXT_PLAIN_VALUE, inputStream); + + AtomicReference idRef = new AtomicReference<>(); + + LinkedList parameters = new LinkedList<>(); + + parameters.add(new DSpaceCommandLineParameter("-u", collection.getID().toString())); + parameters.add(new DSpaceCommandLineParameter("-f", "test.json")); + + + List list = + parameters.stream() + .map(dSpaceCommandLineParameter -> dSpaceRunnableParameterConverter + .convert(dSpaceCommandLineParameter, Projection.DEFAULT)) + .collect(Collectors.toList()); + + context.restoreAuthSystemState(); + + try { + String token = getAuthToken(eperson.getEmail(), password); + + getClient(token) + .perform( + multipart(CURATE_SCRIPT_ENDPOINT) + .file(bitstreamFile) + .param("properties", new ObjectMapper().writeValueAsString(list))) + .andExpect(status().isAccepted()) + .andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), "$.processId"))); + } finally { + ProcessBuilder.deleteProcess(idRef.get()); + } + } + + @Test + public void bulkAccessScriptWithAdminUserOfTargetItemTest() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Community subCommunity = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + + Collection collection = CollectionBuilder.createCollection(context, subCommunity) + .withName("collection") + .build(); + + Item item = ItemBuilder.createItem(context, collection) + .withTitle("Public item") + .withSubject("ExtraEntry") + .withAdminUser(eperson) + .build(); + + String json = "{ \"item\": {\n" + + " \"mode\": \"replace\",\n" + + " \"accessConditions\": [\n" + + " {\n" + + " \"name\": \"openaccess\"\n" + + " }\n" + + " ]\n" + + " }}\n"; + + InputStream inputStream = new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8)); + + MockMultipartFile bitstreamFile = + new MockMultipartFile("file", "test.json", MediaType.TEXT_PLAIN_VALUE, inputStream); + + AtomicReference idRef = new AtomicReference<>(); + + LinkedList parameters = new LinkedList<>(); + + parameters.add(new DSpaceCommandLineParameter("-u", item.getID().toString())); + parameters.add(new DSpaceCommandLineParameter("-f", "test.json")); + + + List list = + parameters.stream() + .map(dSpaceCommandLineParameter -> dSpaceRunnableParameterConverter + .convert(dSpaceCommandLineParameter, Projection.DEFAULT)) + .collect(Collectors.toList()); + + context.restoreAuthSystemState(); + + try { + String token = getAuthToken(eperson.getEmail(), password); + + getClient(token) + .perform( + multipart(CURATE_SCRIPT_ENDPOINT) + .file(bitstreamFile) + .param("properties", new ObjectMapper().writeValueAsString(list))) + .andExpect(status().isAccepted()) + .andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), "$.processId"))); + } finally { + ProcessBuilder.deleteProcess(idRef.get()); + } + } + + @Test + public void bulkAccessScriptWithMultipleTargetUuidsWithAdminUserTest() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Community subCommunity = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + + Collection collection = CollectionBuilder.createCollection(context, subCommunity) + .withName("collection") + .build(); + + Item itemOne = ItemBuilder.createItem(context, collection) + .withTitle("Public item one") + .build(); + + Item itemTwo = ItemBuilder.createItem(context, collection) + .withTitle("Public item two") + .build(); + + Item itemThree = ItemBuilder.createItem(context, collection) + .withTitle("Public item three") + .build(); + + String json = "{ \"item\": {\n" + + " \"mode\": \"replace\",\n" + + " \"accessConditions\": [\n" + + " {\n" + + " \"name\": \"openaccess\"\n" + + " }\n" + + " ]\n" + + " }}\n"; + + InputStream inputStream = new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8)); + + MockMultipartFile bitstreamFile = + new MockMultipartFile("file", "test.json", MediaType.TEXT_PLAIN_VALUE, inputStream); + + AtomicReference idRef = new AtomicReference<>(); + + LinkedList parameters = new LinkedList<>(); + + parameters.add(new DSpaceCommandLineParameter("-u", itemOne.getID().toString())); + parameters.add(new DSpaceCommandLineParameter("-u", itemTwo.getID().toString())); + parameters.add(new DSpaceCommandLineParameter("-u", itemThree.getID().toString())); + parameters.add(new DSpaceCommandLineParameter("-f", "test.json")); + + + List list = + parameters.stream() + .map(dSpaceCommandLineParameter -> dSpaceRunnableParameterConverter + .convert(dSpaceCommandLineParameter, Projection.DEFAULT)) + .collect(Collectors.toList()); + + context.restoreAuthSystemState(); + + try { + String token = getAuthToken(admin.getEmail(), password); + + getClient(token) + .perform( + multipart(CURATE_SCRIPT_ENDPOINT) + .file(bitstreamFile) + .param("properties", new ObjectMapper().writeValueAsString(list))) + .andExpect(status().isAccepted()) + .andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), "$.processId"))); + + itemOne = context.reloadEntity(itemOne); + itemTwo = context.reloadEntity(itemTwo); + itemThree = context.reloadEntity(itemThree); + + Group anonymousGroup = groupService.findByName(context, Group.ANONYMOUS); + + assertThat(itemOne.getResourcePolicies(), hasSize(1)); + assertThat(itemTwo.getResourcePolicies(), hasSize(1)); + assertThat(itemThree.getResourcePolicies(), hasSize(1)); + + assertThat(itemOne.getResourcePolicies(), hasItem( + matches(Constants.READ, anonymousGroup, "openaccess", TYPE_CUSTOM) + )); + + assertThat(itemTwo.getResourcePolicies(), hasItem( + matches(Constants.READ, anonymousGroup, "openaccess", TYPE_CUSTOM) + )); + + assertThat(itemThree.getResourcePolicies(), hasItem( + matches(Constants.READ, anonymousGroup, "openaccess", TYPE_CUSTOM) + )); + + } finally { + ProcessBuilder.deleteProcess(idRef.get()); + } + } + + @Test + public void bulkAccessScriptWithoutTargetUUIDParameterTest() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .withAdminGroup(eperson) + .build(); + + context.restoreAuthSystemState(); + + String token = getAuthToken(eperson.getEmail(), password); + + getClient(token) + .perform( + multipart(CURATE_SCRIPT_ENDPOINT) + .param("properties", new ObjectMapper().writeValueAsString(List.of())) + ) + .andExpect(status().isInternalServerError()) + .andExpect(result -> assertTrue(result.getResolvedException() + .getMessage() + .contains("At least one target uuid must be provided"))); + } + + @Test + public void bulkAccessScriptWithNormalUserTest() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + String json = "{ \"item\": {\n" + + " \"mode\": \"replace\",\n" + + " \"accessConditions\": [\n" + + " {\n" + + " \"name\": \"openaccess\"\n" + + " }\n" + + " ]\n" + + " }}\n"; + + InputStream inputStream = new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8)); + + MockMultipartFile bitstreamFile = + new MockMultipartFile("file", "test.json", MediaType.TEXT_PLAIN_VALUE, inputStream); + + LinkedList parameters = new LinkedList<>(); + + parameters.add(new DSpaceCommandLineParameter("-u", parentCommunity.getID().toString())); + parameters.add(new DSpaceCommandLineParameter("-f", "test.json")); + + + List list = + parameters.stream() + .map(dSpaceCommandLineParameter -> dSpaceRunnableParameterConverter + .convert(dSpaceCommandLineParameter, Projection.DEFAULT)) + .collect(Collectors.toList()); + + context.restoreAuthSystemState(); + + String token = getAuthToken(eperson.getEmail(), password); + + getClient(token) + .perform( + multipart(CURATE_SCRIPT_ENDPOINT) + .file(bitstreamFile) + .param("properties", new ObjectMapper().writeValueAsString(list))) + .andExpect(status().isForbidden()); + } + +} diff --git a/dspace/config/spring/rest/scripts.xml b/dspace/config/spring/rest/scripts.xml index eda8c579a89c..0fae31d552c1 100644 --- a/dspace/config/spring/rest/scripts.xml +++ b/dspace/config/spring/rest/scripts.xml @@ -64,9 +64,4 @@ - - - - - From 2e69ba7f63537636c0f78da04098a17beddb5ecb Mon Sep 17 00:00:00 2001 From: samuel Date: Wed, 23 Nov 2022 15:39:56 +0100 Subject: [PATCH 172/510] 96598: Test feedback pt. 3: submission - repair auto-refresh --- .../java/org/dspace/app/util/DCInputSet.java | 3 +- .../src/main/java/org/dspace/core/Utils.java | 4 ++- .../app/rest/submit/step/DescribeStep.java | 35 ++++++++++++++++++- .../step/validation/MetadataValidation.java | 5 ++- 4 files changed, 43 insertions(+), 4 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/util/DCInputSet.java b/dspace-api/src/main/java/org/dspace/app/util/DCInputSet.java index bfd4270cf27f..06ef1f979cde 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/DCInputSet.java +++ b/dspace-api/src/main/java/org/dspace/app/util/DCInputSet.java @@ -9,6 +9,7 @@ import java.util.List; import java.util.Map; +import java.util.Objects; import org.apache.commons.lang3.StringUtils; import org.dspace.core.Utils; @@ -121,7 +122,7 @@ public boolean isFieldPresent(String fieldName) { } } else { String fullName = field.getFieldName(); - if (fullName.equals(fieldName)) { + if (Objects.equals(fullName, fieldName)) { return true; } } 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 b9fff20c7674..2e30029cc530 100644 --- a/dspace-api/src/main/java/org/dspace/core/Utils.java +++ b/dspace-api/src/main/java/org/dspace/core/Utils.java @@ -415,7 +415,9 @@ public static String[] tokenize(String metadata) { * @return metadata field key */ public static String standardize(String schema, String element, String qualifier, String separator) { - if (StringUtils.isBlank(qualifier)) { + if (StringUtils.isBlank(element)) { + return null; + } else if (StringUtils.isBlank(qualifier)) { return schema + separator + element; } else { return schema + separator + element + separator + qualifier; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/DescribeStep.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/DescribeStep.java index 10a96a557f6c..09ffa900c287 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/DescribeStep.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/DescribeStep.java @@ -7,6 +7,9 @@ */ package org.dspace.app.rest.submit.step; +import static org.apache.commons.collections4.CollectionUtils.isEmpty; +import static org.apache.commons.lang3.StringUtils.isBlank; + import java.util.ArrayList; import java.util.List; import javax.servlet.http.HttpServletRequest; @@ -28,7 +31,11 @@ import org.dspace.app.util.DCInputsReaderException; import org.dspace.app.util.SubmissionStepConfig; import org.dspace.content.InProgressSubmission; +import org.dspace.content.Item; import org.dspace.content.MetadataValue; +import org.dspace.content.RelationshipMetadataService; +import org.dspace.content.RelationshipMetadataValue; +import org.dspace.content.factory.ContentServiceFactory; import org.dspace.core.Context; import org.dspace.core.Utils; @@ -45,6 +52,9 @@ public class DescribeStep extends AbstractProcessingStep { private DCInputsReader inputReader; + private RelationshipMetadataService relationshipMetadataService = + ContentServiceFactory.getInstance().getRelationshipMetadataService(); + public DescribeStep() throws DCInputsReaderException { inputReader = new DCInputsReader(); } @@ -73,7 +83,10 @@ private void readField(InProgressSubmission obj, SubmissionStepConfig config, Da fieldsName.add(input.getFieldName() + "." + (String) qualifier); } } else { - fieldsName.add(input.getFieldName()); + String fieldName = input.getFieldName(); + if (fieldName != null) { + fieldsName.add(fieldName); + } } @@ -108,6 +121,26 @@ private void readField(InProgressSubmission obj, SubmissionStepConfig config, Da } } } + + if (input.isRelationshipField() && isBlank(input.getFieldName())) { + Item item = obj.getItem(); + String key = "relationship." + input.getRelationshipType(); + if (isEmpty(data.getMetadata().get(key))) { + data.getMetadata().put(key, new ArrayList<>()); + } + for (RelationshipMetadataValue metadataValue : + relationshipMetadataService.getRelationshipMetadata(item, true)) { + if (metadataValue.getMetadataField().getElement().equals(input.getRelationshipType())) { + MetadataValueRest dto = new MetadataValueRest(); + dto.setAuthority(metadataValue.getAuthority()); + dto.setConfidence(metadataValue.getConfidence()); + dto.setLanguage(metadataValue.getLanguage()); + dto.setPlace(metadataValue.getPlace()); + dto.setValue(metadataValue.getValue()); + data.getMetadata().get(key).add(dto); + } + } + } } } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/validation/MetadataValidation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/validation/MetadataValidation.java index 9a0132674643..7a8249a77096 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/validation/MetadataValidation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/validation/MetadataValidation.java @@ -83,7 +83,10 @@ public List validate(SubmissionService submissionService, InProgressS } } else { - fieldsName.add(input.getFieldName()); + String fieldName = input.getFieldName(); + if (fieldName != null) { + fieldsName.add(fieldName); + } } for (String fieldName : fieldsName) { From f49b6f24ba14646eb5727116b80c9625f53cd6e0 Mon Sep 17 00:00:00 2001 From: samuel Date: Fri, 10 Feb 2023 12:39:09 +0100 Subject: [PATCH 173/510] 99221: Fix minor issues with Relationships in submission - repair relationship error --- dspace-api/src/main/java/org/dspace/app/util/DCInputSet.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/dspace-api/src/main/java/org/dspace/app/util/DCInputSet.java b/dspace-api/src/main/java/org/dspace/app/util/DCInputSet.java index 06ef1f979cde..3ab250bfc28e 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/DCInputSet.java +++ b/dspace-api/src/main/java/org/dspace/app/util/DCInputSet.java @@ -120,6 +120,9 @@ public boolean isFieldPresent(String fieldName) { return true; } } + } else if (field.isRelationshipField() && + ("relation." + field.getRelationshipType()).equals(fieldName)) { + return true; } else { String fullName = field.getFieldName(); if (Objects.equals(fullName, fieldName)) { From c6b044222d356025d6051fcf676d1acf289c4d4a Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 12 May 2023 10:53:59 -0500 Subject: [PATCH 174/510] Remove setting to wait on two codecov builds --- .codecov.yml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/.codecov.yml b/.codecov.yml index a628d33cbec5..326dd3e0b29e 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -4,13 +4,6 @@ # Can be validated via instructions at: # https://docs.codecov.io/docs/codecov-yaml#validate-your-repository-yaml -# Tell Codecov not to send a coverage notification until (at least) 2 builds are completed -# Since we run Unit & Integration tests in parallel, this lets Codecov know that coverage -# needs to be merged across those builds -codecov: - notify: - after_n_builds: 2 - # Settings related to code coverage analysis coverage: status: From 95af5fff410b92ef9224baed50c6900e7ae88754 Mon Sep 17 00:00:00 2001 From: Agustina Martinez Date: Mon, 15 May 2023 08:40:57 +0100 Subject: [PATCH 175/510] VersionedDOIIdentifierProvider: add non-fatal check that dso is of type Item --- .../dspace/identifier/VersionedDOIIdentifierProvider.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/dspace-api/src/main/java/org/dspace/identifier/VersionedDOIIdentifierProvider.java b/dspace-api/src/main/java/org/dspace/identifier/VersionedDOIIdentifierProvider.java index 4374a2549ea9..4ca186eaab49 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/VersionedDOIIdentifierProvider.java +++ b/dspace-api/src/main/java/org/dspace/identifier/VersionedDOIIdentifierProvider.java @@ -156,7 +156,13 @@ public void register(Context context, DSpaceObject dso, String identifier) throw @Override public String register(Context context, DSpaceObject dso, Filter filter) throws IdentifierException { + if (!(dso instanceof Item)) { + // DOIs are currently assigned only to Items + return null; + } + String doi = mint(context, dso, filter); + register(context, dso, doi, filter); return doi; From 2f1d52bf3fbb40a7c02447a743a9843fe5ca927b Mon Sep 17 00:00:00 2001 From: Sascha Szott Date: Tue, 16 May 2023 09:09:17 +0200 Subject: [PATCH 176/510] removed eperson.subscription.onlynew --- dspace/config/dspace.cfg | 4 ---- 1 file changed, 4 deletions(-) diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 65b1f951fad3..cb69f9841fb0 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -893,10 +893,6 @@ org.dspace.app.batchitemimport.work.dir = ${dspace.dir}/imports # default = false, (disabled) #org.dspace.content.Collection.findAuthorizedPerformanceOptimize = true -# For backwards compatibility, the subscription emails by default include any modified items -# uncomment the following entry for only new items to be emailed -# eperson.subscription.onlynew = true - # Identifier providers. # Following are configuration values for the EZID DOI provider, with appropriate From 84ac53c7c2839f901d73247d33de63e67e32258c Mon Sep 17 00:00:00 2001 From: aroman-arvo Date: Tue, 16 May 2023 13:29:45 +0200 Subject: [PATCH 177/510] Security issue fixed: request item token exposed --- .../repository/RequestItemRepository.java | 4 +- .../app/rest/RequestItemRepositoryIT.java | 61 +++---------------- 2 files changed, 12 insertions(+), 53 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RequestItemRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RequestItemRepository.java index 198d0f765f87..6fe3452a7fb8 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RequestItemRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RequestItemRepository.java @@ -207,8 +207,8 @@ public RequestItemRest createAndReturn(Context ctx) } catch (IOException | SQLException ex) { throw new RuntimeException("Request not sent.", ex); } - - return requestItemConverter.convert(ri, Projection.DEFAULT); + // #8636 - Security issue: Should not return RequestItemRest to avoid token exposure + return null; } // NOTICE: there is no service method for this -- requests are never deleted? diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RequestItemRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RequestItemRepositoryIT.java index 4a90efb2c1bc..a9b2aaf3f279 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RequestItemRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RequestItemRepositoryIT.java @@ -221,32 +221,11 @@ public void testCreateAndReturnAuthenticated() // Create it and see if it was created correctly. ObjectMapper mapper = new ObjectMapper(); String authToken = getAuthToken(eperson.getEmail(), password); - AtomicReference requestTokenRef = new AtomicReference<>(); - try { - getClient(authToken) - .perform(post(URI_ROOT) - .content(mapper.writeValueAsBytes(rir)) - .contentType(contentType)) - .andExpect(status().isCreated()) - .andExpect(content().contentType(contentType)) - .andExpect(jsonPath("$", Matchers.allOf( - hasJsonPath("$.id", not(is(emptyOrNullString()))), - hasJsonPath("$.type", is(RequestItemRest.NAME)), - hasJsonPath("$.token", not(is(emptyOrNullString()))), - hasJsonPath("$.requestEmail", is(eperson.getEmail())), - hasJsonPath("$.requestMessage", is(RequestItemBuilder.REQ_MESSAGE)), - hasJsonPath("$.requestName", is(eperson.getFullName())), - hasJsonPath("$.allfiles", is(true)), - // TODO should be an ISO datetime - hasJsonPath("$.requestDate", not(is(emptyOrNullString()))), - hasJsonPath("$._links.self.href", not(is(emptyOrNullString()))) - ))) - .andDo((var result) -> requestTokenRef.set( - read(result.getResponse().getContentAsString(), "token"))); - } finally { - // Clean up the created request. - RequestItemBuilder.deleteRequestItem(requestTokenRef.get()); - } + getClient(authToken) + .perform(post(URI_ROOT) + .content(mapper.writeValueAsBytes(rir)) + .contentType(contentType)) + .andExpect(status().isCreated()); } /** @@ -273,31 +252,11 @@ public void testCreateAndReturnNotAuthenticated() // Create it and see if it was created correctly. ObjectMapper mapper = new ObjectMapper(); - AtomicReference requestTokenRef = new AtomicReference<>(); - try { - getClient().perform(post(URI_ROOT) - .content(mapper.writeValueAsBytes(rir)) - .contentType(contentType)) - .andExpect(status().isCreated()) - .andExpect(content().contentType(contentType)) - .andExpect(jsonPath("$", Matchers.allOf( - hasJsonPath("$.id", not(is(emptyOrNullString()))), - hasJsonPath("$.type", is(RequestItemRest.NAME)), - hasJsonPath("$.token", not(is(emptyOrNullString()))), - hasJsonPath("$.requestEmail", is(RequestItemBuilder.REQ_EMAIL)), - hasJsonPath("$.requestMessage", is(RequestItemBuilder.REQ_MESSAGE)), - hasJsonPath("$.requestName", is(RequestItemBuilder.REQ_NAME)), - hasJsonPath("$.allfiles", is(false)), - // TODO should be an ISO datetime - hasJsonPath("$.requestDate", not(is(emptyOrNullString()))), - hasJsonPath("$._links.self.href", not(is(emptyOrNullString()))) - ))) - .andDo((var result) -> requestTokenRef.set( - read(result.getResponse().getContentAsString(), "token"))); - } finally { - // Clean up the created request. - RequestItemBuilder.deleteRequestItem(requestTokenRef.get()); - } + getClient().perform(post(URI_ROOT) + .content(mapper.writeValueAsBytes(rir)) + .contentType(contentType)) + .andExpect(status().isCreated()); + } /** From 5f58e587f321989e277f2a37591cd9e939a9b5a2 Mon Sep 17 00:00:00 2001 From: aroman-arvo Date: Tue, 16 May 2023 13:29:45 +0200 Subject: [PATCH 178/510] Security issue fixed: request item token exposed --- .../repository/RequestItemRepository.java | 4 +- .../app/rest/RequestItemRepositoryIT.java | 64 +++---------------- 2 files changed, 12 insertions(+), 56 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RequestItemRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RequestItemRepository.java index 198d0f765f87..6fe3452a7fb8 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RequestItemRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RequestItemRepository.java @@ -207,8 +207,8 @@ public RequestItemRest createAndReturn(Context ctx) } catch (IOException | SQLException ex) { throw new RuntimeException("Request not sent.", ex); } - - return requestItemConverter.convert(ri, Projection.DEFAULT); + // #8636 - Security issue: Should not return RequestItemRest to avoid token exposure + return null; } // NOTICE: there is no service method for this -- requests are never deleted? diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RequestItemRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RequestItemRepositoryIT.java index 4a90efb2c1bc..2000b6e47e61 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RequestItemRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RequestItemRepositoryIT.java @@ -8,13 +8,10 @@ package org.dspace.app.rest; import static com.jayway.jsonpath.JsonPath.read; -import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; import static org.exparity.hamcrest.date.DateMatchers.within; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.not; -import static org.hamcrest.text.IsEmptyString.emptyOrNullString; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; @@ -221,32 +218,11 @@ public void testCreateAndReturnAuthenticated() // Create it and see if it was created correctly. ObjectMapper mapper = new ObjectMapper(); String authToken = getAuthToken(eperson.getEmail(), password); - AtomicReference requestTokenRef = new AtomicReference<>(); - try { - getClient(authToken) - .perform(post(URI_ROOT) - .content(mapper.writeValueAsBytes(rir)) - .contentType(contentType)) - .andExpect(status().isCreated()) - .andExpect(content().contentType(contentType)) - .andExpect(jsonPath("$", Matchers.allOf( - hasJsonPath("$.id", not(is(emptyOrNullString()))), - hasJsonPath("$.type", is(RequestItemRest.NAME)), - hasJsonPath("$.token", not(is(emptyOrNullString()))), - hasJsonPath("$.requestEmail", is(eperson.getEmail())), - hasJsonPath("$.requestMessage", is(RequestItemBuilder.REQ_MESSAGE)), - hasJsonPath("$.requestName", is(eperson.getFullName())), - hasJsonPath("$.allfiles", is(true)), - // TODO should be an ISO datetime - hasJsonPath("$.requestDate", not(is(emptyOrNullString()))), - hasJsonPath("$._links.self.href", not(is(emptyOrNullString()))) - ))) - .andDo((var result) -> requestTokenRef.set( - read(result.getResponse().getContentAsString(), "token"))); - } finally { - // Clean up the created request. - RequestItemBuilder.deleteRequestItem(requestTokenRef.get()); - } + getClient(authToken) + .perform(post(URI_ROOT) + .content(mapper.writeValueAsBytes(rir)) + .contentType(contentType)) + .andExpect(status().isCreated()); } /** @@ -273,31 +249,11 @@ public void testCreateAndReturnNotAuthenticated() // Create it and see if it was created correctly. ObjectMapper mapper = new ObjectMapper(); - AtomicReference requestTokenRef = new AtomicReference<>(); - try { - getClient().perform(post(URI_ROOT) - .content(mapper.writeValueAsBytes(rir)) - .contentType(contentType)) - .andExpect(status().isCreated()) - .andExpect(content().contentType(contentType)) - .andExpect(jsonPath("$", Matchers.allOf( - hasJsonPath("$.id", not(is(emptyOrNullString()))), - hasJsonPath("$.type", is(RequestItemRest.NAME)), - hasJsonPath("$.token", not(is(emptyOrNullString()))), - hasJsonPath("$.requestEmail", is(RequestItemBuilder.REQ_EMAIL)), - hasJsonPath("$.requestMessage", is(RequestItemBuilder.REQ_MESSAGE)), - hasJsonPath("$.requestName", is(RequestItemBuilder.REQ_NAME)), - hasJsonPath("$.allfiles", is(false)), - // TODO should be an ISO datetime - hasJsonPath("$.requestDate", not(is(emptyOrNullString()))), - hasJsonPath("$._links.self.href", not(is(emptyOrNullString()))) - ))) - .andDo((var result) -> requestTokenRef.set( - read(result.getResponse().getContentAsString(), "token"))); - } finally { - // Clean up the created request. - RequestItemBuilder.deleteRequestItem(requestTokenRef.get()); - } + getClient().perform(post(URI_ROOT) + .content(mapper.writeValueAsBytes(rir)) + .contentType(contentType)) + .andExpect(status().isCreated()); + } /** From 567e989202aeb55393892ca402ab26b85324f6da Mon Sep 17 00:00:00 2001 From: aroman-arvo Date: Wed, 17 May 2023 19:11:31 +0200 Subject: [PATCH 179/510] 8636 Request a Copy - integration test increased --- .../app/rest/RequestItemRepositoryIT.java | 68 ++++++++++++++++--- 1 file changed, 57 insertions(+), 11 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RequestItemRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RequestItemRepositoryIT.java index 2000b6e47e61..937d35c091ea 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RequestItemRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RequestItemRepositoryIT.java @@ -13,6 +13,7 @@ import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; @@ -31,6 +32,7 @@ import java.sql.SQLException; import java.time.temporal.ChronoUnit; import java.util.Date; +import java.util.Iterator; import java.util.Map; import java.util.UUID; import java.util.concurrent.atomic.AtomicReference; @@ -218,12 +220,34 @@ public void testCreateAndReturnAuthenticated() // Create it and see if it was created correctly. ObjectMapper mapper = new ObjectMapper(); String authToken = getAuthToken(eperson.getEmail(), password); - getClient(authToken) - .perform(post(URI_ROOT) - .content(mapper.writeValueAsBytes(rir)) - .contentType(contentType)) - .andExpect(status().isCreated()); - } + try { + getClient(authToken) + .perform(post(URI_ROOT) + .content(mapper.writeValueAsBytes(rir)) + .contentType(contentType)) + .andExpect(status().isCreated()) + // verify the body is empty + .andExpect(jsonPath("$").doesNotExist()); + } finally { + Iterator itemRequests = requestItemService.findByItem(context, item); + String token = null; + for (Iterator it = itemRequests; it.hasNext();) { + RequestItem requestItem = it.next(); + // Find the created request via the eperson email + if (requestItem.getReqEmail().equals(eperson.getEmail())) { + // Verify request data + assertEquals(eperson.getFullName(), requestItem.getReqName()); + assertEquals(item.getID(), requestItem.getItem().getID()); + assertEquals(RequestItemBuilder.REQ_MESSAGE, requestItem.getReqMessage()); + assertEquals(true, requestItem.isAllfiles()); + assertNotNull(requestItem.getToken()); + token = requestItem.getToken(); + } + } + // Cleanup created request + RequestItemBuilder.deleteRequestItem(token); + } +} /** * Test of createAndReturn method, with an UNauthenticated user. @@ -249,11 +273,33 @@ public void testCreateAndReturnNotAuthenticated() // Create it and see if it was created correctly. ObjectMapper mapper = new ObjectMapper(); - getClient().perform(post(URI_ROOT) - .content(mapper.writeValueAsBytes(rir)) - .contentType(contentType)) - .andExpect(status().isCreated()); - + try{ + getClient().perform(post(URI_ROOT) + .content(mapper.writeValueAsBytes(rir)) + .contentType(contentType)) + .andExpect(status().isCreated()) + // verify the body is empty + .andExpect(jsonPath("$").doesNotExist()); + }finally{ + Iterator itemRequests = requestItemService.findByItem(context, item); + String token = null; + for (Iterator it = itemRequests; it.hasNext();) { + RequestItem requestItem = it.next(); + // Find the created request via the eperson email + if (requestItem.getReqEmail().equals(RequestItemBuilder.REQ_EMAIL)) { + // Verify request data + assertEquals(item.getID(), requestItem.getItem().getID()); + assertEquals(RequestItemBuilder.REQ_MESSAGE, requestItem.getReqMessage()); + assertEquals(RequestItemBuilder.REQ_NAME, requestItem.getReqName()); + assertEquals(bitstream.getID(), requestItem.getBitstream().getID()); + assertEquals(false, requestItem.isAllfiles()); + assertNotNull(requestItem.getToken()); + token = requestItem.getToken(); + } + } + // Cleanup created request + RequestItemBuilder.deleteRequestItem(token); + } } /** From 9784c6f50dc984f6769f3774066bcd4be5eba53c Mon Sep 17 00:00:00 2001 From: aroman-arvo Date: Wed, 17 May 2023 19:26:50 +0200 Subject: [PATCH 180/510] 8636 - checkstyle --- .../java/org/dspace/app/rest/RequestItemRepositoryIT.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RequestItemRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RequestItemRepositoryIT.java index 937d35c091ea..2fb7dbbc969d 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RequestItemRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RequestItemRepositoryIT.java @@ -273,14 +273,14 @@ public void testCreateAndReturnNotAuthenticated() // Create it and see if it was created correctly. ObjectMapper mapper = new ObjectMapper(); - try{ + try { getClient().perform(post(URI_ROOT) .content(mapper.writeValueAsBytes(rir)) .contentType(contentType)) .andExpect(status().isCreated()) // verify the body is empty .andExpect(jsonPath("$").doesNotExist()); - }finally{ + } finally { Iterator itemRequests = requestItemService.findByItem(context, item); String token = null; for (Iterator it = itemRequests; it.hasNext();) { From 564f9fdcf6d18514cb416773faf9596d63839e2a Mon Sep 17 00:00:00 2001 From: Alan Orth Date: Fri, 19 May 2023 16:43:05 +0300 Subject: [PATCH 181/510] pom.xml: bump a handful of dependencies All minor and patch versions with no breaking changes: - pdfbox 2.0.28 - See: https://issues.apache.org/jira/secure/ReleaseNote.jspa?projectId=12310760&version=12352284 - log4j 2.20.0 - See: https://logging.apache.org/log4j/2.x/release-notes/2.20.0.html - rome 1.19.0 - See: https://github.com/rometools/rome/releases/tag/1.19.0 - slf4j 1.7.36 - https://www.slf4j.org/news.html - solr-client 8.11.2 - https://lucene.apache.org/core/8_11_2/changes/Changes.html - hibernate 5.6.15.Final - See: https://hibernate.org/orm/releases/5.6/#whats-new - hibernate-validator 6.2.5.Final - See: https://hibernate.org/validator/documentation/migration-guide/#6-2-x - postgresql JDBC driver 42.6.0 --- pom.xml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index 7e09188027db..828854c0cab1 100644 --- a/pom.xml +++ b/pom.xml @@ -22,10 +22,10 @@ 5.3.27 2.7.11 5.7.8 - 5.6.5.Final - 6.0.23.Final - 42.4.3 - 8.11.1 + 5.6.15.Final + 6.2.5.Final + 42.6.0 + 8.11.2 3.4.0 2.10.0 @@ -38,10 +38,10 @@ 1.1.0 9.4.51.v20230217 - 2.17.1 - 2.0.27 - 1.18.0 - 1.7.25 + 2.20.0 + 2.0.28 + 1.19.0 + 1.7.36 2.3.0 1.70 From d7d7f7c37034f6c571c1a61d5ba9afdc71d91d0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paulo=20Gra=C3=A7a?= Date: Fri, 19 May 2023 22:44:07 +0100 Subject: [PATCH 182/510] support for entity type for collection at input submissions --- .../app/util/SubmissionConfigReader.java | 26 ++++++++++++++++--- .../dspace/content/CollectionServiceImpl.java | 22 ++++++++++++++++ .../content/service/CollectionService.java | 15 +++++++++++ dspace/config/item-submission.dtd | 3 ++- dspace/config/item-submission.xml | 20 ++++++++++++++ 5 files changed, 82 insertions(+), 4 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/util/SubmissionConfigReader.java b/dspace-api/src/main/java/org/dspace/app/util/SubmissionConfigReader.java index 21208483583e..7132c1e934e5 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/SubmissionConfigReader.java +++ b/dspace-api/src/main/java/org/dspace/app/util/SubmissionConfigReader.java @@ -22,6 +22,8 @@ import org.apache.logging.log4j.Logger; import org.dspace.content.Collection; import org.dspace.content.DSpaceObject; +import org.dspace.content.factory.ContentServiceFactory; +import org.dspace.content.service.CollectionService; import org.dspace.core.Context; import org.dspace.handle.factory.HandleServiceFactory; import org.dspace.services.factory.DSpaceServicesFactory; @@ -104,6 +106,9 @@ public class SubmissionConfigReader { * always reload from scratch) */ private SubmissionConfig lastSubmissionConfig = null; + + protected static final CollectionService collectionService + = ContentServiceFactory.getInstance().getCollectionService(); /** * Load Submission Configuration from the @@ -335,17 +340,22 @@ private void doNodes(Node n) throws SAXException, SubmissionConfigReaderExceptio * by the collection handle. */ private void processMap(Node e) throws SAXException { + // create a context + Context context = new Context(); + NodeList nl = e.getChildNodes(); int len = nl.getLength(); for (int i = 0; i < len; i++) { Node nd = nl.item(i); if (nd.getNodeName().equals("name-map")) { String id = getAttribute(nd, "collection-handle"); + String entityType = getAttribute(nd, "collection-entity-type"); String value = getAttribute(nd, "submission-name"); String content = getValue(nd); - if (id == null) { + if (id == null && entityType == null) { throw new SAXException( - "name-map element is missing collection-handle attribute in 'item-submission.xml'"); + "name-map element is missing collection-handle or collection-entity-type attribute " + + "in 'item-submission.xml'"); } if (value == null) { throw new SAXException( @@ -355,7 +365,17 @@ private void processMap(Node e) throws SAXException { throw new SAXException( "name-map element has content in 'item-submission.xml', it should be empty."); } - collectionToSubmissionConfig.put(id, value); + if (id != null) { + collectionToSubmissionConfig.put(id, value); + + } else { + // get all collections for this entity-type + List collections = collectionService.findAllCollectionsByEntityType( context, + entityType); + for (Collection collection : collections) { + collectionToSubmissionConfig.putIfAbsent(collection.getHandle(), value); + } + } } // ignore any child node that isn't a "name-map" } } 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 ddfd38694f23..ef89009ebfb2 100644 --- a/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java @@ -18,6 +18,7 @@ import java.util.Map; import java.util.MissingResourceException; import java.util.Objects; +import java.util.Optional; import java.util.Set; import java.util.UUID; @@ -1047,4 +1048,25 @@ public int countCollectionsWithSubmit(String q, Context context, Community commu return (int) resp.getTotalSearchResults(); } + @Override + @SuppressWarnings("rawtypes") + public List findAllCollectionsByEntityType(Context context, String entityType) + throws SearchServiceException { + List collectionList = new ArrayList<>(); + + DiscoverQuery discoverQuery = new DiscoverQuery(); + discoverQuery.setMaxResults(0); + discoverQuery.setDSpaceObjectFilter(IndexableCollection.TYPE); + discoverQuery.addFilterQueries("dspace.entity.type:" + entityType); + + DiscoverResult discoverResult = searchService.search(context, discoverQuery); + List solrIndexableObjects = discoverResult.getIndexableObjects(); + + for (IndexableObject solrCollection : solrIndexableObjects) { + Collection c = ((IndexableCollection) solrCollection).getIndexedObject(); + collectionList.add(c); + } + return collectionList; + } + } diff --git a/dspace-api/src/main/java/org/dspace/content/service/CollectionService.java b/dspace-api/src/main/java/org/dspace/content/service/CollectionService.java index a5b2b7d8d891..82d8b24fb706 100644 --- a/dspace-api/src/main/java/org/dspace/content/service/CollectionService.java +++ b/dspace-api/src/main/java/org/dspace/content/service/CollectionService.java @@ -455,4 +455,19 @@ public int countCollectionsWithSubmit(String q, Context context, Community commu public int countCollectionsWithSubmit(String q, Context context, Community community, String entityType) throws SQLException, SearchServiceException; + + /** + * Returns a list of all collections for a specific entity type. + * NOTE: for better performance, this method retrieves its results from an index (cache) + * and does not query the database directly. + * This means that results may be stale or outdated until + * https://github.com/DSpace/DSpace/issues/2853 is resolved." + * + * @param context DSpace Context + * @param entityType limit the returned collection to those related to given entity type + * @return list of collections found + * @throws SearchServiceException if search error + */ + public List findAllCollectionsByEntityType(Context context, String entityType) + throws SearchServiceException; } diff --git a/dspace/config/item-submission.dtd b/dspace/config/item-submission.dtd index 6490dac62c19..dd1afa0dd02d 100644 --- a/dspace/config/item-submission.dtd +++ b/dspace/config/item-submission.dtd @@ -11,7 +11,8 @@ diff --git a/dspace/config/item-submission.xml b/dspace/config/item-submission.xml index 2ab26dcf57df..f937a5fd9a78 100644 --- a/dspace/config/item-submission.xml +++ b/dspace/config/item-submission.xml @@ -47,6 +47,26 @@ --> + + + From 0409373b617a4f3c0b8e76c50133b14c6e016718 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paulo=20Gra=C3=A7a?= Date: Sat, 20 May 2023 08:07:05 +0100 Subject: [PATCH 183/510] handling exceptions --- .../java/org/dspace/app/util/SubmissionConfigReader.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/util/SubmissionConfigReader.java b/dspace-api/src/main/java/org/dspace/app/util/SubmissionConfigReader.java index 7132c1e934e5..82ebbd0d0d25 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 @@ -25,6 +25,7 @@ import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.CollectionService; import org.dspace.core.Context; +import org.dspace.discovery.SearchServiceException; import org.dspace.handle.factory.HandleServiceFactory; import org.dspace.services.factory.DSpaceServicesFactory; import org.w3c.dom.Document; @@ -157,6 +158,9 @@ private void buildInputs(String fileName) throws SubmissionConfigReaderException } catch (FactoryConfigurationError fe) { throw new SubmissionConfigReaderException( "Cannot create Item Submission Configuration parser", fe); + } catch (SearchServiceException se) { + throw new SubmissionConfigReaderException( + "Cannot perform a discovery search for Item Submission Configuration", se); } catch (Exception e) { throw new SubmissionConfigReaderException( "Error creating Item Submission Configuration: " + e); @@ -292,7 +296,7 @@ public SubmissionStepConfig getStepConfig(String stepID) * should correspond to the collection-form maps, the form definitions, and * the display/storage word pairs. */ - private void doNodes(Node n) throws SAXException, SubmissionConfigReaderException { + private void doNodes(Node n) throws SAXException, SearchServiceException, SubmissionConfigReaderException { if (n == null) { return; } @@ -339,7 +343,7 @@ private void doNodes(Node n) throws SAXException, SubmissionConfigReaderExceptio * the collection handle and item submission name, put name in hashmap keyed * by the collection handle. */ - private void processMap(Node e) throws SAXException { + private void processMap(Node e) throws SAXException, SearchServiceException { // create a context Context context = new Context(); From 687b6216dfabb5e7d4069c30e7aeb2cecc73b602 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paulo=20Gra=C3=A7a?= Date: Sat, 20 May 2023 10:41:51 +0100 Subject: [PATCH 184/510] checkstyle violations fixing --- .../java/org/dspace/app/util/SubmissionConfigReader.java | 6 +++++- .../main/java/org/dspace/content/CollectionServiceImpl.java | 1 - .../java/org/dspace/content/service/CollectionService.java | 1 - 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/util/SubmissionConfigReader.java b/dspace-api/src/main/java/org/dspace/app/util/SubmissionConfigReader.java index 82ebbd0d0d25..91be9a08e605 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 @@ -107,7 +107,11 @@ public class SubmissionConfigReader { * always reload from scratch) */ private SubmissionConfig lastSubmissionConfig = null; - + + /** + * Collection Service instance, needed to interact with collection's + * stored data + */ protected static final CollectionService collectionService = ContentServiceFactory.getInstance().getCollectionService(); 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 ef89009ebfb2..2166a947386d 100644 --- a/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java @@ -18,7 +18,6 @@ import java.util.Map; import java.util.MissingResourceException; import java.util.Objects; -import java.util.Optional; import java.util.Set; import java.util.UUID; diff --git a/dspace-api/src/main/java/org/dspace/content/service/CollectionService.java b/dspace-api/src/main/java/org/dspace/content/service/CollectionService.java index 82d8b24fb706..9ded79fadad0 100644 --- a/dspace-api/src/main/java/org/dspace/content/service/CollectionService.java +++ b/dspace-api/src/main/java/org/dspace/content/service/CollectionService.java @@ -455,7 +455,6 @@ public int countCollectionsWithSubmit(String q, Context context, Community commu public int countCollectionsWithSubmit(String q, Context context, Community community, String entityType) throws SQLException, SearchServiceException; - /** * Returns a list of all collections for a specific entity type. * NOTE: for better performance, this method retrieves its results from an index (cache) From 6fa9e74d9006b260a7ca5edc40d734219b487682 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paulo=20Gra=C3=A7a?= Date: Sat, 20 May 2023 11:35:27 +0100 Subject: [PATCH 185/510] checkstyle violations fixing --- .../main/java/org/dspace/app/util/SubmissionConfigReader.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/util/SubmissionConfigReader.java b/dspace-api/src/main/java/org/dspace/app/util/SubmissionConfigReader.java index 91be9a08e605..d394c60e41f6 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 @@ -109,7 +109,7 @@ public class SubmissionConfigReader { private SubmissionConfig lastSubmissionConfig = null; /** - * Collection Service instance, needed to interact with collection's + * Collection Service instance, needed to interact with collection's * stored data */ protected static final CollectionService collectionService @@ -381,7 +381,7 @@ private void processMap(Node e) throws SAXException, SearchServiceException { List collections = collectionService.findAllCollectionsByEntityType( context, entityType); for (Collection collection : collections) { - collectionToSubmissionConfig.putIfAbsent(collection.getHandle(), value); + collectionToSubmissionConfig.putIfAbsent(collection.getHandle(), value); } } } // ignore any child node that isn't a "name-map" From 2ef268380fedec1aaba1acff291ab04e425eab84 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Sat, 20 May 2023 12:56:38 +1200 Subject: [PATCH 186/510] Unlink DOI from item on deletion even if no provider is configured --- .../java/org/dspace/content/ItemServiceImpl.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java index a290cb0d995f..f86b6690ad29 100644 --- a/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java @@ -64,7 +64,9 @@ import org.dspace.event.Event; import org.dspace.harvest.HarvestedItem; import org.dspace.harvest.service.HarvestedItemService; +import org.dspace.identifier.DOI; import org.dspace.identifier.IdentifierException; +import org.dspace.identifier.service.DOIService; import org.dspace.identifier.service.IdentifierService; import org.dspace.orcid.OrcidHistory; import org.dspace.orcid.OrcidQueue; @@ -123,6 +125,8 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl implements It @Autowired(required = true) protected IdentifierService identifierService; @Autowired(required = true) + protected DOIService doiService; + @Autowired(required = true) protected VersioningService versioningService; @Autowired(required = true) protected HarvestedItemService harvestedItemService; @@ -786,6 +790,16 @@ protected void rawDelete(Context context, Item item) throws AuthorizeException, // Remove any Handle handleService.unbindHandle(context, item); + // Delete a DOI if linked to the item. + // If no DOI consumer or provider is configured, but a DOI remains linked to this item's uuid, + // hibernate will throw a foreign constraint exception. + // Here we use the DOI service directly as it is able to manage DOIs even without any configured + // consumer or provider. + DOI doi = doiService.findDOIByDSpaceObject(context, item); + if (doi != null) { + doi.setDSpaceObject(null); + } + // remove version attached to the item removeVersion(context, item); From 208cac08d561de5992812fe2aaaf92929fedd4b4 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Sun, 21 May 2023 15:42:56 +1200 Subject: [PATCH 187/510] modifying unit tests as per CI feedback, stubbings now unnecessary --- .../src/test/java/org/dspace/content/CollectionTest.java | 3 --- dspace-api/src/test/java/org/dspace/content/ItemTest.java | 2 -- 2 files changed, 5 deletions(-) diff --git a/dspace-api/src/test/java/org/dspace/content/CollectionTest.java b/dspace-api/src/test/java/org/dspace/content/CollectionTest.java index 1548ebcae0d8..13d037abf823 100644 --- a/dspace-api/src/test/java/org/dspace/content/CollectionTest.java +++ b/dspace-api/src/test/java/org/dspace/content/CollectionTest.java @@ -725,9 +725,6 @@ public void testRemoveItemAuth() throws Exception { // Allow Item REMOVE perms doNothing().when(authorizeServiceSpy) .authorizeAction(any(Context.class), any(Item.class), eq(Constants.REMOVE)); - // Allow Item WRITE perms (Needed to remove identifiers, e.g. DOI, before Item deletion) - doNothing().when(authorizeServiceSpy) - .authorizeAction(any(Context.class), any(Item.class), eq(Constants.WRITE)); // create & add item first context.turnOffAuthorisationSystem(); diff --git a/dspace-api/src/test/java/org/dspace/content/ItemTest.java b/dspace-api/src/test/java/org/dspace/content/ItemTest.java index 15e425e23a2a..bae6ce9e1d90 100644 --- a/dspace-api/src/test/java/org/dspace/content/ItemTest.java +++ b/dspace-api/src/test/java/org/dspace/content/ItemTest.java @@ -1189,8 +1189,6 @@ public void testDeleteAuth() throws Exception { doNothing().when(authorizeServiceSpy).authorizeAction(context, item, Constants.REMOVE, true); // Allow Item DELETE perms doNothing().when(authorizeServiceSpy).authorizeAction(context, item, Constants.DELETE); - // Allow Item WRITE perms (required to first delete identifiers) - doNothing().when(authorizeServiceSpy).authorizeAction(context, item, Constants.WRITE); UUID id = item.getID(); itemService.delete(context, item); From 2b3af3a126ae9b5f523660cb544d7cd0a6192f9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paulo=20Gra=C3=A7a?= Date: Mon, 22 May 2023 08:42:33 +0100 Subject: [PATCH 188/510] checkstyle violations fixing --- .../main/java/org/dspace/app/util/SubmissionConfigReader.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 d394c60e41f6..9ed539ee4f48 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 @@ -663,4 +663,4 @@ public List getCollectionsBySubmissionConfig(Context context, String } return results; } -} +} \ No newline at end of file From 6018a2b7be85c3593e30c66bfedf1a95d9363439 Mon Sep 17 00:00:00 2001 From: Alan Orth Date: Mon, 22 May 2023 11:50:59 +0300 Subject: [PATCH 189/510] pom.xml: bump tika from 2.3.0 to 2.5.0 A handful of bug fixes, improvements to PDF parsing, and updates to dependencies. This is the highest we can go right now without hitting dependency convergence issues related to bouncycastle. See: https://github.com/apache/tika/blob/2.5.0/CHANGES.txt --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 828854c0cab1..ad368e318072 100644 --- a/pom.xml +++ b/pom.xml @@ -42,7 +42,7 @@ 2.0.28 1.19.0 1.7.36 - 2.3.0 + 2.5.0 1.70 From 8ddf4702af8a13f0527ede69109d2de993463c72 Mon Sep 17 00:00:00 2001 From: Francesco Pio Scognamiglio Date: Mon, 22 May 2023 11:09:43 +0200 Subject: [PATCH 190/510] [DURACOM-127] check if versioning is enabled for versioned identifier providers --- .../VersionedDOIIdentifierProvider.java | 16 +++++++++++++++- .../VersionedHandleIdentifierProvider.java | 16 +++++++++++++++- ...eIdentifierProviderWithCanonicalHandles.java | 17 ++++++++++++++++- 3 files changed, 46 insertions(+), 3 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/identifier/VersionedDOIIdentifierProvider.java b/dspace-api/src/main/java/org/dspace/identifier/VersionedDOIIdentifierProvider.java index 4ca186eaab49..e5a90907c7b6 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/VersionedDOIIdentifierProvider.java +++ b/dspace-api/src/main/java/org/dspace/identifier/VersionedDOIIdentifierProvider.java @@ -27,13 +27,14 @@ import org.dspace.versioning.VersionHistory; import org.dspace.versioning.service.VersionHistoryService; import org.dspace.versioning.service.VersioningService; +import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; /** * @author Marsa Haoua * @author Pascal-Nicolas Becker (dspace at pascal dash becker dot de) */ -public class VersionedDOIIdentifierProvider extends DOIIdentifierProvider { +public class VersionedDOIIdentifierProvider extends DOIIdentifierProvider implements InitializingBean { /** * log4j category */ @@ -49,6 +50,19 @@ public class VersionedDOIIdentifierProvider extends DOIIdentifierProvider { @Autowired(required = true) protected VersionHistoryService versionHistoryService; + /** + * After all the properties are set check that the versioning is enabled + * + * @throws Exception throws an exception if this isn't the case + */ + @Override + public void afterPropertiesSet() throws Exception { + if (!configurationService.getBooleanProperty("versioning.enabled", true)) { + throw new RuntimeException("the " + VersionedDOIIdentifierProvider.class.getName() + + " is enabled, but the versioning is disabled."); + } + } + @Override public String mint(Context context, DSpaceObject dso) throws IdentifierException { return mint(context, dso, this.filter); diff --git a/dspace-api/src/main/java/org/dspace/identifier/VersionedHandleIdentifierProvider.java b/dspace-api/src/main/java/org/dspace/identifier/VersionedHandleIdentifierProvider.java index b29d47f406c2..4f9efd220695 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/VersionedHandleIdentifierProvider.java +++ b/dspace-api/src/main/java/org/dspace/identifier/VersionedHandleIdentifierProvider.java @@ -35,6 +35,7 @@ import org.dspace.versioning.VersionHistory; import org.dspace.versioning.service.VersionHistoryService; import org.dspace.versioning.service.VersioningService; +import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -45,7 +46,7 @@ * @author Pascal-Nicolas Becker (dspace at pascal dash becker dot de) */ @Component -public class VersionedHandleIdentifierProvider extends IdentifierProvider { +public class VersionedHandleIdentifierProvider extends IdentifierProvider implements InitializingBean { /** * log4j category */ @@ -71,6 +72,19 @@ public class VersionedHandleIdentifierProvider extends IdentifierProvider { @Autowired(required = true) protected ContentServiceFactory contentServiceFactory; + /** + * After all the properties are set check that the versioning is enabled + * + * @throws Exception throws an exception if this isn't the case + */ + @Override + public void afterPropertiesSet() throws Exception { + if (!configurationService.getBooleanProperty("versioning.enabled", true)) { + throw new RuntimeException("the " + VersionedHandleIdentifierProvider.class.getName() + + " is enabled, but the versioning is disabled."); + } + } + @Override public boolean supports(Class identifier) { return Handle.class.isAssignableFrom(identifier); 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 0fac326ca1a7..78ad6b7b79bb 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/VersionedHandleIdentifierProviderWithCanonicalHandles.java +++ b/dspace-api/src/main/java/org/dspace/identifier/VersionedHandleIdentifierProviderWithCanonicalHandles.java @@ -30,6 +30,7 @@ import org.dspace.versioning.VersionHistory; import org.dspace.versioning.service.VersionHistoryService; import org.dspace.versioning.service.VersioningService; +import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -39,7 +40,8 @@ * @author Ben Bosman (ben at atmire dot com) */ @Component -public class VersionedHandleIdentifierProviderWithCanonicalHandles extends IdentifierProvider { +public class VersionedHandleIdentifierProviderWithCanonicalHandles extends IdentifierProvider + implements InitializingBean { /** * log4j category */ @@ -65,6 +67,19 @@ public class VersionedHandleIdentifierProviderWithCanonicalHandles extends Ident @Autowired(required = true) private ItemService itemService; + /** + * After all the properties are set check that the versioning is enabled + * + * @throws Exception throws an exception if this isn't the case + */ + @Override + public void afterPropertiesSet() throws Exception { + if (!configurationService.getBooleanProperty("versioning.enabled", true)) { + throw new RuntimeException("the " + VersionedHandleIdentifierProviderWithCanonicalHandles.class.getName() + + " is enabled, but the versioning is disabled."); + } + } + @Override public boolean supports(Class identifier) { return Handle.class.isAssignableFrom(identifier); From fc2589464f7e2471aff52b252c83fb4b6e7eebdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paulo=20Gra=C3=A7a?= Date: Mon, 22 May 2023 12:57:56 +0100 Subject: [PATCH 191/510] checkstyle violations fixing and remove unnecessary max rows limit --- .../main/java/org/dspace/app/util/SubmissionConfigReader.java | 2 +- .../src/main/java/org/dspace/content/CollectionServiceImpl.java | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/util/SubmissionConfigReader.java b/dspace-api/src/main/java/org/dspace/app/util/SubmissionConfigReader.java index 9ed539ee4f48..0f144fd69f46 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 @@ -381,7 +381,7 @@ private void processMap(Node e) throws SAXException, SearchServiceException { List collections = collectionService.findAllCollectionsByEntityType( context, entityType); for (Collection collection : collections) { - collectionToSubmissionConfig.putIfAbsent(collection.getHandle(), value); + collectionToSubmissionConfig.putIfAbsent(collection.getHandle(), value); } } } // ignore any child node that isn't a "name-map" 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 2166a947386d..5b70cc4ec086 100644 --- a/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java @@ -1054,7 +1054,6 @@ public List findAllCollectionsByEntityType(Context context, String e List collectionList = new ArrayList<>(); DiscoverQuery discoverQuery = new DiscoverQuery(); - discoverQuery.setMaxResults(0); discoverQuery.setDSpaceObjectFilter(IndexableCollection.TYPE); discoverQuery.addFilterQueries("dspace.entity.type:" + entityType); From cbfed3e2183dc4fa7d9c0e8fd253b47e28364f79 Mon Sep 17 00:00:00 2001 From: Francesco Pio Scognamiglio Date: Mon, 22 May 2023 15:37:48 +0200 Subject: [PATCH 192/510] [DURACOM-148] add crossref metadata processor to manage the date issued field --- .../CrossRefDateMetadataProcessor.java | 79 +++++++++++++++++++ ...CrossRefImportMetadataSourceServiceIT.java | 4 +- .../spring/api/crossref-integration.xml | 5 +- 3 files changed, 85 insertions(+), 3 deletions(-) create mode 100644 dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefDateMetadataProcessor.java diff --git a/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefDateMetadataProcessor.java b/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefDateMetadataProcessor.java new file mode 100644 index 000000000000..c7d806cdf4cd --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefDateMetadataProcessor.java @@ -0,0 +1,79 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.importer.external.crossref; + +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.dspace.importer.external.metadatamapping.contributor.JsonPathMetadataProcessor; +import org.joda.time.LocalDate; + +/** + * This class is used for CrossRef's Live-Import to extract + * issued attribute. + * Beans are configured in the crossref-integration.xml file. + * + * @author Francesco Pio Scognamiglio (francescopio.scognamiglio at 4science.com) + */ +public class CrossRefDateMetadataProcessor implements JsonPathMetadataProcessor { + + private final static Logger log = LogManager.getLogger(); + + private String pathToArray; + + @Override + public Collection processMetadata(String json) { + JsonNode rootNode = convertStringJsonToJsonNode(json); + Iterator dates = rootNode.at(pathToArray).iterator(); + Collection values = new ArrayList<>(); + while (dates.hasNext()) { + JsonNode date = dates.next(); + LocalDate issuedDate = null; + SimpleDateFormat issuedDateFormat = null; + if (date.has(0) && date.has(1) && date.has(2)) { + issuedDate = new LocalDate( + date.get(0).numberValue().intValue(), + date.get(1).numberValue().intValue(), + date.get(2).numberValue().intValue()); + issuedDateFormat = new SimpleDateFormat("yyyy-MM-dd"); + } else if (date.has(0) && date.has(1)) { + issuedDate = new LocalDate().withYear(date.get(0).numberValue().intValue()) + .withMonthOfYear(date.get(1).numberValue().intValue()); + issuedDateFormat = new SimpleDateFormat("yyyy-MM"); + } else if (date.has(0)) { + issuedDate = new LocalDate().withYear(date.get(0).numberValue().intValue()); + issuedDateFormat = new SimpleDateFormat("yyyy"); + } + values.add(issuedDateFormat.format(issuedDate.toDate())); + } + 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; + } + + public void setPathToArray(String pathToArray) { + this.pathToArray = pathToArray; + } + +} \ No newline at end of file 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 9a0d39225c3d..11fe58ac1d2e 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 @@ -146,7 +146,7 @@ private ArrayList getRecords() { + " Medical College of Prevention of Iodine Deficiency Diseases"); MetadatumDTO author = createMetadatumDTO("dc", "contributor", "author", "L.V. Senyuk"); MetadatumDTO type = createMetadatumDTO("dc", "type", null, "journal-article"); - MetadatumDTO date = createMetadatumDTO("dc", "date", "issued", "2016"); + MetadatumDTO date = createMetadatumDTO("dc", "date", "issued", "2016-05-19"); MetadatumDTO ispartof = createMetadatumDTO("dc", "relation", "ispartof", "Ukraïnsʹkij žurnal medicini, bìologìï ta sportu"); MetadatumDTO doi = createMetadatumDTO("dc", "identifier", "doi", "10.26693/jmbs01.02.184"); @@ -172,7 +172,7 @@ private ArrayList getRecords() { "Ischemic Heart Disease and Role of Nurse of Cardiology Department"); MetadatumDTO author2 = createMetadatumDTO("dc", "contributor", "author", "K. І. Kozak"); MetadatumDTO type2 = createMetadatumDTO("dc", "type", null, "journal-article"); - MetadatumDTO date2 = createMetadatumDTO("dc", "date", "issued", "2016"); + MetadatumDTO date2 = createMetadatumDTO("dc", "date", "issued", "2016-05-19"); MetadatumDTO ispartof2 = createMetadatumDTO("dc", "relation", "ispartof", "Ukraïnsʹkij žurnal medicini, bìologìï ta sportu"); MetadatumDTO doi2 = createMetadatumDTO("dc", "identifier", "doi", "10.26693/jmbs01.02.105"); diff --git a/dspace/config/spring/api/crossref-integration.xml b/dspace/config/spring/api/crossref-integration.xml index e01b613833e4..5d67c17626e2 100644 --- a/dspace/config/spring/api/crossref-integration.xml +++ b/dspace/config/spring/api/crossref-integration.xml @@ -69,8 +69,11 @@ - + + + + From c3dd1f8489a37fc177769ecd50a492397ab72502 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 22 May 2023 09:23:21 -0500 Subject: [PATCH 193/510] Update to latest Spring Boot --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 828854c0cab1..ef6227e6ff2d 100644 --- a/pom.xml +++ b/pom.xml @@ -20,7 +20,7 @@ 11 5.3.27 - 2.7.11 + 2.7.12 5.7.8 5.6.15.Final 6.2.5.Final From 7ab5f3b48925449cae681c05f305dffe86f970e6 Mon Sep 17 00:00:00 2001 From: Alan Orth Date: Wed, 17 May 2023 22:58:15 +0300 Subject: [PATCH 194/510] dspace-api: fix typo in ImageMagickThumbnailFilter --- .../org/dspace/app/mediafilter/ImageMagickThumbnailFilter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/app/mediafilter/ImageMagickThumbnailFilter.java b/dspace-api/src/main/java/org/dspace/app/mediafilter/ImageMagickThumbnailFilter.java index d16243e3e3bc..9fa310120555 100644 --- a/dspace-api/src/main/java/org/dspace/app/mediafilter/ImageMagickThumbnailFilter.java +++ b/dspace-api/src/main/java/org/dspace/app/mediafilter/ImageMagickThumbnailFilter.java @@ -208,7 +208,7 @@ public boolean preProcessBitstream(Context c, Item item, Bitstream source, boole if (description != null) { if (replaceRegex.matcher(description).matches()) { if (verbose) { - System.out.format("%s %s matches pattern and is replacable.%n", + System.out.format("%s %s matches pattern and is replaceable.%n", description, nsrc); } continue; From c37622cb3f094d4f1b472db2a588e096422b0b62 Mon Sep 17 00:00:00 2001 From: Alan Orth Date: Wed, 17 May 2023 23:00:36 +0300 Subject: [PATCH 195/510] dspace-api: improve logging in ImageMagickThumbnailFilter Instead of logging the name of the source bitstream, we should be logging the name of the actual thumbnail bitstream that is being considered for replacement. For example, instead of this: IM Thumbnail manual.pdf matches pattern and is replaceable. ... the message should read: IM Thumbnail manual.pdf.jpg matches pattern and is replaceable. This message is already confusing enough, but this will help. --- .../dspace/app/mediafilter/ImageMagickThumbnailFilter.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/mediafilter/ImageMagickThumbnailFilter.java b/dspace-api/src/main/java/org/dspace/app/mediafilter/ImageMagickThumbnailFilter.java index 9fa310120555..b7c8acc6fdc5 100644 --- a/dspace-api/src/main/java/org/dspace/app/mediafilter/ImageMagickThumbnailFilter.java +++ b/dspace-api/src/main/java/org/dspace/app/mediafilter/ImageMagickThumbnailFilter.java @@ -209,14 +209,14 @@ public boolean preProcessBitstream(Context c, Item item, Bitstream source, boole if (replaceRegex.matcher(description).matches()) { if (verbose) { System.out.format("%s %s matches pattern and is replaceable.%n", - description, nsrc); + description, n); } continue; } if (description.equals(getDescription())) { if (verbose) { System.out.format("%s %s is replaceable.%n", - getDescription(), nsrc); + getDescription(), n); } continue; } From 7dbfa9a3c5e3c85daf2e83d5cbbeb547be93376f Mon Sep 17 00:00:00 2001 From: Alan Orth Date: Wed, 17 May 2023 23:10:29 +0300 Subject: [PATCH 196/510] dspace-api: minor logging fix in ImageMagickThumbnailFilter Minor standardization to logging (unneccessary capitalization and excessive spaces). --- .../org/dspace/app/mediafilter/ImageMagickThumbnailFilter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/app/mediafilter/ImageMagickThumbnailFilter.java b/dspace-api/src/main/java/org/dspace/app/mediafilter/ImageMagickThumbnailFilter.java index b7c8acc6fdc5..383fc50868e5 100644 --- a/dspace-api/src/main/java/org/dspace/app/mediafilter/ImageMagickThumbnailFilter.java +++ b/dspace-api/src/main/java/org/dspace/app/mediafilter/ImageMagickThumbnailFilter.java @@ -221,7 +221,7 @@ public boolean preProcessBitstream(Context c, Item item, Bitstream source, boole continue; } } - System.out.format("Custom Thumbnail exists for %s for item %s. Thumbnail will not be generated.%n", + System.out.format("Custom thumbnail exists for %s for item %s. Thumbnail will not be generated.%n", nsrc, item.getHandle()); return false; } From a4105bdcb0a07f4d4b573a36010d39fa4a576552 Mon Sep 17 00:00:00 2001 From: Alan Orth Date: Wed, 17 May 2023 23:20:34 +0300 Subject: [PATCH 197/510] dspace-api: simplify logic in ImageMagickThumbnailFilter There is no point passing a page parameter here, with a default of 0 no less, because we will *always* use the first page of the PDF to generate the thumbnail. No other filters use this function and the page parameter is not configurable so we should just hard code it. --- .../dspace/app/mediafilter/ImageMagickPdfThumbnailFilter.java | 2 +- .../dspace/app/mediafilter/ImageMagickThumbnailFilter.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/mediafilter/ImageMagickPdfThumbnailFilter.java b/dspace-api/src/main/java/org/dspace/app/mediafilter/ImageMagickPdfThumbnailFilter.java index 467303c3cafd..e03e770f7665 100644 --- a/dspace-api/src/main/java/org/dspace/app/mediafilter/ImageMagickPdfThumbnailFilter.java +++ b/dspace-api/src/main/java/org/dspace/app/mediafilter/ImageMagickPdfThumbnailFilter.java @@ -22,7 +22,7 @@ public InputStream getDestinationStream(Item currentItem, InputStream source, bo File f2 = null; File f3 = null; try { - f2 = getImageFile(f, 0, verbose); + f2 = getImageFile(f, verbose); f3 = getThumbnailFile(f2, verbose); byte[] bytes = Files.readAllBytes(f3.toPath()); return new ByteArrayInputStream(bytes); diff --git a/dspace-api/src/main/java/org/dspace/app/mediafilter/ImageMagickThumbnailFilter.java b/dspace-api/src/main/java/org/dspace/app/mediafilter/ImageMagickThumbnailFilter.java index 383fc50868e5..d156fdbc1a2a 100644 --- a/dspace-api/src/main/java/org/dspace/app/mediafilter/ImageMagickThumbnailFilter.java +++ b/dspace-api/src/main/java/org/dspace/app/mediafilter/ImageMagickThumbnailFilter.java @@ -116,7 +116,7 @@ public File getThumbnailFile(File f, boolean verbose) return f2; } - public File getImageFile(File f, int page, boolean verbose) + public File getImageFile(File f, boolean verbose) throws IOException, InterruptedException, IM4JavaException { File f2 = new File(f.getParentFile(), f.getName() + ".jpg"); f2.deleteOnExit(); @@ -155,7 +155,7 @@ public File getImageFile(File f, int page, boolean verbose) op.define("pdf:use-cropbox=true"); } - String s = "[" + page + "]"; + String s = "[0]"; op.addImage(f.getAbsolutePath() + s); if (configurationService.getBooleanProperty(PRE + ".flatten", true)) { op.flatten(); From 7ebb5290fc6a1c65b7ffc3f653e43932d8b79f02 Mon Sep 17 00:00:00 2001 From: Alan Orth Date: Thu, 18 May 2023 09:47:06 +0300 Subject: [PATCH 198/510] dspace-api: comment ImageMagick filters Add some comments to document the functionality of the ImageMagick thumbnail filters. This will help others understand it later when we need to re-factor them. --- .../app/mediafilter/ImageMagickPdfThumbnailFilter.java | 2 ++ .../dspace/app/mediafilter/ImageMagickThumbnailFilter.java | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/dspace-api/src/main/java/org/dspace/app/mediafilter/ImageMagickPdfThumbnailFilter.java b/dspace-api/src/main/java/org/dspace/app/mediafilter/ImageMagickPdfThumbnailFilter.java index e03e770f7665..afe1bb3d75df 100644 --- a/dspace-api/src/main/java/org/dspace/app/mediafilter/ImageMagickPdfThumbnailFilter.java +++ b/dspace-api/src/main/java/org/dspace/app/mediafilter/ImageMagickPdfThumbnailFilter.java @@ -22,7 +22,9 @@ public InputStream getDestinationStream(Item currentItem, InputStream source, bo File f2 = null; File f3 = null; try { + // Step 1: get an image from our PDF file, with PDF-specific processing options f2 = getImageFile(f, verbose); + // Step 2: use the image above to create the final resized and rotated thumbnail f3 = getThumbnailFile(f2, verbose); byte[] bytes = Files.readAllBytes(f3.toPath()); return new ByteArrayInputStream(bytes); diff --git a/dspace-api/src/main/java/org/dspace/app/mediafilter/ImageMagickThumbnailFilter.java b/dspace-api/src/main/java/org/dspace/app/mediafilter/ImageMagickThumbnailFilter.java index d156fdbc1a2a..450882ed1717 100644 --- a/dspace-api/src/main/java/org/dspace/app/mediafilter/ImageMagickThumbnailFilter.java +++ b/dspace-api/src/main/java/org/dspace/app/mediafilter/ImageMagickThumbnailFilter.java @@ -116,6 +116,13 @@ public File getThumbnailFile(File f, boolean verbose) return f2; } + /** + * Return an image from a bitstream with specific processing options for + * PDFs. This is only used by ImageMagickPdfThumbnailFilter in order to + * generate an intermediate image file for use with getThumbnailFile. It + * is unfortunate that this means we convert from PDF to JPEG to JPEG, + * which incurs generation loss. + */ public File getImageFile(File f, boolean verbose) throws IOException, InterruptedException, IM4JavaException { File f2 = new File(f.getParentFile(), f.getName() + ".jpg"); From 5357923d281d796cb72733ff9fa3244b3ef55490 Mon Sep 17 00:00:00 2001 From: Alan Orth Date: Thu, 18 May 2023 14:18:00 +0300 Subject: [PATCH 199/510] dspace-api: avoid generation loss in ImageMagickThumbnailFilter When filtering PDF bitstreams, the ImageMagickThumbnailFilter first creates an intermediate JPEG and then a "thumbnail" JPEG. These two operations are both lossy. The ImageMagick usage guide warns against doing that: > JPEG losses (sic) information, degrading images when saved. > Use some other format for intermediate images during processing. > Only use JPEG format, for the final image, not for further processing. As our current filter architecture requires writing a temporary file we must choose one of the following lossless formats to use for the intermediate: PNG, TIFF, or MIFF. MIFF is ImageMagick's own internal format and is much faster to write than PNG. By eliminating the first lossy conversion we gain 1.1% points on the ssimulacra2 (v2.1) scoring scale of visual quality. See: https://imagemagick.org/Usage/formats/#jpg --- .../app/mediafilter/ImageMagickThumbnailFilter.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/mediafilter/ImageMagickThumbnailFilter.java b/dspace-api/src/main/java/org/dspace/app/mediafilter/ImageMagickThumbnailFilter.java index 450882ed1717..408982d157e5 100644 --- a/dspace-api/src/main/java/org/dspace/app/mediafilter/ImageMagickThumbnailFilter.java +++ b/dspace-api/src/main/java/org/dspace/app/mediafilter/ImageMagickThumbnailFilter.java @@ -119,13 +119,14 @@ public File getThumbnailFile(File f, boolean verbose) /** * Return an image from a bitstream with specific processing options for * PDFs. This is only used by ImageMagickPdfThumbnailFilter in order to - * generate an intermediate image file for use with getThumbnailFile. It - * is unfortunate that this means we convert from PDF to JPEG to JPEG, - * which incurs generation loss. + * generate an intermediate image file for use with getThumbnailFile. */ public File getImageFile(File f, boolean verbose) throws IOException, InterruptedException, IM4JavaException { - File f2 = new File(f.getParentFile(), f.getName() + ".jpg"); + // Writing an intermediate file to disk is inefficient, but since we're + // doing it anyway, we should use a lossless format. IM's internal MIFF + // is lossless like PNG and TIFF, but much faster. + File f2 = new File(f.getParentFile(), f.getName() + ".miff"); f2.deleteOnExit(); ConvertCmd cmd = new ConvertCmd(); IMOperation op = new IMOperation(); From 05f1714b0566f2091590c1981e4a128db03ab428 Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Mon, 10 Apr 2023 11:36:47 +0200 Subject: [PATCH 200/510] Replace occurrences of DSpace with the dspace.name variable --- dspace/config/dspace.cfg | 1 + dspace/config/emails/change_password | 12 ++++-------- dspace/config/emails/doi_maintenance_error | 4 +++- dspace/config/emails/export_error | 7 ++----- dspace/config/emails/export_success | 6 ++---- dspace/config/emails/feedback | 3 ++- dspace/config/emails/flowtask_notify | 4 ++-- dspace/config/emails/harvesting_error | 4 +++- dspace/config/emails/internal_error | 2 +- dspace/config/emails/register | 10 +++------- dspace/config/emails/registration_notify | 4 +++- dspace/config/emails/request_item.admin | 8 +++++--- dspace/config/emails/request_item.author | 8 +++++--- dspace/config/emails/submit_archive | 6 +++--- dspace/config/emails/submit_reject | 7 +++---- dspace/config/emails/submit_task | 6 +++--- dspace/config/emails/subscription | 4 ++-- dspace/config/emails/welcome | 5 ++--- 18 files changed, 49 insertions(+), 52 deletions(-) diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 8e532310c11b..11c53cb6a7a9 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -38,6 +38,7 @@ dspace.ui.url = http://localhost:4000 # Name of the site dspace.name = DSpace at My University +dspace.shortname = DSpace # Assetstore configurations have moved to config/modules/assetstore.cfg # and config/spring/api/bitstore.xml. diff --git a/dspace/config/emails/change_password b/dspace/config/emails/change_password index eb114feeeb0c..908e494596cc 100644 --- a/dspace/config/emails/change_password +++ b/dspace/config/emails/change_password @@ -4,19 +4,15 @@ ## ## See org.dspace.core.Email for information on the format of this file. ## -#set($subject = 'Change Password Request') +#set($subject = "${config.get('dspace.name')}: Change Password Request") #set($phone = ${config.get('mail.message.helpdesk.telephone')}) -To change the password for your DSpace account, please click the link -below: +To change the password for your ${config.get('dspace.name')} account, please click the link below: ${params[0]} -If you need assistance with your account, please email - - ${config.get("mail.helpdesk")} +If you need assistance with your account, please email ${config.get("mail.helpdesk")} #if( $phone ) - or call us at ${phone}. #end -The DSpace Team +The ${config.get('dspace.name')} Team diff --git a/dspace/config/emails/doi_maintenance_error b/dspace/config/emails/doi_maintenance_error index 5424432f64ce..a86de915469b 100644 --- a/dspace/config/emails/doi_maintenance_error +++ b/dspace/config/emails/doi_maintenance_error @@ -10,9 +10,11 @@ ## ## See org.dspace.core.Email for information on the format of this file. ## -#set($subject = "DSpace: Error ${params[0]} DOI ${params[3]}") +#set($subject = "${config.get('dspace.name')}: Error ${params[0]} DOI ${params[3]}") Date: ${params[1]} ${params[0]} DOI ${params[4]} for ${params[2]} with ID ${params[3]} failed: ${params[5]} + +The ${config.get('dspace.name')} Team diff --git a/dspace/config/emails/export_error b/dspace/config/emails/export_error index 79468c281e3e..5223f64e3379 100644 --- a/dspace/config/emails/export_error +++ b/dspace/config/emails/export_error @@ -6,14 +6,11 @@ ## ## See org.dspace.core.Email for information on the format of this file. ## -#set($subject = 'DSpace - The item export you requested was not completed.') +#set($subject = "${config.get('dspace.name')}: The item export you requested was not completed.") The item export you requested was not completed, due to the following reason: ${params[0]} For more information you may contact your system administrator: ${params[1]} - - -The DSpace Team - +The ${config.get('dspace.name')} Team diff --git a/dspace/config/emails/export_success b/dspace/config/emails/export_success index b97a3798738e..211e40dd787d 100644 --- a/dspace/config/emails/export_success +++ b/dspace/config/emails/export_success @@ -5,7 +5,7 @@ ## ## See org.dspace.core.Email for information on the format of this file. ## -#set($subject = 'DSpace - Item export requested is ready for download') +#set($subject = "${config.get('dspace.name')}: Item export requested is ready for download") The item export you requested from the repository is now ready for download. You may download the compressed file using the following link: @@ -13,6 +13,4 @@ ${params[0]} This file will remain available for at least ${params[1]} hours. - -The DSpace Team - +The ${config.get('dspace.name')} Team diff --git a/dspace/config/emails/feedback b/dspace/config/emails/feedback index 7998367c264f..5bf83eda760c 100644 --- a/dspace/config/emails/feedback +++ b/dspace/config/emails/feedback @@ -10,7 +10,7 @@ ## ## See org.dspace.core.Email for information on the format of this file. ## -#set($subject = 'Feedback Form Information') +#set($subject = "${config.get('dspace.name')}: Feedback Form Information") Comments: @@ -24,3 +24,4 @@ Referring Page: ${params[3]} User Agent: ${params[4]} Session: ${params[5]} +The ${config.get('dspace.name')} Team diff --git a/dspace/config/emails/flowtask_notify b/dspace/config/emails/flowtask_notify index 7e5286e3074c..f277b7f2e79f 100644 --- a/dspace/config/emails/flowtask_notify +++ b/dspace/config/emails/flowtask_notify @@ -7,7 +7,7 @@ ## {4} Task result ## {5} Workflow action taken ## -#set($subject = 'DSpace: Curation Task Report') +#set($subject = "${config.get('dspace.name')}: Curation Task Report") Title: ${params[0]} Collection: ${params[1]} @@ -20,4 +20,4 @@ ${params[4]} Action taken on the submission: ${params[5]} -DSpace +The ${config.get('dspace.name')} Team diff --git a/dspace/config/emails/harvesting_error b/dspace/config/emails/harvesting_error index d14b51fe8235..40e4fa58e844 100644 --- a/dspace/config/emails/harvesting_error +++ b/dspace/config/emails/harvesting_error @@ -8,7 +8,7 @@ ## ## See org.dspace.core.Email for information on the format of this file. ## -#set($subject = 'DSpace: Harvesting Error') +#set($subject = "${config.get('dspace.name')}: Harvesting Error") Collection ${params[0]} failed on harvest: Date: ${params[1]} @@ -18,3 +18,5 @@ ${params[3]} Exception: ${params[4]} + +The ${config.get('dspace.name')} Team diff --git a/dspace/config/emails/internal_error b/dspace/config/emails/internal_error index ee622f4b3865..266c91b116a1 100644 --- a/dspace/config/emails/internal_error +++ b/dspace/config/emails/internal_error @@ -10,7 +10,7 @@ ## ## See org.dspace.core.Email for information on the format of this file. ## -#set($subject = 'DSpace: Internal Server Error') +#set($subject = "${config.get('dspace.name')}: Internal Server Error") An internal server error occurred on ${params[0]}: Date: ${params[1]} diff --git a/dspace/config/emails/register b/dspace/config/emails/register index 694be449a887..87b005bc99c3 100644 --- a/dspace/config/emails/register +++ b/dspace/config/emails/register @@ -6,17 +6,13 @@ ## #set($subject = "${config.get('dspace.name')} Account Registration") #set($phone = ${config.get('mail.message.helpdesk.telephone')}) -To complete registration for a DSpace account, please click the link -below: +To complete registration for a ${config.get('dspace.name')} account, please click the link below: ${params[0]} -If you need assistance with your account, please email - - ${config.get("mail.helpdesk")} +If you need assistance with your account, please email ${config.get("mail.helpdesk")} #if( $phone ) - or call us at ${phone}. #end -The DSpace Team +The ${config.get('dspace.name')} Team diff --git a/dspace/config/emails/registration_notify b/dspace/config/emails/registration_notify index 96c87fa63d9c..0627d17fe02c 100644 --- a/dspace/config/emails/registration_notify +++ b/dspace/config/emails/registration_notify @@ -8,10 +8,12 @@ ## ## See org.dspace.core.Email for information on the format of this file. ## -#set($subject = 'DSpace: Registration Notification') +#set($subject = "${config.get('dspace.name')}: Registration Notification") A new user has registered on ${params[0]} at ${params[1]}: Name: ${params[2]} Email: ${params[3]} Date: ${params[4]} + +The ${config.get('dspace.name')} Team diff --git a/dspace/config/emails/request_item.admin b/dspace/config/emails/request_item.admin index c0443c60f8dc..ee8daa510d05 100644 --- a/dspace/config/emails/request_item.admin +++ b/dspace/config/emails/request_item.admin @@ -8,11 +8,13 @@ ## {4} the approver's email address ## See org.dspace.core.Email for information on the format of this file. ## -#set($subject = 'Request for Open Access') +#set($subject = "${config.get('dspace.name')}: Request for Open Access") ${params[3]}, with address ${params[4]}, requested the following document/file to be in Open Access: -Document Handle:${params[1]} +Document Handle: ${params[1]} File ID: ${params[0]} -Token:${params[2]} +Token: ${params[2]} + +The ${config.get('dspace.name')} Team diff --git a/dspace/config/emails/request_item.author b/dspace/config/emails/request_item.author index ac79270e7fbc..eb9c4f38f661 100644 --- a/dspace/config/emails/request_item.author +++ b/dspace/config/emails/request_item.author @@ -11,7 +11,7 @@ ## 8 corresponding author email ## 9 configuration property "dspace.name" ## 10 configuration property "mail.helpdesk" -#set($subject = 'Request copy of document') +#set($subject = "${config.get('dspace.name')}: Request copy of document") Dear ${params[7]}, @@ -21,10 +21,12 @@ This request came along with the following message: "${params[5]}" -To answer, click ${params[6]}. Whether you choose to grant or deny the request, we think that it''s in your best interest to respond. +To answer, click ${params[6]}. Whether you choose to grant or deny the request, we think that it's in your best interest to respond. -IF YOU ARE NOT AN AUTHOR OF THIS DOCUMENT, and only submitted the document on the author''s behalf, PLEASE REDIRECT THIS MESSAGE TO THE AUTHOR(S). Only the author(s) should answer the request to send a copy. +IF YOU ARE NOT AN AUTHOR OF THIS DOCUMENT, and only submitted the document on the author's behalf, PLEASE REDIRECT THIS MESSAGE TO THE AUTHOR(S). Only the author(s) should answer the request to send a copy. IF YOU ARE AN AUTHOR OF THE REQUESTED DOCUMENT, thank you for your cooperation! If you have any questions concerning this request, please contact ${params[10]}. + +The ${config.get('dspace.name')} Team diff --git a/dspace/config/emails/submit_archive b/dspace/config/emails/submit_archive index d3d62f7f4d07..ea1c31a75599 100644 --- a/dspace/config/emails/submit_archive +++ b/dspace/config/emails/submit_archive @@ -4,13 +4,13 @@ ## {1} Name of collection ## {2} handle ## -#set($subject = 'DSpace: Submission Approved and Archived') +#set($subject = "${config.get('dspace.name')}: Submission Approved and Archived") You submitted: ${params[0]} To collection: ${params[1]} -Your submission has been accepted and archived in DSpace, +Your submission has been accepted and archived in ${config.get('dspace.name')}, and it has been assigned the following identifier: ${params[2]} @@ -18,4 +18,4 @@ Please use this identifier when citing your submission. Many thanks! -DSpace +The ${config.get('dspace.name')} Team diff --git a/dspace/config/emails/submit_reject b/dspace/config/emails/submit_reject index 44e6cf2cd9f3..f5376cb3a03b 100644 --- a/dspace/config/emails/submit_reject +++ b/dspace/config/emails/submit_reject @@ -6,7 +6,7 @@ ## {3} Reason for the rejection ## {4} Link to 'My DSpace' page ## -#set($subject = 'DSpace: Submission Rejected') +#set($subject = "${config.get('dspace.name')}: Submission Rejected") You submitted: ${params[0]} @@ -17,7 +17,6 @@ with the following explanation: ${params[3]} -Your submission has not been deleted. You can access it from your -"My DSpace" page: ${params[4]} +Your submission has not been deleted. You can access it from your "My${config.get('dspace.shortname')}" page: ${params[4]} -DSpace +The ${config.get('dspace.name')} Team diff --git a/dspace/config/emails/submit_task b/dspace/config/emails/submit_task index 8c8b4a7e7245..f68bac80b186 100644 --- a/dspace/config/emails/submit_task +++ b/dspace/config/emails/submit_task @@ -6,7 +6,7 @@ ## {3} Description of task ## {4} link to 'my DSpace' page ## -#set($subject = 'DSpace: You have a new task') +#set($subject = "${config.get('dspace.name')}: You have a new task") A new item has been submitted: @@ -16,9 +16,9 @@ Submitted by: ${params[2]} ${params[3]} -To claim this task, please visit your "My DSpace" +To claim this task, please visit your "My${config.get('dspace.shortname')}" page: ${params[4]} Many thanks! -DSpace +The ${config.get('dspace.name')} Team diff --git a/dspace/config/emails/subscription b/dspace/config/emails/subscription index 2879e579075d..5141192b572d 100644 --- a/dspace/config/emails/subscription +++ b/dspace/config/emails/subscription @@ -4,9 +4,9 @@ ## Parameters: {0} is the details of the new collections and items ## See org.dspace.core.Email for information on the format of this file. ## -#set($subject = 'DSpace Subscription') +#set($subject = "${config.get('dspace.name')} Subscription") New items are available in the collections you have subscribed to: ${params[0]} -DSpace +The ${config.get('dspace.name')} Team diff --git a/dspace/config/emails/welcome b/dspace/config/emails/welcome index febc082e072e..1c22cb05e830 100644 --- a/dspace/config/emails/welcome +++ b/dspace/config/emails/welcome @@ -3,13 +3,12 @@ ## See org.dspace.core.Email for information on the format of this file. ## #set($subject = "Welcome new registered ${config.get('dspace.name')} user!") -Thank you for registering an account. Your new account can be used immediately +Thank you for registering an account. Your new account can be used immediately to subscribe to notices of new content arriving in collections of your choice. Your new account can also be granted privileges to submit new content, or to edit and/or approve submissions. -If you need assistance with your account, please email -${config.get("mail.admin")}. +If you need assistance with your account, please email ${config.get("mail.helpdesk")}. The ${config.get('dspace.name')} Team From 9bc7edb73382a38c94a765551a0ccd4cb6a08458 Mon Sep 17 00:00:00 2001 From: Bui Thai Hai Date: Thu, 18 May 2023 13:59:12 +0700 Subject: [PATCH 201/510] Fix: Collection's admin cannot edit its template item. --- .../app/rest/ItemTemplateRestController.java | 4 +- ...lateItemRestPermissionEvaluatorPlugin.java | 83 +++++++++++++++++++ 2 files changed, 85 insertions(+), 2 deletions(-) create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/security/TemplateItemRestPermissionEvaluatorPlugin.java diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/ItemTemplateRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/ItemTemplateRestController.java index e297dab44cad..a6dbf3496e49 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/ItemTemplateRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/ItemTemplateRestController.java @@ -120,7 +120,7 @@ public TemplateItemResource getTemplateItem(HttpServletRequest request, @PathVar * @throws SQLException * @throws AuthorizeException */ - @PreAuthorize("hasPermission(#uuid, 'ITEM', 'WRITE')") + @PreAuthorize("hasPermission(#uuid, 'ITEMTEMPLATE', 'WRITE')") @RequestMapping(method = RequestMethod.PATCH) public ResponseEntity> patch(HttpServletRequest request, @PathVariable UUID uuid, @RequestBody(required = true) JsonNode jsonNode) @@ -153,7 +153,7 @@ public ResponseEntity> patch(HttpServletRequest request, * @throws AuthorizeException * @throws IOException */ - @PreAuthorize("hasPermission(#uuid, 'ITEM', 'DELETE')") + @PreAuthorize("hasPermission(#uuid, 'ITEMTEMPLATE', 'DELETE')") @RequestMapping(method = RequestMethod.DELETE) public ResponseEntity> deleteTemplateItem(HttpServletRequest request, @PathVariable UUID uuid) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/TemplateItemRestPermissionEvaluatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/TemplateItemRestPermissionEvaluatorPlugin.java new file mode 100644 index 000000000000..cb977dff3aef --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/TemplateItemRestPermissionEvaluatorPlugin.java @@ -0,0 +1,83 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.security; + +import java.io.Serializable; +import java.sql.SQLException; +import java.util.UUID; + +import org.apache.commons.lang3.StringUtils; +import org.dspace.app.rest.model.TemplateItemRest; +import org.dspace.app.rest.utils.ContextUtil; +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.content.Collection; +import org.dspace.content.service.ItemService; +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; +import org.dspace.services.RequestService; +import org.dspace.services.model.Request; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.Authentication; +import org.springframework.stereotype.Component; + +/** + * {@link RestObjectPermissionEvaluatorPlugin} class that evaluate WRITE and DELETE permission over a TemplateItem + * + * @author Bui Thai Hai (thaihai.bui@dlcorp.com.vn) + */ +@Component +public class TemplateItemRestPermissionEvaluatorPlugin extends RestObjectPermissionEvaluatorPlugin { + + private static final Logger log = LoggerFactory.getLogger(TemplateItemRestPermissionEvaluatorPlugin.class); + + @Autowired + private RequestService requestService; + + @Autowired + ItemService its; + + @Autowired + private AuthorizeService authorizeService; + + @Override + public boolean hasDSpacePermission(Authentication authentication, Serializable targetId, String targetType, + DSpaceRestPermission permission) { + + DSpaceRestPermission restPermission = DSpaceRestPermission.convert(permission); + if (!DSpaceRestPermission.WRITE.equals(restPermission) && + !DSpaceRestPermission.DELETE.equals(restPermission)) { + return false; + } + if (!StringUtils.equalsIgnoreCase(targetType, TemplateItemRest.NAME)) { + return false; + } + + Request request = requestService.getCurrentRequest(); + Context context = ContextUtil.obtainContext(request.getHttpServletRequest()); + + EPerson ePerson = context.getCurrentUser(); + if (ePerson == null) { + return false; + } + // Allow collection's admin to edit/delete the template + + UUID dsoId = UUID.fromString(targetId.toString()); + requestService.getCurrentRequest().getHttpServletRequest().getRequestURL(); + try { + Collection coll = its.find(context, dsoId).getTemplateItemOf(); + if (authorizeService.isAdmin(context, coll)) { + return true; + } + } catch (SQLException e) { + log.error(e.getMessage(), e); + } + return false; + } +} From 8980b07f2b29b00256c3020a40c080d78e5a6450 Mon Sep 17 00:00:00 2001 From: Francesco Pio Scognamiglio Date: Tue, 23 May 2023 08:51:07 +0200 Subject: [PATCH 202/510] [DURACOM-148] fix typo: remove trailing whitespace --- .../external/crossref/CrossRefDateMetadataProcessor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefDateMetadataProcessor.java b/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefDateMetadataProcessor.java index c7d806cdf4cd..dec0b050f396 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefDateMetadataProcessor.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefDateMetadataProcessor.java @@ -24,7 +24,7 @@ * This class is used for CrossRef's Live-Import to extract * issued attribute. * Beans are configured in the crossref-integration.xml file. - * + * * @author Francesco Pio Scognamiglio (francescopio.scognamiglio at 4science.com) */ public class CrossRefDateMetadataProcessor implements JsonPathMetadataProcessor { From 50f808a7d003b0e185e790ca501546481e9c4d60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paulo=20Gra=C3=A7a?= Date: Tue, 23 May 2023 08:51:27 +0100 Subject: [PATCH 203/510] removing Person test configuration --- dspace/config/item-submission.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace/config/item-submission.xml b/dspace/config/item-submission.xml index f937a5fd9a78..2f20e34c6b3c 100644 --- a/dspace/config/item-submission.xml +++ b/dspace/config/item-submission.xml @@ -66,7 +66,7 @@ --> - + From 2f59554a5d6ab40dd6dbb1272e69d7a25bdeaac8 Mon Sep 17 00:00:00 2001 From: Bui Thai Hai Date: Tue, 23 May 2023 14:11:04 +0700 Subject: [PATCH 204/510] ADD: Unit Tests for fix --- .../rest/ItemTemplateRestControllerIT.java | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemTemplateRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemTemplateRestControllerIT.java index 55e82831f3d1..1fd9e81ca88d 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemTemplateRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemTemplateRestControllerIT.java @@ -33,6 +33,7 @@ import org.dspace.authorize.service.ResourcePolicyService; import org.dspace.builder.CollectionBuilder; import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.ResourcePolicyBuilder; import org.dspace.content.Collection; import org.dspace.core.Constants; import org.hamcrest.Matchers; @@ -243,6 +244,35 @@ public void patchTemplateItem() throws Exception { ))))); } + /* Similar to patchTemplateItem(), except it is for collection admin, not repository admin + Test case was simplified, since it does not do anything else. + */ + @Test + public void patchTemplateItemAsCollectionAdmin() throws Exception { + setupTestTemplate(); + + String itemId = installTestTemplate(); + + ResourcePolicyBuilder.createResourcePolicy(context).withUser(eperson) + .withAction(Constants.ADMIN) + .withDspaceObject(childCollection).build(); + String collAdminToken = getAuthToken(eperson.getEmail(), password); + + getClient(collAdminToken).perform(patch(getTemplateItemUrlTemplate(itemId)) + .content(patchBody) + .contentType(contentType)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.allOf( + hasJsonPath("$.type", is("itemtemplate")) + ))); + + getClient(collAdminToken).perform(get(getCollectionTemplateItemUrlTemplate(childCollection.getID().toString()))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.allOf( + hasJsonPath("$.type", is("itemtemplate")) + ))); + } + @Test public void patchIllegalInArchiveTemplateItem() throws Exception { setupTestTemplate(); @@ -337,6 +367,22 @@ public void deleteTemplateItem() throws Exception { .andExpect(status().isNoContent()); } + /*Similar to deleteTemplateItem(), except it is for collection admin, not repository admin + */ + @Test + public void deleteTemplateItemAsCollectionAdmin() throws Exception { + setupTestTemplate(); + String itemId = installTestTemplate(); + + ResourcePolicyBuilder.createResourcePolicy(context).withUser(eperson) + .withAction(Constants.ADMIN) + .withDspaceObject(childCollection).build(); + String collAdminToken = getAuthToken(eperson.getEmail(), password); + + getClient(collAdminToken).perform(delete(getTemplateItemUrlTemplate(itemId))) + .andExpect(status().isNoContent()); + } + @Test public void deleteTemplateItemNoRights() throws Exception { setupTestTemplate(); From 571df9b38a735e7ed3eaae8e9132d5c9a499e661 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Wed, 24 May 2023 15:56:03 -0400 Subject: [PATCH 205/510] Add a simple test for new TimeHelpers class. --- .../java/org/dspace/util/TimeHelpersTest.java | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 dspace-api/src/test/java/org/dspace/util/TimeHelpersTest.java diff --git a/dspace-api/src/test/java/org/dspace/util/TimeHelpersTest.java b/dspace-api/src/test/java/org/dspace/util/TimeHelpersTest.java new file mode 100644 index 000000000000..12055140a2f7 --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/util/TimeHelpersTest.java @@ -0,0 +1,34 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.util; + +import static org.junit.Assert.assertEquals; + +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.util.Date; + +import org.junit.Test; + +/** + * Test {@link TimeHelpers}. + * @author Mark H. Wood + */ +public class TimeHelpersTest { + /** + * Test of toMidnightUTC method, of class TimeHelpers. + */ + @Test + public void testToMidnightUTC() { + System.out.println("toMidnightUTC"); + Date from = Date.from(ZonedDateTime.of(1957, 01, 27, 04, 05, 06, 007, ZoneOffset.UTC).toInstant()); + Date expResult = Date.from(ZonedDateTime.of(1957, 01, 27, 00, 00, 00, 000, ZoneOffset.UTC).toInstant()); + Date result = TimeHelpers.toMidnightUTC(from); + assertEquals(expResult, result); + } +} From eb46a99dff6265aaf5d3e36a21cc2b8d8b3a7b6a Mon Sep 17 00:00:00 2001 From: Bui Thai Hai Date: Thu, 25 May 2023 09:57:13 +0700 Subject: [PATCH 206/510] Fix: default sort option (lastModified) for discovery --- .../DiscoverySortConfiguration.java | 14 ++++ .../discovery/utils/DiscoverQueryBuilder.java | 8 ++- .../org/dspace/discovery/DiscoveryIT.java | 68 +++++++++++++++++++ .../DiscoverConfigurationConverter.java | 9 +++ .../rest/model/SearchConfigurationRest.java | 10 +++ .../app/rest/DiscoveryRestControllerIT.java | 4 +- .../utils/RestDiscoverQueryBuilderTest.java | 15 ++++ dspace/config/spring/api/discovery.xml | 6 ++ dspace/solr/search/conf/schema.xml | 1 + 9 files changed, 132 insertions(+), 3 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/discovery/configuration/DiscoverySortConfiguration.java b/dspace-api/src/main/java/org/dspace/discovery/configuration/DiscoverySortConfiguration.java index e251d1bc5118..cd1a4eecb8d4 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/configuration/DiscoverySortConfiguration.java +++ b/dspace-api/src/main/java/org/dspace/discovery/configuration/DiscoverySortConfiguration.java @@ -9,6 +9,7 @@ import java.util.ArrayList; import java.util.List; +import javax.annotation.Nullable; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; @@ -22,6 +23,11 @@ public class DiscoverySortConfiguration { private List sortFields = new ArrayList(); + /** + * Default sort configuration to use when needed + */ + @Nullable private DiscoverySortFieldConfiguration defaultSortField; + public List getSortFields() { return sortFields; } @@ -30,6 +36,14 @@ public void setSortFields(List sortFields) { this.sortFields = sortFields; } + public DiscoverySortFieldConfiguration getDefaultSortField() { + return defaultSortField; + } + + public void setDefaultSortField(DiscoverySortFieldConfiguration configuration) { + this.defaultSortField = configuration; + } + public DiscoverySortFieldConfiguration getSortFieldConfiguration(String sortField) { if (StringUtils.isBlank(sortField)) { return null; 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 fa5cc3281393..92a973dff883 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 @@ -332,7 +332,9 @@ private boolean isConfigured(String sortBy, DiscoverySortConfiguration searchSor } private String getDefaultSortDirection(DiscoverySortConfiguration searchSortConfiguration, String sortOrder) { - if (Objects.nonNull(searchSortConfiguration.getSortFields()) && + if (searchSortConfiguration.getDefaultSortField() != null) { + sortOrder = searchSortConfiguration.getDefaultSortField().getDefaultSortOrder().name(); + } else if (Objects.nonNull(searchSortConfiguration.getSortFields()) && !searchSortConfiguration.getSortFields().isEmpty()) { sortOrder = searchSortConfiguration.getSortFields().get(0).getDefaultSortOrder().name(); } @@ -342,7 +344,9 @@ private String getDefaultSortDirection(DiscoverySortConfiguration searchSortConf private String getDefaultSortField(DiscoverySortConfiguration searchSortConfiguration) { String sortBy;// Attempt to find the default one, if none found we use SCORE sortBy = "score"; - if (Objects.nonNull(searchSortConfiguration.getSortFields()) && + if (searchSortConfiguration.getDefaultSortField() != null) { + sortBy = searchSortConfiguration.getDefaultSortField().getMetadataField(); + } else if (Objects.nonNull(searchSortConfiguration.getSortFields()) && !searchSortConfiguration.getSortFields().isEmpty()) { DiscoverySortFieldConfiguration defaultSort = searchSortConfiguration.getSortFields().get(0); if (StringUtils.isBlank(defaultSort.getMetadataField())) { diff --git a/dspace-api/src/test/java/org/dspace/discovery/DiscoveryIT.java b/dspace-api/src/test/java/org/dspace/discovery/DiscoveryIT.java index 0d1cc13106a8..0c3a52ec79f5 100644 --- a/dspace-api/src/test/java/org/dspace/discovery/DiscoveryIT.java +++ b/dspace-api/src/test/java/org/dspace/discovery/DiscoveryIT.java @@ -8,13 +8,16 @@ package org.dspace.discovery; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import java.io.IOException; import java.sql.SQLException; import java.util.ArrayList; import java.util.Iterator; +import java.util.LinkedList; import java.util.List; +import java.util.stream.Collectors; import javax.servlet.http.HttpServletRequest; import org.dspace.AbstractIntegrationTestWithDatabase; @@ -24,6 +27,7 @@ import org.dspace.builder.ClaimedTaskBuilder; import org.dspace.builder.CollectionBuilder; import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.EPersonBuilder; import org.dspace.builder.ItemBuilder; import org.dspace.builder.PoolTaskBuilder; import org.dspace.builder.WorkflowItemBuilder; @@ -39,6 +43,8 @@ import org.dspace.content.service.CollectionService; import org.dspace.content.service.ItemService; import org.dspace.content.service.WorkspaceItemService; +import org.dspace.discovery.configuration.DiscoveryConfiguration; +import org.dspace.discovery.configuration.DiscoverySortFieldConfiguration; import org.dspace.discovery.indexobject.IndexableClaimedTask; import org.dspace.discovery.indexobject.IndexableCollection; import org.dspace.discovery.indexobject.IndexableItem; @@ -731,6 +737,68 @@ public void iteratorSearchServiceTest() throws SearchServiceException { } } + /** + * Test designed to check if default sort option for Discovery is working, using workspace + * DiscoveryConfiguration
+ * Note: this test will be skipped if workspace do not have a default sort option set and of + * metadataType dc_date_accessioned or lastModified + * @throws SearchServiceException + */ + @Test + public void searchWithDefaultSortServiceTest() throws SearchServiceException { + + DiscoveryConfiguration workspaceConf = SearchUtils.getDiscoveryConfiguration("workspace", null); + // Skip if no default sort option set for workspaceConf + if (workspaceConf.getSearchSortConfiguration().getDefaultSortField() == null) { + return; + } + + DiscoverySortFieldConfiguration defaultSortField = + workspaceConf.getSearchSortConfiguration().getDefaultSortField(); + + // Populate the testing objects: create items in eperson's workspace and perform search in it + int numberItems = 10; + context.turnOffAuthorisationSystem(); + EPerson submitter = EPersonBuilder.createEPerson(context).withEmail("submitter@example.org").build(); + context.setCurrentUser(submitter); + Community community = CommunityBuilder.createCommunity(context).build(); + Collection collection = CollectionBuilder.createCollection(context, community).build(); + for (int i = 0; i < numberItems; i++) { + ItemBuilder.createItem(context, collection) + .withTitle("item " + i) + .build(); + } + + // Build query with default parameters (except for workspaceConf) + DiscoverQuery discoverQuery = SearchUtils.getQueryBuilder() + .buildQuery(context, new IndexableCollection(collection), workspaceConf,"",null,"Item",null,null, + null,null); + + DiscoverResult result = searchService.search(context, discoverQuery); + + if (defaultSortField.getMetadataField().equals("dc_date_accessioned")) { + // Verify that search results are sort by dc_date_accessioned + LinkedList dc_date_accesioneds = result.getIndexableObjects().stream() + .map(o -> ((Item) o.getIndexedObject()).getMetadata()) + .map(l -> l.stream().filter(m -> m.getMetadataField().toString().equals("dc_date_accessioned")) + .map(m -> m.getValue()).findFirst().orElse("") + ) + .collect(Collectors.toCollection(LinkedList::new)); + assertFalse(dc_date_accesioneds.isEmpty()); + for (int i = 1; i < dc_date_accesioneds.size() - 1; i++) { + assertTrue(dc_date_accesioneds.get(i).compareTo(dc_date_accesioneds.get(i + 1)) >= 0); + } + } else if (defaultSortField.getMetadataField().equals("lastModified")) { + LinkedList lastModifieds = result.getIndexableObjects().stream() + .map(o -> ((Item) o.getIndexedObject()).getLastModified().toString()) + .collect(Collectors.toCollection(LinkedList::new)); + assertFalse(lastModifieds.isEmpty()); + for (int i = 1; i < lastModifieds.size() - 1; i++) { + assertTrue(lastModifieds.get(i).compareTo(lastModifieds.get(i + 1)) >= 0); + } + } + } + private void assertSearchQuery(String resourceType, int size) throws SearchServiceException { assertSearchQuery(resourceType, size, size, 0, -1); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/DiscoverConfigurationConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/DiscoverConfigurationConverter.java index 73851bd94523..41cf235a878b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/DiscoverConfigurationConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/DiscoverConfigurationConverter.java @@ -80,6 +80,15 @@ private void addSortOptions(SearchConfigurationRest searchConfigurationRest, sortOption.setSortOrder(discoverySearchSortConfiguration.getDefaultSortOrder().name()); searchConfigurationRest.addSortOption(sortOption); } + + DiscoverySortFieldConfiguration defaultSortField = searchSortConfiguration.getDefaultSortField(); + if (defaultSortField != null) { + SearchConfigurationRest.SortOption sortOption = new SearchConfigurationRest.SortOption(); + sortOption.setName(defaultSortField.getMetadataField()); + sortOption.setActualName(defaultSortField.getType()); + sortOption.setSortOrder(defaultSortField.getDefaultSortOrder().name()); + searchConfigurationRest.setDefaultSortOption(sortOption); + } } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SearchConfigurationRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SearchConfigurationRest.java index 7ec1b2250092..b25d827e75c1 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SearchConfigurationRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SearchConfigurationRest.java @@ -31,6 +31,8 @@ public class SearchConfigurationRest extends BaseObjectRest { private List filters = new LinkedList<>(); private List sortOptions = new LinkedList<>(); + private SortOption defaultSortOption; + public String getCategory() { return CATEGORY; } @@ -75,6 +77,14 @@ public List getSortOptions() { return sortOptions; } + public SortOption getDefaultSortOption() { + return defaultSortOption; + } + + public void setDefaultSortOption(SortOption defaultSortOption) { + this.defaultSortOption = defaultSortOption; + } + @Override public boolean equals(Object object) { return (object instanceof SearchConfigurationRest && diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryRestControllerIT.java index a115c8aa2f15..dd0b2fe576f2 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryRestControllerIT.java @@ -1286,8 +1286,10 @@ public void discoverSearchByFieldNotConfiguredTest() throws Exception { context.restoreAuthSystemState(); + //Update this test since dc.date.accessioned is now configured for workspace configuration, + // which will return status 400 instead of 422 if left unchanged getClient().perform(get("/api/discover/search/objects") - .param("sort", "dc.date.accessioned, ASC") + .param("sort", "person.familyName, ASC") .param("configuration", "workspace")) .andExpect(status().isUnprocessableEntity()); } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/utils/RestDiscoverQueryBuilderTest.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/utils/RestDiscoverQueryBuilderTest.java index 6c9544d2f927..511bb8f98b7b 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/utils/RestDiscoverQueryBuilderTest.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/utils/RestDiscoverQueryBuilderTest.java @@ -115,6 +115,8 @@ public void setUp() throws Exception { sortConfiguration.setSortFields(listSortField); + sortConfiguration.setDefaultSortField(defaultSort); + discoveryConfiguration.setSearchSortConfiguration(sortConfiguration); DiscoverySearchFilterFacet subjectFacet = new DiscoverySearchFilterFacet(); @@ -167,6 +169,19 @@ public void testSortByScore() throws Exception { page.getOffset(), "SCORE", "ASC"); } + @Test + public void testSortByDefaultSortField() throws Exception { + page = PageRequest.of(2, 10, Sort.Direction.DESC, "dc.date.accessioned"); + restQueryBuilder.buildQuery(context, null, discoveryConfiguration, null, null, emptyList(), page); + + verify(discoverQueryBuilder, times(1)) + .buildQuery(context, null, discoveryConfiguration, null, emptyList(), emptyList(), + page.getPageSize(), page.getOffset(), + discoveryConfiguration.getSearchSortConfiguration().getDefaultSortField().getMetadataField(), + discoveryConfiguration.getSearchSortConfiguration().getDefaultSortField() + .getDefaultSortOrder().name().toUpperCase()); + } + @Test(expected = DSpaceBadRequestException.class) public void testCatchIllegalArgumentException() throws Exception { when(discoverQueryBuilder.buildQuery(any(), any(), any(), any(), any(), anyList(), any(), any(), any(), diff --git a/dspace/config/spring/api/discovery.xml b/dspace/config/spring/api/discovery.xml index 611e77b27b1c..3f0f507451d3 100644 --- a/dspace/config/spring/api/discovery.xml +++ b/dspace/config/spring/api/discovery.xml @@ -865,8 +865,11 @@ + + + @@ -938,6 +941,8 @@ + + @@ -1015,6 +1020,7 @@ + diff --git a/dspace/solr/search/conf/schema.xml b/dspace/solr/search/conf/schema.xml index caa646ba1b8b..df21afbc6426 100644 --- a/dspace/solr/search/conf/schema.xml +++ b/dspace/solr/search/conf/schema.xml @@ -283,6 +283,7 @@ + From b3a21ebd5a81701899753d83511746ab90f36cce Mon Sep 17 00:00:00 2001 From: Bui Thai Hai Date: Thu, 25 May 2023 14:54:46 +0700 Subject: [PATCH 207/510] Minor Tweaks --- .../java/org/dspace/app/rest/DiscoveryRestControllerIT.java | 4 +--- dspace/config/spring/api/discovery.xml | 4 ++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryRestControllerIT.java index dd0b2fe576f2..a115c8aa2f15 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryRestControllerIT.java @@ -1286,10 +1286,8 @@ public void discoverSearchByFieldNotConfiguredTest() throws Exception { context.restoreAuthSystemState(); - //Update this test since dc.date.accessioned is now configured for workspace configuration, - // which will return status 400 instead of 422 if left unchanged getClient().perform(get("/api/discover/search/objects") - .param("sort", "person.familyName, ASC") + .param("sort", "dc.date.accessioned, ASC") .param("configuration", "workspace")) .andExpect(status().isUnprocessableEntity()); } diff --git a/dspace/config/spring/api/discovery.xml b/dspace/config/spring/api/discovery.xml index 3f0f507451d3..45e5829e1a3a 100644 --- a/dspace/config/spring/api/discovery.xml +++ b/dspace/config/spring/api/discovery.xml @@ -866,10 +866,10 @@ - + - + From f3b939e88f63fd83f56dd1ea7f29781ce398d4fe Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Thu, 25 May 2023 15:34:05 +0300 Subject: [PATCH 208/510] 94299: Add rest.patch.operations.limit to config file --- .../dspace/app/rest/repository/BitstreamRestRepository.java | 2 +- .../java/org/dspace/app/rest/BitstreamRestRepositoryIT.java | 4 ++-- dspace/config/modules/rest.cfg | 4 ++++ 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BitstreamRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BitstreamRestRepository.java index 454b6f8453d6..12e27dccacf2 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BitstreamRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BitstreamRestRepository.java @@ -265,7 +265,7 @@ public BundleRest performBitstreamMove(Context context, Bitstream bitstream, Bun * @param jsonNode the json body provided from the request body */ public void patchBitstreamsInBulk(Context context, JsonNode jsonNode) throws SQLException { - int operationsLimit = configurationService.getIntProperty("patch.operations.limit", 1000); + int operationsLimit = configurationService.getIntProperty("rest.patch.operations.limit", 1000); ObjectMapper mapper = new ObjectMapper(); JsonPatchConverter patchConverter = new JsonPatchConverter(mapper); Patch patch = patchConverter.convert(jsonNode); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java index 2a1044c28a04..8b34edb938a6 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java @@ -2565,7 +2565,7 @@ public void deleteBitstreamsInBulk_invalidRequestSize() throws Exception { context.restoreAuthSystemState(); // Add three out of four bitstreams to the list of bitstreams to be deleted - // But set the patch.operations.limit property to 2, so that the request is invalid + // But set the rest.patch.operations.limit property to 2, so that the request is invalid List ops = new ArrayList<>(); RemoveOperation removeOp1 = new RemoveOperation(OPERATION_PATH_BITSTREAM_REMOVE + bitstream1.getID()); ops.add(removeOp1); @@ -2577,7 +2577,7 @@ public void deleteBitstreamsInBulk_invalidRequestSize() throws Exception { String token = getAuthToken(admin.getEmail(), password); Assert.assertTrue(bitstreamExists(token, bitstream1, bitstream2, bitstream3, bitstream4)); - DSpaceServicesFactory.getInstance().getConfigurationService().setProperty("patch.operations.limit", 2); + DSpaceServicesFactory.getInstance().getConfigurationService().setProperty("rest.patch.operations.limit", 2); getClient(token).perform(patch("/api/core/bitstreams") .content(patchBody) diff --git a/dspace/config/modules/rest.cfg b/dspace/config/modules/rest.cfg index 6421258c575b..657e02b58de7 100644 --- a/dspace/config/modules/rest.cfg +++ b/dspace/config/modules/rest.cfg @@ -25,6 +25,10 @@ rest.projections.full.max = 2 # This property determines the max embed depth for a SpecificLevelProjection rest.projection.specificLevel.maxEmbed = 5 +# This property determines the max amount of rest operations that can be performed at the same time, for example when +# batch removing bitstreams. The default value is set to 1000. +rest.patch.operations.limit = 1000 + # Define which configuration properties are exposed through the http:///api/config/properties/ # rest endpoint. If a rest request is made for a property which exists, but isn't listed here, the server will # respond that the property wasn't found. This property can be defined multiple times to allow access to multiple From 4fa51d03d11259cd67506247be406d5b7faa7e35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paulo=20Gra=C3=A7a?= Date: Fri, 26 May 2023 17:14:10 +0100 Subject: [PATCH 209/510] adding support for access status xoai plugin --- .../AccessStatusElementItemCompilePlugin.java | 67 ++++++++++++++++ .../oai/metadataFormats/oai_openaire.xsl | 78 +++++++++++++++++-- dspace/config/spring/oai/oai.xml | 4 + 3 files changed, 144 insertions(+), 5 deletions(-) create mode 100644 dspace-oai/src/main/java/org/dspace/xoai/app/plugins/AccessStatusElementItemCompilePlugin.java diff --git a/dspace-oai/src/main/java/org/dspace/xoai/app/plugins/AccessStatusElementItemCompilePlugin.java b/dspace-oai/src/main/java/org/dspace/xoai/app/plugins/AccessStatusElementItemCompilePlugin.java new file mode 100644 index 000000000000..65ec251b21ce --- /dev/null +++ b/dspace-oai/src/main/java/org/dspace/xoai/app/plugins/AccessStatusElementItemCompilePlugin.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.xoai.app.plugins; + +import java.sql.SQLException; +import java.util.List; +import org.dspace.access.status.factory.AccessStatusServiceFactory; +import org.dspace.access.status.service.AccessStatusService; +import org.dspace.content.Item; +import org.dspace.core.Context; +import org.dspace.xoai.app.XOAIExtensionItemCompilePlugin; +import org.dspace.xoai.util.ItemUtils; +import com.lyncode.xoai.dataprovider.xml.xoai.Element; +import com.lyncode.xoai.dataprovider.xml.xoai.Metadata; + +/** + * AccessStatusElementItemCompilePlugin aims to add structured information about the + * Access Status of the item (if any). + + * The xoai document will be enriched with a structure like that + *

+ * {@code
+ *   
+ *       
+ *          open.access
+ *       ;
+ *   ;
+ * }
+ * 
+ * Returning Values are based on: + * @see org.dspace.access.status.DefaultAccessStatusHelper DefaultAccessStatusHelper + */ +public class AccessStatusElementItemCompilePlugin implements XOAIExtensionItemCompilePlugin { + + @Override + public Metadata additionalMetadata(Context context, Metadata metadata, Item item) { + AccessStatusService accessStatusService = AccessStatusServiceFactory.getInstance().getAccessStatusService(); + + try { + String accessStatusType; + accessStatusType = accessStatusService.getAccessStatus(context, item); + + Element accessStatus = ItemUtils.create("access-status"); + accessStatus.getField().add(ItemUtils.createValue("value", accessStatusType)); + + Element others; + List elements = metadata.getElement(); + if (ItemUtils.getElement(elements, "others") != null) { + others = ItemUtils.getElement(elements, "others"); + } else { + others = ItemUtils.create("others"); + } + others.getElement().add(accessStatus); + + } catch (SQLException e) { + e.printStackTrace(); + } + + return metadata; + } + +} diff --git a/dspace/config/crosswalks/oai/metadataFormats/oai_openaire.xsl b/dspace/config/crosswalks/oai/metadataFormats/oai_openaire.xsl index 7b66eaf04372..19b1486f4cbd 100644 --- a/dspace/config/crosswalks/oai/metadataFormats/oai_openaire.xsl +++ b/dspace/config/crosswalks/oai/metadataFormats/oai_openaire.xsl @@ -93,6 +93,9 @@ + + @@ -658,6 +661,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1207,7 +1244,7 @@ - + + + + + + + + + + + open access + + + embargoed access + + + restricted access + + + metadata only access + + + + + + + + From e889abc6238b257bbde537814e011a842f4512d1 Mon Sep 17 00:00:00 2001 From: nwoodward Date: Fri, 26 May 2023 14:21:57 -0500 Subject: [PATCH 210/510] check that zip file exists and has correct MIME type; also make sure that common temp imports directory is not removed --- .../org/dspace/app/itemimport/ItemImport.java | 49 ++++++++++++++++-- .../dspace/app/itemimport/ItemImportCLI.java | 23 +++++++- .../app/itemimport/ItemImportCLIIT.java | 24 +++++++++ .../org/dspace/app/itemimport/test.pdf | Bin 0 -> 56812 bytes .../dspace/app/itemimport/ItemImportIT.java | 6 +++ 5 files changed, 95 insertions(+), 7 deletions(-) create mode 100644 dspace-api/src/test/resources/org/dspace/app/itemimport/test.pdf diff --git a/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImport.java b/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImport.java index ac9db7605103..bcf7afed38b5 100644 --- a/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImport.java +++ b/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImport.java @@ -23,6 +23,7 @@ import org.apache.commons.cli.ParseException; import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.StringUtils; +import org.apache.tika.Tika; import org.dspace.app.itemimport.factory.ItemImportServiceFactory; import org.dspace.app.itemimport.service.ItemImportService; import org.dspace.authorize.AuthorizeException; @@ -77,6 +78,7 @@ public class ItemImport extends DSpaceRunnable { protected boolean zip = false; protected boolean remoteUrl = false; protected String zipfilename = null; + protected boolean zipvalid= false; protected boolean help = false; protected File workDir = null; protected File workFile = null; @@ -235,11 +237,19 @@ public void internalRun() throws Exception { handler.logInfo("***End of Test Run***"); } } finally { - // clean work dir if (zip) { - FileUtils.deleteDirectory(new File(sourcedir)); - FileUtils.deleteDirectory(workDir); - if (remoteUrl && workFile != null && workFile.exists()) { + // if zip file was valid then clean sourcedir + if (zipvalid && sourcedir != null && new File(sourcedir).exists()) { + FileUtils.deleteDirectory(new File(sourcedir)); + } + + // clean workdir + if (workDir != null && workDir.exists()) { + FileUtils.deleteDirectory(workDir); + } + + // conditionally clean workFile if import was done in the UI or via a URL and it still exists + if (workFile != null && workFile.exists()) { workFile.delete(); } } @@ -329,7 +339,14 @@ protected void readZip(Context context, ItemImportService itemImportService) thr // manage zip via remote url optionalFileStream = Optional.ofNullable(new URL(zipfilename).openStream()); } + if (optionalFileStream.isPresent()) { + // validate zip file + Optional validationFileStream = handler.getFileStream(context, zipfilename); + if (validationFileStream.isPresent()) { + validateZip(validationFileStream.get()); + } + workFile = new File(itemImportService.getTempWorkDir() + File.separator + zipfilename + "-" + context.getCurrentUser().getID()); FileUtils.copyInputStreamToFile(optionalFileStream.get(), workFile); @@ -337,10 +354,32 @@ protected void readZip(Context context, ItemImportService itemImportService) thr throw new IllegalArgumentException( "Error reading file, the file couldn't be found for filename: " + zipfilename); } - workDir = new File(itemImportService.getTempWorkDir() + File.separator + TEMP_DIR); + + workDir = new File(itemImportService.getTempWorkDir() + File.separator + TEMP_DIR + + File.separator + context.getCurrentUser().getID()); sourcedir = itemImportService.unzip(workFile, workDir.getAbsolutePath()); } + /** + * Confirm that the zip file has the correct MIME type + * @param inputStream + */ + protected void validateZip(InputStream inputStream) { + Tika tika = new Tika(); + try { + String mimeType = tika.detect(inputStream); + if (mimeType.equals("application/zip")) { + zipvalid = true; + } else { + handler.logError("A valid zip file must be supplied. The provided file has mimetype: " + mimeType); + throw new UnsupportedOperationException("A valid zip file must be supplied"); + } + } catch (IOException e) { + throw new IllegalArgumentException( + "There was an error while reading the zip file: " + zipfilename); + } + } + /** * Read the mapfile * @param context diff --git a/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportCLI.java b/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportCLI.java index 1a71a8c4c09e..98d2469b7155 100644 --- a/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportCLI.java +++ b/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportCLI.java @@ -8,6 +8,7 @@ package org.dspace.app.itemimport; import java.io.File; +import java.io.FileInputStream; import java.io.InputStream; import java.net.URL; import java.sql.SQLException; @@ -101,6 +102,17 @@ protected void readZip(Context context, ItemImportService itemImportService) thr // If this is a zip archive, unzip it first if (zip) { if (!remoteUrl) { + // confirm zip file exists + File myZipFile = new File(sourcedir + File.separator + zipfilename); + if ((!myZipFile.exists()) || (!myZipFile.isFile())) { + throw new IllegalArgumentException( + "Error reading file, the file couldn't be found for filename: " + zipfilename); + } + + // validate zip file + InputStream validationFileStream = new FileInputStream(myZipFile); + validateZip(validationFileStream); + workDir = new File(itemImportService.getTempWorkDir() + File.separator + TEMP_DIR + File.separator + context.getCurrentUser().getID()); sourcedir = itemImportService.unzip( @@ -109,15 +121,22 @@ protected void readZip(Context context, ItemImportService itemImportService) thr // manage zip via remote url Optional optionalFileStream = Optional.ofNullable(new URL(zipfilename).openStream()); if (optionalFileStream.isPresent()) { + // validate zip file via url + Optional validationFileStream = Optional.ofNullable(new URL(zipfilename).openStream()); + if (validationFileStream.isPresent()) { + validateZip(validationFileStream.get()); + } + workFile = new File(itemImportService.getTempWorkDir() + File.separator + zipfilename + "-" + context.getCurrentUser().getID()); FileUtils.copyInputStreamToFile(optionalFileStream.get(), workFile); + workDir = new File(itemImportService.getTempWorkDir() + File.separator + TEMP_DIR + + File.separator + context.getCurrentUser().getID()); + sourcedir = itemImportService.unzip(workFile, workDir.getAbsolutePath()); } else { throw new IllegalArgumentException( "Error reading file, the file couldn't be found for filename: " + zipfilename); } - workDir = new File(itemImportService.getTempWorkDir() + File.separator + TEMP_DIR); - sourcedir = itemImportService.unzip(workFile, workDir.getAbsolutePath()); } } } diff --git a/dspace-api/src/test/java/org/dspace/app/itemimport/ItemImportCLIIT.java b/dspace-api/src/test/java/org/dspace/app/itemimport/ItemImportCLIIT.java index 411e8de4dfed..02a0a8aee048 100644 --- a/dspace-api/src/test/java/org/dspace/app/itemimport/ItemImportCLIIT.java +++ b/dspace-api/src/test/java/org/dspace/app/itemimport/ItemImportCLIIT.java @@ -8,6 +8,7 @@ package org.dspace.app.itemimport; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import java.io.File; import java.nio.file.Files; @@ -33,6 +34,7 @@ import org.dspace.content.service.RelationshipService; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; +import org.flywaydb.core.internal.util.ExceptionUtils; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -46,6 +48,7 @@ public class ItemImportCLIIT extends AbstractIntegrationTestWithDatabase { private static final String ZIP_NAME = "saf.zip"; + private static final String PDF_NAME = "test.pdf"; private static final String publicationTitle = "A Tale of Two Cities"; private static final String personTitle = "Person Test"; @@ -55,6 +58,7 @@ public class ItemImportCLIIT extends AbstractIntegrationTestWithDatabase { private Collection collection; private Path tempDir; private Path workDir; + private static final String TEMP_DIR = ItemImport.TEMP_DIR; @Before @Override @@ -226,6 +230,10 @@ public void importItemByZipSafWithBitstreams() throws Exception { checkMetadata(); checkMetadataWithAnotherSchema(); checkBitstream(); + + // confirm that TEMP_DIR still exists + File workTempDir = new File(workDir + File.separator + TEMP_DIR); + assertTrue(workTempDir.exists()); } @Test @@ -254,6 +262,22 @@ public void importItemByZipSafWithRelationships() throws Exception { checkRelationship(); } + @Test + public void importItemByZipSafInvalidMimetype() throws Exception { + // use sample PDF file + Files.copy(getClass().getResourceAsStream("test.pdf"), + Path.of(tempDir.toString() + "/" + PDF_NAME)); + + String[] args = new String[] { "import", "-a", "-e", admin.getEmail(), "-c", collection.getID().toString(), + "-s", tempDir.toString(), "-z", PDF_NAME, "-m", tempDir.toString() + "/mapfile.out" }; + try { + perfomImportScript(args); + } catch (Exception e) { + // should throw an exception due to invalid mimetype + assertEquals(UnsupportedOperationException.class, ExceptionUtils.getRootCause(e).getClass()); + } + } + @Test public void resumeImportItemBySafWithMetadataOnly() throws Exception { // create simple SAF diff --git a/dspace-api/src/test/resources/org/dspace/app/itemimport/test.pdf b/dspace-api/src/test/resources/org/dspace/app/itemimport/test.pdf new file mode 100644 index 0000000000000000000000000000000000000000..5b3749cbff73a41baf8aa06b7f62339886bc6ca2 GIT binary patch literal 56812 zcmdSAWmH|=wk=2?K!Ow8-GXo2-QC^UIBXn(TX1&`1b26LcMmSX-5rYL-0$9V+Iz36 ze$=0$HQJtQtuf~4W6ZH;?`sR0ys#)OBOMz&3|Za#A3O{rAp;@E&;lNYo10$wn=O!D z$iUIS3S>$zZ(s^^AOyT$R-~6UurZ|s+R&;fQ4!KBIodlJIV#%&fr=oIBOxQpUlSPv zdrJu$6VUrKe}+I~!gn(uFE70e(9yv7eNI9qj=zSg=0F#qy}Uip1ZWSmF?x@|%<$&| zFE2a{(8l;LmOoMd1JqwI^gNk{)lp2z(ZCV-eqBt7jgak6u=f!=A^V@v zyM??x$Vdt3s73!?QF>*dt0TRH^?S(#|MepDuNR3w_})ukY;GV3a@BfwWgui_1Q4=u z0CWi7^RaP!NA_NUf6ZUFwoiD2&gC~ z_&%?)fuX~@$-frlUs*a3GW>f%{!IK&K}5~19D(-qqE_!&2?LEl#z1;$ppB`c86hKp zmHCgCgQGprz#1OLHN8x2J!**^$xCd@JK3Ei;gj<-Nr$w zOT0dWYUJgH&4{99vU3X9BV>Dg;lZ>GGQq|4^joM9BcM35@rf9)^fYYJpw@aVCcpM0bz1drdfU8G6jA=#C+*NKP(v>OP83QJ{k^e zO036Hyh0Jb^606Q8xp4L6@%Pc67}EIp?wI!U3Bqyzxs0q7ars7_Skbdx1y1f7}Q+? z#*OrbuHf1WHj*5*T^2#LW#o5Kq^gGc0w~{e46E`?02KY3t=Pt7)06l|fU4h)&@Utz zLWx>Mk>iS=N}&(q`L6ub>op3R>{vVG2F+5Rq1&x-UpTiDdKUu=q&ZwF4aA_sZWujy z-DcsNLz@U1rub0FMAI$Cm#U81mrALc40KB?0jlNTi_&@p{pjFYP3@p+EPQ2&IlDA1 z6Ex)y1REelpHT4w+)#q+H+4g`p^m|RyitkzMj=mcG%eJ7_;T*US0U?&4^o=yv8V!X zgO^_7XZ-6**B$*rn$bgOdR1I@8J6JzIr z$Le;hD7c7U@+BbR1v5JDSFH=3S?NOE<7blE5~&s&L)HnmR>4kEyK>$(5$|PW>pN z=W0gx+bwskC!`zg$%6OwRhmK$UI-7d<%T=glf_grj@ zu8T{UO0H>RNiE<}W<~#_1`k$O2syLDmK~@=7ww^ddIzo2|KxDZQGx zjew1V`F|h(u@?q97}=ZKI)dy8S^u(tsJXp^qmY?_Js}g|op}Fi%m@IyyBj;2IS?}a z7X$qdUH)@fL&X1)>whyf zy`T)eEXdy4z>40;fRORuY4A_Q{vVx$1pd?Mj}-V1r~hn#_by=gf3@lo#_y#xcl<^x z{$I5;Hn4I0GnWG)<6q@NuMASLG5h$iGAXYw{O{79rywvGy)`{%7U?p7{sl z|4j@2)3!nq!ha(F>0H3SbjIJL-cKuo#3Y1e3~cHDflDu}_Kxp;?cOgceRFtUsXy}W zT`T;-@OQ!fr^5&t|B5ksHA1GpLg_ye_;)cUXKxI&e_wpcf1nC2K)~(^Eb#p$^Q2(`9CQBg8Kh+pZ`tZU8Vs6giL=oga4@GziT;V zb4M#6r2yd{-AVX<2iO}JIhuoP-j5v|357vMPJgRs^1i7EE7=+t{i&*elcO2Pp7M`4 zCsYH0jNeZQz01A#jfe8}4NL~i9LxyJ9t;HL@cwTC<_J~}MhK?%KDGxlejl0q9TEO- z>W+ng&fR=%Uf`NhJ-Ao2#{QqF_F0GB7 zjDY`N0sHqV0{*=l|Mrlap@ov8HN7k$BM1H8z4yJDncheLC$Y@T0{AoGKXmb~m6c^I zxB21PFdhjUSAOh{Y$H;B2hL2(V+-Og3N7|9Pm$->7qFw4y?N$^FE>k39s%jUP1pPo zC5gx;6Q6`C1<&w_#jb?}!tbNjO7oivt50%ZN=Sd`H!&6@4pE3!H)Jj%Oa5q8+=VHQ z2K-7m20Q$*DKSftk{tvknFpgRo2+CHVEL}C8_GdVN+V3QYxr;(kPWwLfLCs5)w>Gw zn;z+k7BjM`nUn7cHUyh~YMacYl@yD>L&p44xB`qOeY-r}WG@L-a2B3{CHGPtPHhy(ir( z8WpyhS3TIJU_8$!vU{D78unQiMbcP6x3y&@=pz~1ZYlR`iclC|*KfTriq|82YBFCc za{`DIjYdNy3mXwUS97LwA6K-~p91lZdzDlZwS_QXcp6UgPJAgSwH7#g$CxYpcj>o{ zoI4@6!bb}QdiFIT+v`mAMX+Uu!i?C-AWA@6vC0Lqo^|*&SJ4^y#Iz*C071Fn0O4BX zfYZ+sf@nW^oSWvgis?-*Y4dk-6F4l`aHbVQtVMrcut5&NNJt?3M&RQD5Z?*o2e0Vj z$;;V2`NuXN-xhiXdkLq$)Zh)EHZ&dc>$DfH+0>kIZTl(Lx z!M`h;|MMC!voXAjiGMxJGQN`*fSvU(as1Ccdh7}75#Kj=my%)%l3;D|U19qr#hM12 z799;6T^UQE=IaxXW{fN?*r-Q}$-jvfqVmxn=If1a*H_8D`7u#P0qUuR7L}^91<0&ajrsJbbHJ^Pkk^4v#%tm^tM98Q{ z^0j4q(S4Se+H1>lj)a}i6s+-)y>mV1U`%&$U&$3lyoirft~ht;bq*V3h>H2dQMWTY zjBi@$5v8Y4oTSN?Zl3*`tQ{gpIh;Q)J>s%^e8BXlAgvI1`bB9yo zJcmK2F~8<~HdnEOJTmBWKb~iuC}Ux4T%0T#Lm45n5a#YuFHTs6woBKX#7G(4w!a^)r)5 z++RSa$VohoFVRK}sHQj0yZyBL5=t(k2VU4Urzba4=4jQ|&{3=FDhU9)s39|*{J12n zI!qEdAo*2CCie;T#a{e7rKy&{E@KMxKZfo(kqmzg>BV(!f_2kQ4T#AJVpw?qU(QuyRc#4{YmOT1f;$EZ^Z z7=>U#)NG{hl+SOUtxR3d_?83DU^$mNqRReWIG^|^y4fb0m5}{p^4Cn5=)XPw*Ws&O z7PrJzdlzafjGHm<-FS%9p!F*gGA>L%!Mo$?<{kOFY}{hfN=_n*jqFYe_|hEZZfqcpJ(Xa8glFD?wkHp7+4GXGt` zQkwz4O>)2NHfL_1&k@Gm!Ja?ZvEjpsd}2|YQ^ouvpTFgVtCk8E!TUTz&i!6@TkGZ6 zb)!ZN;dQ?zgW!kI^@4ZiR>y~j0VLIF-b1DefSzCI<4gk9UJM_(9XUTG>MBg%bwrmg&Q%~ZL#MIbyc2{}7xi6r| zun0yhc()8<_8dPq7oifS=oxHz`xT>p)ZywD`0#or%(4#@>hPvVAJfZHQ?IOK&{D5n zC6yw~iVK)i<_<{o418WDgxHQRkX=FBM zjbrJTjX(1D6hp4wlsTD|Q?TaG>u|BZ?i*0$q$F-q-6Zg&hnrxc ziMuS!K~JwZP5b$r&o+#GW|pxiuc&F#e(m-HE3tN&K!|Q3*T1m%s$AlPGPwW+r^z<{ z-JZF2=$F#uf~WPMWQ|svox_U5k_jucaGKt2%!WvZlW#mrkfI#M)P2^{F1)Fvb#@D{ z(@!PzmKHX4h-C_vOe&|L*)uVm52xm4CpP>83*Fx%l+yRZD(={z7u_894?e^JIU#; zrsZ?aj3=U^U#p`K+nj_nz!`6inwsqXh&AVs(b>hO$h@SELvI=&#swdDrl2u*Hj*X* z0$fO)8lnQQJ169+8KAGK81##+PwG|uKnXVxoKbTp$FHFbZ>K$(nPcWS8~Z%-72+nq z_;eWNLAhNBU6B4D5*A^g<|w5VZdt+zEUfBx*UnV~C%;b5*^le>57c2B9!XNNXZg{R zH42{`Wh^4&GnkjrN$UoW5YJVq4!Ec!Qpw45D98$m8=8>^5G}O?6g!h04vnhF5zF=D zvGj-DB3x~R*Vy}3+08|3YT8?zIvXOCCccXNCXpM0vZo>T>46A^{(*fYx-au%iU#X` zpBw)4@q3w3<*dGuzO8jlrJXnFfVH3{%mvq#@<{b0F2%AUw;S#2UI3iv$I5<#@j#zC zCu!1~fky518bwME;rZ#o0}BWv7-hE!x+u;PkHl|wtKd-79IbYT;isp`HOBCZrOhok zE8~Hu^QZOljh6Mx=1wkW$Ya?}EjjHszpsP3-PwMD!!JBC21AxsS9a2pt*$z_iM!^r zLMv+_&HzX(*d8EI+Pqx!>laS!*(O9NcoIv8ZJSNIh5ZV@A7Y5-st73}O_Wuo6o;lc84p|DP>@)V|K6Hl-BHkRBa zW?hhLfN6wde2!D@mhdFLBJr1-+%m+-Q<&(O8dj* zspk|9&2;-Vk%wUXYA~8}FmEDFR7U6hSt%@!k&X_r@|JGRXl3vxkNxt0J7sKj*%vvB z3hmPa&dm$N7_3Dd&dda#G+`pJKihW%Ae)wdw04gv;a`?oz5q{y{+eW4f>W}@Qy!GL zH%B>RcE|4Wf@>z;i<6D3+LZ3_tT;lOBQ$>grW%s*lk|?K?84@#Fpi8>UhqTaY3FlI z7_2NB4(ZnSLo93!!eCQcRvllF`pz#Mckz;1S(f!RI0D*KBu$DX9_78NG{+7QIw#il z5k^xDEnS$Ui$9Ba0FWaVR^{!5=3-f=cA5z)I+?A3eKU;lm-o@ug=U0oXnLD%U zGc<@xt&-39fLG{%zjkxV+QgqqylvjC3pqW{1L^b`WwQYiZn0{&Zp>+92QL~0=fcYv zgpH9!HTI#}$)%+Q0*>4bJ1czG0sJw&Dt2RL)2U%BVIcb3B`SdJGBBVi)y3Gd-mPH- z5$$x!UgM)@GT9EBw4}Bvnq{#!Q8Kn+5G0Qz%S_x zs)^5Ll-XvN;v%yPyd1uHYd2#KK%* ze*nk9)e2sac7l^Z^Zw13Cv6|h7u>_X&egk_#XE!iCG#$nD6;sF)Gk=Fub@`OLmb)% zMx(2WSPk-+pBHZS`%~{z+szG`#iv;=<(#h8=U5&^BjopG!6LAY8kR?L3(jS1aULglir+yX zf=7X&X_B87ICYM72E8MV=JXNi42dOi4$-M}fIifUM*ooXfeix9_4dO##)$GUYM3po zU9e@JswDU-sm=6$Th>7qAJjU68{2TK4G!h{hp#&gi02?74y^0>4{osctdnU9iPu38 zJ1T~Gs-QlwRXR?8=b*YJgeDYqIMhLF5gTgl1{K}fXn9j$Eq6mAYA_vFk%NYCfxI?U#z>X}mLr|NTBr!c<57HrVyq}voObC zHXpuXTEpJU+7VSkdYStwU<@x4C<+e2tVBHv$m2<(?`ve4epmo6htwAQN+#2(sz2bP z>@8_u9XZQKOxCF_zTFiuK#54qL+sFLN137k$T1F__wvF;*NoxxmEr$+fdIaP0XrZ! z6bk*jjK7VXFZ&=pzNngxuN?jN?M@9p@A8j{`rk}*YAQO}CPY}wW|NO&+XUDhpy!h9 zgv5)H{t6s=(SUBxHUW@bh-eLW>?uNiUQ&3aa?}=LS2cR>vJbS6AFgsd)!c4S$rwt>*u$JjZK5C@;EI%XZY16_KX1?6+&6U} z;C%$}J^;K80bgTAT33L)17t@)eU0RA*@^wb@vEhI+qKMM5l+dOPRRvaqa?Vy7ECQ+ zKV6Ea6k8IPjECD@7Zpv3W{i$ui-n|U_D5VcBFd4`5gv;L9{t-L1Y|#Yy7)em?Y9hF zus2RQPZ^h=jA;GQvp1MJhujiqnE`2PY84VUv7Wnd7lki|qc518Y&TuCub6 zVcE9bAl4xM=d8S$Ej9oiD^PfwFlP=E6wcI-86n!Hz-Ui`g(9@+U##XOmsHpwnXP7A zZ1#;hi7Lt?Vjv~P=;k9-nCKdNULYhCi{kcdYUd}A(ICkubI{h{-Erg7KRHwzChrHqpcvJo{Iz7+-fq>POdhbt^a@vX&Q z>ntYs1409v&%`yM70=AYZ+?6(N@4L$!zo~f9wBfgtm!A9oVwg(xQy_1*uju+zqfBM zZMQ)@i|$=-4Jz9lIiir_)l%By%M;aTg|PS?CtQ4^n5djXGhb2^u`r8Np>gBHqoIME zSFosF6u|B#sgz1!PK~3JRYoq6)qfISR+lh#q6qJcY(8plVr17#qiK}LK8-yREy;tA zs0_ocm}eRQ4a4&TIn{N+ylNSRaX=2gi~#Y<@`b@!lY&0mUmtUFQ*__;Ery|C_e|cT z&H8NIJV|e_uK8#WTni#il~z1KjmLB%-37XRZ^S%fwVhv4+j-4tq6~J_Zh^ynX{keK zg|5%~v>D%`B6DeFrhL2%w!X;v=Cq#E`^@vm0%zjzC{@XGuKX#oe}1Un0;qS#;uX}W z5PBBp$uiuI{eE0@XYR=&TW$~`GsYuYe6cVdKB@|dOJ_kKJ&&d~e^`Xzi$u+E{Zzgu3T1aD8 zcUL2^!72za#WTgk2--r(8`jw=8ZKG-iP{v#E%Hr-3pHqY)9yEiGbJ_4`2uUnyl1&CC8KboUii>;%v)S2Hcwbnoc9^~npT#*!mwB`wFdL@O4wLTP9 z%#7-M4vnHISEquW{(MxeC4-kw7gw2@o^KduVF5Tu3~@57K|)X*xao6KSQ>pGTFmx|+`c8{ct1T&)2Mdy^g5FSSvxqP z*KK>yyrsGk!9z9s=5wVw@^ZKm3FPY2s;q1e$WF#f(_nRWvqg=#i-KMzw z(Ac%D%h(vA$@*`|oIufl`Am$T?T4|-llm&=(shtxPNMEH?VmiL@7*`_d^i<|C2RZD zMCY?Q;yByjdWca~2Xkjy{JzB)0Gw4F-P%YM=+t^ql-rJvRisND$ef(NWW#=x#BNYh z)uK9(@bO{xT)h!)zW5DhV%ES3(}BqS@j2I9`@YeJvFQopJ0i@(!i+bkk1&37O1%Or zoIqs$9u0sq+FHv4V`nH(b{Y7I~PNDeG(2vD~l&A#LraIqDz=-5M`Lfd6`qX^#L+JiGL5AgWq=xrS*8m3x3;7>H+<{ zCV7ko2DzKT*%K&$pCTXaLut2ROoeT3S2CxW%)A@}ZmAF=%4#L3l$v|O%u!GB7c@xh zQOxR2X=iM3-EFem)nSfc2|jsC%WXt6V9>t!uy$_|-dz05R&B^gj~CjT{5A`9;61r) z#ehRTYirX;?t#;IH-oV8(2u_kN&71DWUa2h|V52NXxvfg2E!z>t8?)yZmRJiGN73fr*sm}KM z50@NU;H+OTZaUCy;PmqFTCOXplVA$NMG3ujiBi#f+7Y-hW`K^6PBS;bNjT`X-#B1fAHY> zQRczt?e9$t5ax28qT_Z65$j!cJ))FUSMxIsw~0ITdXWm`?!oM_+NO^r;AQ+B@Yd`8 zGMl;PE$}w>B6sVJ%gfh_ed}Hr9oRh$UwO&eyMe%sNce@|r|AL4b-u|FMmQ55F>gW> zGCX8*-B)M2NLiy_A6Y+7FmuoRg5$QfV%737#XBW88*bc+w$al8meve~V zd9L4KjWW__y=A&Zbzyl?)|~*JCw@V-M}3t*ouHPm5Z@4Q#2Hks+raOVbV}7GqnDqV zub11Q@)rGy@?z(|HW{FF4-C}9=@vzFF4{Wqhof=NGu?(t*5z9u!`Ly%4}mj7%@xva%J)Rz=uCW;0u|qL7p4usM1MH1l<+83cijSlmRV~n4Hlev?@x!XlGmHIK z=m&jHAC)%WbwA09^Y0!gEY!eeqgo`dw~$lCdw1sb!N6cWYL}^??y-*Ex(kzIZ2B5I zN!E^NwMv~uJ2?g}Jd=?k|CeaxTg)d~*v-NYrIm^PEA~-UpR`|}R>*bH>258Ng}ls3 zMm8l%LkKo!dbb~6vT^U4UaqAydp=T&9;XL*HGiVkr=IBhp&GUNTdg!e@c{k!PV9*w ziL3sUc9p#M*J`#8xf0{p7QF)5e1DK@>Vd`ApKZZi5z3>Wype7lD}>vZ&x?Ep+OXPsuGvvkBsZ_Wq(b+~3NDxW#o3t10e^gz&0&C%80=LwrKvtQb|xKY&3sWvkfz16<^ zGpDoeKR=#{X&djP4Q@vcS!12Xqh~ORTzJ=JyR3Kr`fC z!AT>@n*Yp_Kl{%0mI-n;&-KSsK{<=NepA~SR|JYij03XtJ}-fajn@aMN3>J?vq_LG zt9ul}hNBbGUQJZ+$Xl3f5|MXjaLY<;ryIxt(KPCU!WNO~+*4qhis-es$IlQBP?M&R zs!*{E%|X$1In;|@14}h$`Lg8F?egQM5Y+vdK!pgz6KSkoRTdN!H77?n=quibw9%mMpv9WzK2+#4J+d;la6W!>W00c`y^CUFPzI{d1H^jesUe1_C8^rz zIbYXvH9B}bu5G)sLbH36of?N9rz#EWwVy=$N3ajAiifz98GH2O1mrDL&}P3#8qhj~ zrC(NW=8+9agg_A~wQ)(=FZ!jMcv>qc)Y(r>9v=SWXVrtFlB>YxYiFEuZ*6RruZAtz zDV*O%zhD;yiV8#S6yn3@@nqiau_I%^c+VW&#+%nxHim!i&b&5cM}^_5Fd>dh3?=m> zjpZ9)6w;Q&jT%yq5=gU)&0MlXI=IN{5*ENb?J;o(|3W{Ik^bXHEK-f~!$f>ueCL<> zWk7v1dYQzHTgMNlt)pOAsAt=5HL`+{k;Dn?d&Nh?Wt&hn_>gJXyaW0qaf09#CXWry z86s`GAEROjH>z;u`s2uH|uXR z9UmN8;gE5pa&qZs;S_JG1X+p_cF60BTl;$JQ}6+HGmq@%H8)2)818NM!$>Lu(*u$% z`tf_COiJGUentCdA=v8)ZSY8VZ$L&UO*3zcf9@5EogHMs$m)5gwvmj zoYV_L+fZ;(?VD6FLu=QM>44tfzvD^O9?W!q$S0=7ZI1(R4YsxYJ(-@q9S#0||EP6U zDTmwTt_mI_s@5SY$P_`VXML5k*F0(cWem=nQj}=7+Ke(RX?xuopc%@M#0Q0=p{=W` ztgXUb)tGk=m?58uY{WabTa@PLXLo3=B!|ry`N<&lyzzdE|5k-aa?q``VZgyPj5>}q z7e@)$VbQ2-ntN7%Ac)yT#dJe321t>QtVA2>q&_PM*ag#iGK)E(qhg%<4K!lmm@?u_ z9W^_MlEUy+ABacc||N8P>0!{BW(;8mTmls5Z~8wgucWx@kkj<HBdDq+JINjTzg@k+z& z`oqy}a*3NX#@xQWjfjX$YrVI?+c^hysE^Z0U_oX*c$KSi8snNFqn6PzxmcW3rZJY4 zjGTDGFMuM`4c&tfgj^G8Ti+NE`vzgojDfG)c~^Mx;Pw-jh!J!gbM~4cp0-JSZ}tVu z2)mTOQ>kb62q?uSBXQA6&o~97qpt@%QDp6P(PC9+gKO?@pRxLeyi^N zqkZ5tc1PssDusT@+vwZKW6BQ75ydTtc3r(=?0H^SznXcev1dFtj&jCkf}w9QYYvoGZqtrgOZixhBoSN;!^%d&#UL|GNH~V23yPRsXc$pkv~tk6t#JY1?V1-}7+wmHm_v+J9F|m3`w+|Qt`TxLE@U`DQ!-M1r5*Rm>Q3^M zF^$LrMV9t$luR`yw;_*WDXu2&DmFAIan>}7I&D04#^m?fC`zeuS^E2zxEfTQm7P^= zDwy*z3X+(SZ>z7Z+;uf$viG`w7-p(BGd|rOH%P&cOV(8vRcADWsp^!p&rNE!O0K7@ znIBbF44#ha#JMEAS}f1qj^*hj(4)60U2ELxRnI(&hV_`(12ypKjgwlK(*hLvcXY+- z2ZsqX?9-0D#kuca$^Ct?A0T+0SP;K1iQ6QII-ZgrxDhIgth6(f~inT9ZbNW8;tEaB(Tj&*X{o;%KuNUss+`{BLgNvvfCkF>&DXsaIMGKNFf_ z5x^)Hrq1Q* zytn4as}^(t6@ne5<280Fm!Vx!*C)2$<*ks$T(xpIe zU2t2u)g(jZP*Y(1(5yrCZ{=+w7McQWItnydNPnLw$6HsTa~Rz|OIMkSKCP6e?a$$; zX9;%Um3?`BSV5Aw2z`hH1zV2V@W|ocKlKZKH2Ucl>GbK75e>)S{HFhL*W*z=5>~EJ zz*uydyr}&tL>qUt#UpWa=v8OPY~RWUfh!8b(?L#k4F0r2FeyAi&oL?Mwc2{4+*$8b*Q-#SPgL$K! zwws*byFS$$!C>1?U5Wk3Y`eK@nQw#FGJShl$=9ldJfywepv#kJak>4qKHC0pOuZns z0l@S#Xe14tH6<||vNNv2U6DIh0l>0}+JrsJZMSS8Z$I@b`|4rf8wNG%JkuRfat)ts z-)WS=SEH&Nv1m-sv4gfg?5HG3)k|^*$^6_RNtaXrP}?C3Wb5OKT$>?q=bQ4^OiHt; z6|NJ4TTd7Hyj2xmJClCdDHC)3!K1k4Y=^q>u{wTxCqiv#Wz{AZ~;5G2xqVh%J4AI|Dh<(7E$6;RZ2+N_e+{0s#cNVsUlw2;WQ%Czcr%RpzAup;7F#t zFloy0!37X)DdRaTIM#|cVU;Ok9nz~*lVmL!7=OIQWg2Bh)U_grxQ-@eUi0?N0n{16 zgob7SSpjVp*nlFFJglles-0zRT?0^EuA+I#dJTWge${$pd*mABs)v1lbH!;8(0eMl zk5w05{e%z7} zA4$350+qvNKEfj+e`E;apFYxRb;OJrvgzocJjM<&LXxwe#-ZdSa$`~4&61sjo)*EjUin@Vt{KQA|=`Nlf z-6?4a3H@A+q*#qh?V}bHVqz(dtUxe{hcl87|J20*N!0^-EYjt>Rx?``X5Xn5B}r&{ zBRBHr+#9$^-i$2TvJ0ElDuV)DKHUpFE394OdIx3-kx0!2Uh2%q1p>tT8)dmj_zj8$ zO5|zfij>y7nvbkfqJiHqMf#T5vr%0eXC-=P#TXR|bOn=Z&$Q-wB}#E&fHgD?y} zA=BY+p{^glekm}+$MyAB{_fT_3iaka-D6RQTwN8D!wPP(EO66&DNI{{?TB_ zu0A{nBZ3}xN+CXl-#kLiHcp;c-Z8g3g8)bP5j%>Mhd@%ZMCh~7HASugVVCMpDL-+X zE1%|+V#hzv<>T(W3MuuU$X1r-~FL*#xma6t3?s+nHmhq z`BTuI)R-fo`l9VTmT3hsHiXJ6nRaAbaIpNZ=={Q}ar&&!$uDYtWo)r+3jNXx?m$@& zJPVO6_+(8quWmAFZJ1W2o$D{ED2>TXtr;#KJTHKKLi-W#XSVt$jBrIuxg(hkgi4_m ztTmez*xS&S2#AfeewVK8{>^!#GWsChFBDpDNRMT%%}$)x<@YwO#WxV=>pweime9_J zC##Ri8yRcqo>C@HHZl4#F)KnVutA|9>~ag;+(E?*&CHm*cul#XFBSbIDY0LrByt5r zs_^;wkb1xZMUYJo!Xir7k%UB5N4sg7@8osZakERg5q%6um0GK?E{xgJiqueVD&3^} z(wXzB4+Uz_>IGrE1C(s3Z4k%pG(Tw&9c_1Ce=QZZh5n}Mj4t4ldrWFB*3meqywbBU z?TimRX;jhzMj$DQO0#i?gUc?keEOx)bw=5BC`uWcT|f!5M!eBySO{=&BUXmPdLPdc1*mN-SZEkmbb>c9JbRoTLQtI{S}<>K6|F&8$=}Kz_xWWKb!shV`f_>s_6QC zp30zVH_orNDc_pQ+R7pmlfA0=R`eF=tI!MjAh9X9l22|=#!t%ePJb>&bI0QWZZGv} z&+Ja^R=3x3_`0}bP+G^+na*g09i}{LefarVm?WiRW)5~&3>%ZbGKww+V^#m88>auN zz1f;wHPJraq^q8!TObCcR8o|s6Swv>)N8D;F*;&tvb19TyFvG9lPOqEEqT0K zh!&ZuStil2$~604ocB4p1KBRLXUBzIyE$j7kF+$`VA~ez!LImwWWlrQ;U{LJn{S_h z>CZilRK`3L-wK)V3tc1K#$CN8YM?@9Z%RFs|5e_XTXIW;+*L{?(D_ez(<~jW8WQN^NOB%3=H0C21e@8` z3*3zwD+CO^Dhw62IS)M3L;c*9x~^KAs`ZUlg~e>9od_Umr6U7eFagJLcFQ2b<-5d? z$T{LCtQs<-+ds6!i^&g%goO;-$HR8;UvhV837U5E=?}uzzumT@dB1jbw?1z-+s4n- zHp8R%yLed(h}qvxRaB%u>iKgm3po{!*wA5k>~wP_j19u~YQ>N`JP|Et8MSpMY`F57Z{Bz!~b(wMAvf z63iRrT$2RPNeJMl{v1C4C@(*6#13-Yq~@0nz8OFmNM2-8hWWU+#&*JP0VUB2mo4Wl z`Af5$+;(rI@e^uV8g327Idu51)o-nxPov;uq)`%ouKiL$OdW{9ph+uXDmK^4d{tlVR>9Fz>navRb*E_tY)WWwCf zqTscd#8lv@R0X2fi8Y5S>`;c>9)eZmD3s5hG%AF$I!XrD6&uRsYKWyjydI)JSi_3i z9wT=w0XfuntF=!HCDBMdU+Sgse>|*?fViJ0F<4(CF4w*#E!M&)XvJRqDA7IVa$EkpxVJ<*)yO7~j#YHV|!-5}=ooMWC9 zo}#IKg>Ct5l5^z4$_v43zHFty>a^9K6N#zl98&c>NRFT40C6CxJXvruW`ZI7XGT=P zfITsDu7N8|-J2oQC8>}QdDcEGk5DOc;V>G0Fk_Cu&t$0SayEWfL-41`Hr>!S*n)#1oHm}Gk?C zB&3wXrVd~O=g7IaB1c-TIfIR?SkWb!9EdULCL`}Kt>{A?W;BYo9`u%@GWUuZ!zVc$ z%q|g%<~($CB_@Z7vqrA<Ai;k4N=)lij*HI80y$;Gmn*j%{kYia?XKQEk7*nA$mw8r7gC;FGlPQ(b@q0ThGfVxR(VG-Fb>*ut5vqQdoZ zqZ}=8@EiwwImmS_?)o29NjN+*qOU@_PDezJ>+L9N*HRtw=okKidE zPZ6J^>)EQOjnNK>{A{{ff2&TuFE1>N#4WxB?QQK8YvpY`#i9(ao?;93q5?coUj%r4 zZrK@u_3?$4Vg$7Fc?A~oc5g(8KvzB5bg-QfK+3{cCe|WDV8$Y9u|dXO z6Q3Uy4{<3A5q+(m(zc*FE5rsum#!xW1p}nMj4RFo63IMxfkD8J4pGa$($Kc3|8ueQ zFWz6jrlcjPY(mQ(J9NiY3nN}z$SpVmokKUYbii*m^(Wdct4$Ojpa2+t)Sh zy`jFosUy>xZ(g@QEnlwho_*==g?`T2+5W(iufuX@xM`qxML$ZJ=Pt~hrB7PE3WA^k zesGaHj>jyR0|d+l7QX|CHtheR1B(<)6%Gtke28=~Q#kPXPJF(@A%*}$S$#N__4rLI zI?W#dl4iB58!3+DAEnpzY63z${dIj(coDfhUK&PVW}=Vip?xe8}NQXdXf-h&m>5?^NyYovJPtk<6V!0zS%~U?;eDWyX@| za+cea#I5uW=8N<-v?nvF0>gGG0-~&>fNK1z zDYConLN0^61VzD(8>OcLfKn@bXnc_3tP-hyTj%kLV_0pyRuz9_#*#~FIgck;o5IAn zg$s8-^p#(K=e{R2T7!1gPPmQ4X8sZg;$VF zV9cpe6j#Q5NCTtJ&rjgE6>XtWAVEPwFPC1JCSW>+SF;@VlG({y8k_SVjx8K=uD4JT zUmvr^vWD&BHb@&H+>MqUmQgUOj!QpLPAgO3O^bB_Tn^WXYa{*f0dXL*LmY|R>;0;4 zzh}SrIDC{k8h;8t4}Z%2H2e;KHuA1=9*Qj0?OpG^PrgqXjh~Bi9tHkjZVD*qB_mn_ zQGjXQK-d{jM^y@_f~t_B`H;H*k~-Bn)v4YPJ>!P%p9VE6haT^>F@F&^X1lkD9@4J9 zEIVM2W1oX^WCcO-QN4BWP#vtRK!@o)$3nmgZ1wxR z7PhOt>m>EHYH46(c-3@yWO!z{e0G>XPuEM+)59b{ob}e+(6%PJC3;Jg{(2O#{_sZR zKke=9upJJUF|+^*448(2AQmOWt|xp&iy#ysP6`Fgzmvy>Vr@2l<3>0vJ?(Z|Rh6~| zE8M%|gI11?R`|%+AYB8a_oIXzt9q)C2f5lxt0a+A^<0%15n0mR+G}@z>um_f_x$gc zriC$&Jsw}YbVCZ^BQ#RTb0Nr0z2eb_w^4ydZ8<`6R)#cGIy4- zAkLFPGc|IFUP@%i+)A1i2?Y5n90@dm5)Bdx4B8cviedCABo$N0+34tpW;M?e@fAws z8T$W#C>9TRs!R4_G8BBK)rImbKhThwo0@2%f-o0m(F@j8&+AC@OcE#~-7`;!S9{S_ zK(W&_zBDovg=TahN=0Qm`WSnVuwszG6o~H7k6VRtyU~`y?sN>PBKK7)J%rQPPz zPNhe=R=L48sNA6(0*93EaVNPK8|?|+mvmg_jro=YqRHT8kyvzzj4rb?O#w1$auaN7 zl4-jP?5ab-qQ@H;2#yAi25C9CKS%}NOZT!keNScbxP5wgf$hq4?W*OqtEOj0%AGTv z_y^bfN6;f9!W9VP0^uU@)+Ka?PihH1tpJ@tn-r%hu+h>)s20}N8nk177{5#PfEOu%!bQ5R>VS|XUhv>*RSoOZPrF9DADH^^ z$2)rv?bhi|=xKJVL8;k3doIIvZpm&~y7}1P<{OtSy6}^qz~!sH`)$I$7hZpOdBhVR zehI#|WT@D4)6ahXpBVWo5eu)Oj{!f3(&LvPKg9=8q;=qhK)^+s+FWhGFagAqE))O) z{)*t-Bw=9OF+Cm+>H)i?c{sd!fWqq##_`o0;cRps$5iHCC0C%%&!5I#!?f7#gl@3< zM@YlyE0@az{zwR9PfcEw0nxxHIE+Y+o}c$2H&OkI)m0A-80rG2aK|_ra05v69_ASC ze&##O7(?Sf#i2*T9-F|h_xt4-dQRL$PmA0GKP_rdeuxgaTypH=;z2)k8taGh3*|D( z`tZG?JH>8chQ0k_IXnOc{IAlMuo6KMGg1tik)li%UUhZnc^L;DnLsg>Cx_QGX7Umn zwr=oU8|)8l5;xazkhZd%m3LSISF(GlFSC0b_X@kCk5EsF6TVld-@1P%oTvUT?eh+B z1N;zry1mxtxu3buaVUOr&U+}@iv5X2{?y%OT}Cap_Q-uypY?ibgxc%d8$RHB)cUBc z!cSO_*?vm>7d7QLZ}an~IS9DZ95swvJa+rhpdI5_?oP%Jj9>uYs?S^W_6K$a4h7Bx zm_R`K4PHYwcN&Ey2Fq(7ZeBC5@D}kHUn4WS*lkms*DW2uO=L#@jFUA$!WO@x3UQI~@m*7%d^f?!F;XARSv2=CC zx@wvUr7x>yhOuB8E~91| zhH)1E-{`{|Nn%45`HzrF0f+vYJzjK}%|$1A;ja>mrEr-5H<(Mtr0SkjeXq7>tM1vV zdseb%x~#>35H5xlPtl29gfc#sVQj=)!Ets^093d)Ck6uny9a5r_r?c3oSmug^4OsL zVqBb0DST|TI321*NH30%nwqGVvwsJ-Zr!tKce5P$`Gb$W_m>k7y*RT6K4B5UTiVv# zO)Y$R`}Qrj`}e*9;cwrA+%LY@v7w>eybD*gdH|sBuzVTl)Mb~jg4RspVY7+DS~IC7 zNIDc;EaY8j$m4zJb>Z1;FLrE~mrzD^sRxV0pB2SC8{eSCLIAka?h2H~yewXmJUuB) zmQGCz)779ii4%YqgctGe3uK)@jQ~ypH@OC&OU-DSZ9t#Iry+4*$YRfhBoiRF@tSET zUQCXo{oj#{z~yRgnh$ZW;|==rr%qv|jooimW~;y=e(P`S*Qp5a8jtb!*TeA`SD|@Rj>itgo{Ld2PlFbMdM`@& zjmEUs!}6R>K#oy?S3k86CHWQVgE82sSD;`zQ)%dRdxZPlZrFf9{?x!gp0phu)wWcs zwhb+$HG>f~zuyJ1YxKK@T$8TTF4h%pdWL3ct~S+E1@o$D1bLFpccSL4^4SqmW$Wym z8R;y|plm){TRGs>lKx;q3na8)Dgu)Jh6tQrD33xJKraN) zPp2*qkBx1Z82z3t2#*DmAubg>&w7#jKA+_XsYSR^+d!M~^NppLwpnlfp zYq{~&%dbEoVbSa|`u8ZaSO~5J8|fJ`lkx>`u=ksZ18p>D7B*8m8h5OrKqH%Buf9)V zN(*{6Z(DF{VrcU|W}oHm&^_Y5!o7>{Ub=6^7kj=D`i6L*r^1}HjEBa>m+~*InB07N z^VH^Zo25D>kP{02HhHt<-}vsfk_3Wuo7ydbaM#8EUf=5T`K|n@2E7`trFl`@)36!+ zjuLK7yVqViq#f0s(`c;%4^C{*M^RddPBoqQT<;!Li!M>oxu&?k8+M!M zXt#+DbvI#`>n00QAaDA%@o*Q9IvzYlP+$*q!LAD3VmiXzwrm*ohDXCx_=nW50gIe+ z73f57*;p>T8m?|?a<6)pHc)(xp=}YYq773P47knMXB;wUL&Wjia9~$53dIaP+6Vja zvpJEMqt4IA1wZNhk<_jFaFNC4L=M=e$tjp3*bUX?_oZM@YA7|CI-O!tE_`NczOIZq ze=@z8Xi~Q-n~lxp=EG>rEt~PB5xXP5*|qP1WpEj(cr9yDg3uit3jPuWm&)8VoHC;VjaWD0LU6Ye8t{EgX_c!Zcn9 zg;dRmfX_!Kqo_3gOZALOp|~`1ei|?6$49g?8agsEUj?rf!AUWEzP@(HiuD~!8w!zV zNQ9Pz*4mP5$+yt#Wr?0dMr%y0)A}MXvM?3_D+;R=a5*d~V3DO10lm$uBH(JhPk~Fs zWf8bGxjq8dt&es{=yYizSlQC8z!lwvHk0a7@N6X08G%=2S4Y5_^lAkx4Ru9GQ9+-d zzpZUP{(l@b>UWVJI8w&Jb(n-w)0PnsMhk)$*X7O~_ts`{kMsTt_kpPmBB&z3_VciZ zhb10nc_{PH%|kEEvu0a&6Oh7P74FQ$U^v{(c9I^;t!{GTWf0YPNQS3QIMsh`x%NZ2 zXMP5ktY5-44K^OHrI4ho79zj3YDbck=Fl&>3RWkGNy#Ibfb6AvXur_6>D1x72Y#fx zXx2iz_1oHCcx=h?CRsHiL;t?0yzMjJy72t&6?RX7>(A>&80g-*B;UL8`lY$q53@$c z)*p^Pnae-)2D~c$^^IS8!L+c}P@T=fUNJO!!k;MmJqpLr7OQjU>ftT-uWxM=MeTCy z7P&=^UrX)TamT^+myg_WXw&~=>}#Ogwyres0FnSfkO24#fPbJ!5QO+AiKHaTY6K3Y+ov06{o&9;+B-gh5JS?3`U*;9#Uf8 zDghV@N*?C&G&JNh^@dqS8J8f?+cTt(Oj&_G$`BZWR$O_PxB$!Tk&G)BY>_|)(q9>2 z8+4e*Kp==W18)VqnN%Ia0OzKyhi|g&t|4 z=(L>0=JCQR%Xn8k2m`-C_V^RcI_?y@lue$11D*f^o`43v<8e6H*&PxO835J_ADXXFyW)ljxmmllC>*lcAbwxB~;OSn0Y5zi;w zY67vG#wGzgv^AQR-^P?`v8=ygbkEwY>g z9|*6>VQLWj7`L|g*DbRdZQb{o;p->kp#a}yFJBX}dw3?jwyXQj&5`E%<$(`m!-|;J zBGmv2x*uJ-?NjS^eAxcZ^55>%l|(LQ=B4Y=+ArTyt#4aS+)_#Bav{Ed2lD~Pga&(g z5bsz#x_ndzz3dB;CtHK__4eR=2@moRR8Bk`(1EAJ z6T-;$6+F?L9hZJK-ts0nk+idT0AW2|TmXq|j~l;EMofn6p`VjmwzMk4!%oFk`}#cF zZEzwU^U(026s2c6*bv;_yLJNJ>GkHaelmLeGz=0y89i`k&gfAZZbEWJC?hcbL+X$9Bsoc2a|YDPo0$g15R1)a>}eh zO1q+Jbv&Fxn>+kgDu@S>sSfL@%vwPeLeuzGniSO`u%RG7?M2=u;DY1VHK4G?p$2o8 zAox{(PO*zkg~a`O2kO%b8dcDQGO0`}$CQhTTREOR{#Ru1kThf%F&GXkI*=Qq0mxlg zN$7Q^O*}9hr~Dbp!*!pw@kOw5C`^V#s0F5nv_%*ZtFlZ(E7ITFyOQsVrQhmYv!=6i zaLt2i_we#{*Hz+PHl-yB0TgyW3I_%|JJ&2{mh>G>EbiLic695PO1diMCMo*z?aS-Y z5%&=++ZMXGYVPnALd5I~rQuh;bP|+4skDUn^|lcI)DAgP?2G^&n*Hgy%gf{~xN?EK z1y}wjc}p7J;)S=QDUW4>R0{BH62`v6(afYNXf6N~2bcDg)&|oUXSWLrG;K(*{rox@#eJ-7z){&^A z3H_LUQD=0W_*>^L{}$WG;B7tLS^S3#NCwHDYejh+D$Tl&tbJqaB`hNtCW517F#Ld4%<7C3W!M~A*K^36^H{qLv4!6AK?##YgI@xTKfYTD-;I@i&&JVUOe`pUELWiF5j9_ zD$y&Fc7z=6EDkQ~?|<~|^VepxJ$`nlvGXtJ&wZtsA<~B6F>@{fYtlaE_p4;Bs1f*~ zlD$JvlF1%9WF^55(h`L|#wKqPd4h^vbU;e4SxKW7JRw;N84m$06@ou1Q4qvR8>dv- zIHdx*89JyyQiDXABqXO@c|N8Y1@b{)m^_2gN}g(9ISuv_w9?y0F)$)AW@0(}5S57sR*dx5P#H zH$iSl9#V$V{rXUIy?B7XN93w>r=%NtzS`_Z{Sq6Iwxb)Qn{-!J+KG0GZ%DtD+?(W$ z>F2%A^Z$!?$GlM~olK|K(btL|pDc#_nvfJzfwbpFX1nV~_n35(d{fAyipgXueIxB^ zO(fM`g~&gW7~X8)@8|&m3HRf1#Cudz5b#I0&=qmDW-1UvhSy*kkeLBj{)Nm8l}fY! z3a%os3h>lfJf*D!aPb6YZ=Y?k(VTe@1v;L9J*kSD8i3?KHh_+UsV;}*Ck8| z^hE)k6J8hSNnsx1Z?V{nf)p*?L`@8>s9d#5Riw&nWxjH*;vTJ_smgSPu1xed=g=ol zWWM#sL=Ege0DL-(Zm*XP0N)(^lH>aauVjcX!l+l_mcjJHf+Gz=zPLkyBFuBBz|sdw zYff23gE|by7FZStPzMg|hw$71qUO{Zqgu~hN zK_NFqr|>!jP+p#t31z+57TGa`)r{>!lFpd;dlPuCLs{oNh8AVa0->y{Cci>i?`c!k zkqp)rP;DETh{waSA!3cy+8OAmy{JV`-xcHsDrnnVn*}bDM~~j{@!_}MzOADxr(Uys zT|Qn|{-s*kx?EYGjrhcXu0=X!B)K1bfB%oy1_dFUq;;LHta)|$AMejp1AH!rBB59f zeQf#MSigdDIoTJ>e2}?zW?m zXaPKa+`{qbDhTBm9z%myK+`zdPyCVuGo6f3 zZk8ihiRD*TLqS)Aqqfl8uhus#4-HkcKw8lXGLqeozBjz1KS_WPV;|i>Kt7q7JYCEa z)J_+-YG#U!5es^Zc)epX`O)R=BVFw|qzis(_+h&UEfRkHt|B?B*xk3jeb)N+S?ft= z7@W0!c*8I`Z<=BBRq67QY3FLlD8N82$K&Q z`XqQ;A29F;gS#&Q9rqE#9!~9}$#Ig1)+dubWYUKWv?-?#y{i?`e%|Rr?p$G~oB9{$>s2hRLg8@@jYl-^Z^`J3;bYec&2_=03+qLMrc&qkVn5MgTWJ<4AjK z;mYsG(UFm{U9BM{uk0m!v6f+4ePtRlo&sT(XS-uGF>{F)Ms^CpF<5>?6WGxTz^*|n(UkHqx+yOx^?GpZe%3A zb#yB|wRL(cO>LF7(p&L}KNXJFw@&ODo1<^W^WY(64(<6Yfy67kr%T{#IPVmL*8?_& z1T6eFME)}T!~qIc{DClr&F^5>^;FfU zdjm-6>Al^`2HtQF&T;&Sy*@0I3JGA;$b3Z@QGJd#;$QJew5BE!!(iFi72UJa%&u}) zTm_|TXmn4o{71DN4@5rp=$1|UGf_X^`+?=b(3(t)cg4*ejk`u@Ix?_+xqGDPb7xB1 zdK)*E)$T3JYleC>0=P{Pg^TosJz~Cd&+bod*|L4$f#px^(4*;GE+%E=QS_&i6}z#~ zS6tpg92$6%y8*X#+sRVja^&XTcrF)TvmM>?m68L@vHlY%iuu1-|J4}RYW>$B`mc*1 z^KP;aaAGtIilhRsvdNsq5k=O@7baSSixNj;D{n;jLjt)&FOGu8KM$uzsU&%Gl5~_L zT_i2yXS4`}x7twMa=;>~yxYo?z#5T8W#sU=tXINIF{_k}=Vw614usa)E z1GGk0W3(~Rm~2cp++`QCNqq{xnr(Py8|NDIYy(Z;hWQ4QU!18@N(<*U5irdh=2lszwIXNCp z;<34554!1_8#OXAX*@BUXU^b}DN;$gylQMFFc5W!wggZ*_NWBZenE4=XoG`Lu|v@4 zBB!}x2IrFu%9(*3s-)vxxk9(`!f`lHgEz-P3N=@l$Y@xQ2!@mhfOHYO0acePB?t~} zhD>=Nj(E zgf26aMlVEupwV_3ZQ|Nj6C{fn6|wEI4GXUeorC?TYr-|@ns&{)Jgy4@ghXv&2AgLq zOc7wYgHyY>WwvnRmf6t{-u$GH+Vo`FwdsbNcRdTdet2jhJlqhb+Us`xEu}F%l#2>8 zJ#R^G#aF$=v(Q-Uh9DIZC4)vFPaBCmZ+P;u7}lvI(sUH{;+n$ZTF5WyD9+$r#23>k z)s1&8d%yihcbh^gW(W@t+EOD)`ChJ<2@^ZhUOq$%gbMB)J*KTLeOB zJ|~n0v3W`&G)ju)Vr1bWu0Q1s>BFK=d^ z#0=pFO0#82p@h+d}G*59Vehs$7dBh0h2;%0AGv`t_CiE@W~}d z*P<1^*tr&AtO!#7AioBrxhZXuJ|sONyB;c`HKn07TS^})-6`K$x|_RCzOVEr+&9^` zxZivIU2AsL#_D_PE_)5Ca!kPrhA_NSALl zRSEQTr}=4~p5UkWCwPW`Tc?RDGp_5SK%bty zj;;8snEWl{xm8Pwfr(VyZ?AI5p5p@^nb(j8TiUNg_^c)@*~QT69&&$L*+KNU~~yEgEV0 z4M(>9>4*0}Jo%l?y@j4wbIY=>_L-rGluaoHs(S+;zj4nsAN;VrtE-x0ng?FF@7BG4 z^5ey$hazHm`K?=ODZ@ZfU-us7wy`cHaA^5E`?3SOuK(zn|GxivB?x@s<;^Y%%YKq7 z((YBVujPsA%}2;G9uW^>Q!Vd60BkA35e{e$DT=Iu1I}}Tq_EjQoB;v%(~dL1me{06 zOa-%s;<3hpJ~rUU85Xgj)$V|KBH@mZ&Br@ICC58KZsVOGvzn--c1R2=6H`G~%+WG! zm#51ArEsk5>e9M0L!JG_ZIZ3onQfgLi@U^8Z7emKxw&(9ai4UXc3Wm&=L6Dy?NDle z=1}po+MgG{CVpA_TI$Q0zwG>a@$u-lw7*V#w|FM{J$wbP6)zOuD|YJTyN$aGM?zl- zeI-0!W^W9k4lZD&ShM95h$~_$&15wT!EI%Yq{6bEKs-*R(*Y3EDwRglh@L=G=n2Fi zj(9NM&UZ_Z(FpxqfUqj?&ek`Yd#p$M4;dm zxC6z5uaqcanON#@jsHRvaT+`yAm30SOulrw(^};nmKF$;Z+r#y#_fIBOci_R9?_<4 z@iQ*g?nJvgQ5v);v5Ozdnx;OSPnl~e-q$IIbqTqYDTw$;0usg;hGMbhyW4|o5Asyf zov4$NbLn(iM^pN=PE)#sE#Z0nobJ{qtZ%L4(>b>6f%E&Fz@M~uU~yb-WwcVO0)PiF zBE@R)cvMk|m??ZgnBzhx?qUj#3F@qXgd)A|yoa_m8C!{cU*#7ax%U4?v7CHa+{}v(k^9 z*?dKUdNQKPxjLh%V8u;$cC$`5YH*7i-Q~XDO}mXwc5MpnN&SZuoic(x1XuWut${>| zVy#Ex0>m~mgHV`et*13>FF54_u)W7&lNJ`nhol9P=h(^|v%Dp>L7I zaHuq1`s4D!(h+ooKEg~zrnIT}RN|5Hk%}MIPT?7^;7RbM7dw8C-nv26I`z&n23qaHr6Jqbk+TZi<30ReMlX z6-Z_ZN2~C~(mx6!6a+%sZ-B4Jgl*rXh$h5Iaav@=In-;brlwZX9O6ndCYm7W;0)x3 z)8^Air+Se7oe!~h5deCdm)oCXC;&I16QqOf~&1;@rv&C*Qw%|v#MmlO6!vBVbgLZfjkzMpkSe3 ziY>Sks6APLMG31~yvM#Py(_<0aE*_l17x|9UA8)dX6PAa#`jhKbYxnaj!!2Z>-b7` zrYv9xUj#-RF#T*_HCxU7S?Or*XvsYe&vvxsg042b1+|H6zDeUP?$F3J!o1*{72H%J zo3|-Qso+pRhaHBo$Ky>>sLh;1#?Ly1fQwV1W=RP-eFdGq#325NApVG8v!n;%Jr`|J z#HWc(M)Kp&@k0j}?Vuljjvt?cH$^7fA11|K{fC?^*|E6or|F8OMJwH`%6Gcw*f&n@ z?PP-~Tv;`Dwijk`mSky7k+Enj)=u(*g*D|`JMA!Mu3nCXp3da&{qXu7dV2TgfB5V@ zH|@`9Jz^E<0g(JH<@4EA;*>GAy-Of z)=)d{8VV&7s~k%t=VfT=-1zvsbXHmzZ*y0$FvQPLetZGLLUT9TP18fk$K=P<=OWKV z=hQb&^ZB>wu5 zwo$X>q_U4+8V6GDCCF%BI`70+@uBlbhV|nCCpzmyg0*r0jFnSygmoKo5!{D7B1iJN z#g^<6uBo#UWF#vST-R44s4ou;_g=D`_U2b!sTDHU$YyqGZDm*IU-aEwj#;knFaMAA zOMf$Vja9hq_S){-=|9Ru@7S3C2s~2y@_A;7`66Y|yH?3x)Fhr)u4PL2^g=88(!ykV zs%1r=clbBDMo!d%B+?UX3udrwZ{x}(LL>|3+U9jYF+4s!pm>s{fRBZIm8ZbG=6I_5 zvm)e1IA+I%HVuaqke9d$%k5;HL>`9YeY#Hxy1@9!{rG?!*A=6-SC14O^Qj(?B zKqgp1x(V~otm&%z`qBgX#`H$rt#P4kU|nanr3^F6nP`|zacgy-k>uvkIy=NuhJmL% zxSs&;<9)siNj3~nvj~Z363w8OkqZ%uWl+^}!Qg0UIz&(5-E4>f@zh%aip4Yk{GqF{ zBA)6X2a!DbL250eL)Of0uAoJr`@|EXoDj7HCCPCqnP@NW2=q!2+2c8~PR{AQ*l6T^ zf#e(~9LE;6L-naSSaMZbVOv}=d}JG$mJrJjjc0BbGtsmeSpIeS6A!H0y1$g@+lYq8 zhKe8G(!81Z;?hrMNHXE`Q`e4tZVEj%+!IH}($T5W-VvI;zK=E_ii%}<5zDep|LTe? zpYnPsE$AUnE0FP~w9n7G!%A&*RK zTRG^VNek8(I;rT;&)MRYe}_0<{n=TGL%?D?rYp*-v6Y1H9phvU#Z8rZ+gF> zenWda{ypv~?_dnP{L@e}1;{$1rgtvusJ9fX}YQ72`w+bL6)Q*PMcl&0yF z*{oB_GAV6aP1MDX2dD$+06pn`K>xJ+v+|J+?*Lcl>q;~Jf@ePSQ;mI?e?&Q~GJU~~ z3LR3ysSp*{Q&fkX(FCW*moR`Bjj`g@*E)GM(P!Pj6%A=`1 zWe(v9Z#R!5KF81Sr}!W9ZvLP*4#FDqQe7sPKd#}x0O;s zxFS6bC{7Cv$~%uuyE*jSDX9ZZbvWM!J`2iIVyK=0Rjo=zY{Xw8Pc_h%ls8~$Amx&_ z2;~7~ab;m3X%Y1G;nspc?iLB=Uwdc*S@~yte;b*QAyPz;jPMk%NBu)s$-Q|Rm)@KY z2%WEEw8R7boewoR9m6HOwOB65aj)8buSNwunopO*4CO3Ti{;JXECZ5(Y`pgRKA{kC zN0AkOJlJAGS+W3-DWfM6rWN_gD>2U3QAfpkIGb30##%lTEu`fh=8HyN&vq?)Xn%h) z;1zv_;gVD9-~Tn^?yX8*4x)4Z%jexE@wh5EnN6*6mCvN)09^vtXn^wO70y*K(jL(R z17c{XT8%ZOrE~aie%0tYL*?3XJ8vG(*PRW%*YN);EMPv%ScW`29@+p%ve-_CPBqc7KI zZlgxfR(2D&A-CS#TG(nm%uY#D@}CQh36_S8ZQ=JHC`>8 ztE4Hni}gfYF|Waz-h#(!#IBRBlSkb*usf6+tVeuDq=%J9)JL)p8xQBFDzU@fhhm5G zjNdzk?v?J9U3m21pVTmT#NzQI#pINvXEQ0CvPvmR3SeA>=T%8cr*)m8p%GCfd6 z{0NV&z$Z`6Qy;OikS!Lm>dWdsVg2~|Too&V_-S1m_P4YEZqjfAKCGPAFpPt({WulP;mQHL zYcE{n)E6Dg5bFs?UjTcKYby?JbxZNze(uWZJu7j8+@Q0l7#erNiAAhfK&#|onO88S zAW<&_8YKywho*)>wWm**}OCkSmo)mzMq+efRKSEWj8B6O*#x$kIFLy#7X7hom97 zKR;Ax*1HBa`L;>hLl+`}I79g+^ohoXmKUt(vZ8Ts4FGx=1& zElQ#+meNvMPM0j+id6@AX<)ne=HAhPHk&nvulv4!)DJiEi7KjA@^yuGQ5Cp{R3({g zRw@I{wgRkHo%MK$2Ap^Ha8IAkl}fW#e#f%R+9>|U4>nN4?Pd*?WpfQ9+-Mj=)HJ(l zLbz5F@UO3Ug;>orR9}BJukegeXB!DnswJ=Y+U;TjCDQmesaD_z!5~4FVdk%tbvWuO zqjEWwO7a2*y-)3nqG-jKLxB^zid5)AkQ%l+tG=#YR9(;j^S=5F-AmOd7Jck^qhexx za)PR%+B5X`;h~50))SetBzzLl*&D{Peg8O9H%(iyl~=%#RQFNeg&n@;Yq) zkFhTSbK|P>e6LDp=_r+?l1eJ6bX1jYsdTHQ?v~VdyWO_iP22G$+p*&##)%J14ksqe zB#sl3;BW;dsxln+~>GHn7!L)>)0C4`XoQ@x17txvKFdgFb9Ek3Z7EF5I0XCCO=ZyIb?~TNy4&NRrm+ zzw_#qgB3h3sxwah2FbZRv0;UrLsoP$L4v!YFzQF1jT#rn**oVLbrVk$d5%yw3A~wC zH}e-3ou1@E@|Wn{P%gC=1z=C^2*xDvI~n459_EqV33TFdd=1*^EC5g}ESX$CY0V=) zSCur~#~xp|t)AQTD&VjFKj69gYv4s{hO+gB4?b)p-oPu*p>o4VU`r}r3tSL$KvVb2%9 z7mT0xK9PI{yk7jNPDB<_GMP#wD5!cA9_)ss@;x^sVhDP?-fV($q93G^N!0;KCeSGf zuTeu8QfTc#c8)>pxgzTHp;mOaQAMo!>7v{1^kGR@e=A-ce?0;?X~38o(o1taP~`Y4ju7N5?vO_#g3@-Dt8P_ks&q8&_^W`Os)8QE<`q&1Z-g0~yh5 zGlOoW2J#?6JPUuM&Z3~$4H`~!Msvn;#`=`zf<}MF4K41ET5{zXu!){wNnYc&I_T}1 zS^90w>$JXyuU6`~HTKtt4z(%vO`#%f#w|L~Z25oBPQ<%3vu{3edFP zIFQc!B1h)#xVee$*#m%PQ2R;Hg#V&S^F@~!@-M`7E*y=v09}7E4X7X$pxrc1W5h18 z`dm|uVA5JO-&Kq2V@YaUU7a*as~sK!ZuJ*#b*!gb4ZXYLayMa3hi3r!CpD6ZHh!T# z$(H5ep=;)(Ssux$J~LJZR!mW|;jnVJNK7fs@+Xu}l%GTg@7YtBFDyL{|w4^zseGc)i_wexdhKb>(TZ$Z#Ps@b- z>8NLD_yXMeV(95^nj_*Cwb*m7n1;$W>KJZ38x7{4g@q$bcf1s49$a9h8{~8iG!m(L zW3WN!l97Q_GYA@q{-B!kkx)lz;D_e?`C<{IDJtEMfc?_JG;qbDxb!7;5X5wTlYtC| z)BsIzF{!+ujx1VCb`0e!cz(4-lcGB!``SWI-HB?{d1_p}XYbMOL^a6zfQR6HheN4S ztJAm3=cEkj<8PadzEm*LF-zn1=bzv6+KG*ij-&(OY&9hN5`{euFZo$ryLPC7Op2ti z5B{{}pqchF2ZJ2rlcY5d5Ob@~U%I2ZIU3H0>lK!sElr-qBR<^-r45AA2q?lIVw_%p z`IB9&zT{4D9I)btN{kL-z*>vCbb~eu>@o3rtIoApp7?G#99^+Yq6jXdLP90()3n97I!*^ZPBF6l!+(H@NgN1 z4VJTl;lXeuE(D`t;;f*5Q1r%yKvZNbsid1EIBz7DaHkU&V6<6CU*=clJ4Ag)dzC0|)8=d6|b1SlAwbsZzH~i zOopeNLSN!rTd=-YXEz|911{LVEEAhKr7ryO(1|ta!4s zGSlqeuFo{%+}EV~1Er8;3XPv!vUT&#UVNN`YJR;Z0AoO$zn?E2=APqz&5=QFh9jD2 zv6Fk8({Q~Uf3BC~&-HTr)w5)ex44{vHYKuA%aBuGM=g36(5pFU7AUF7XC9otuO^>8 zZvM5Iz!_+S`2}|`_Y4mcUKr?A;&DqB(`#ArMKkL%}=jmrldDHiJug}RRpL@Sd zd5puJ|{Aj$FI28M_ZSDBZ1+ zv9c`n#&olpPB$CcDqi6aqWIO9p$xwYe+ZCzeH9Soc>u{5kZdKS#^|#~JO-%jZDwLFfE*`8uHUG8|t%$M8p-V*tN<*|6dq+aGa` z4gBh5TlX9r*bBC3wrbV_J)n`i4S*<6Km|0w1Xu@d0z1Gya0uK9?gQU#?%scJW^QiV zrn~O#8#sI{mfCrXG(BlHj5amEfZo2KG?0>{)Bw53SIjzXHqN(p>W-sFckLY?{@C$y z{`LcQ*X%|@SF3MC@9_35f_KaD16#HnI8N>jQ`ST#6AAAH%3GJJ%B9!U>}Fh(`*r*X z6W1cQUBa6yKkDzG(p}pwcYlA~k8k)r>H()1E)@&;Xm9Q4t=+x#;`jRN_OD-G|GnOI z?Y?~7|6jZw`9`)_%zhRxepkp9auVL^RPyNcwL&geAZGEh?ZpR(`(AmX@(HxaqP@SMy4#J&CqHm(3Q5 zVDCpAJ=*v=KJ9-Nv&9VBKuE%~VSEh9!T`8Uk9Vfh+mqCsy8A0a`m`wYRSjeZvUIX{Af1J`Pp`AQB-y{e`!vg!!Uc;l zo?4%J9M88<>t%JVc;)c^?MT)iAb$jSkbB8XJIzpc0j_FNL1)%UIK7bPFOUmo%`|Vp zUfDeQgG#IY)@!!6UPAz4!&ahOR0WHV^O_!5GMI3~(hx63bx{dPaDA>Jj9c7o%B^vFEd8NP<6l4iB=G*i0^No4Dg3A0) zUbD65FJc;^13$*o=)9(eT?$IeUTtBs2Ig`3C@Ud`GlVMWQNOV2h>#V16S`&(Ta|T9 z0iVd$No_cz>7ErT73)Mrr_g^O7eL4oN9rsU9%GKPLqH3@#3Xp zDWN zw{Hh`?%uoC(52GCB}65aOkMJ*bShj=C?xq>EnE9yK|$1-$H&y*Fecfh6$I0%^vaO9 z=W%%jHl&twMPza>0lMcBAj)+ef)Ai1fhp-Omsb$7Sgu$J&9}$)kBjMm-Q$x3v_BN} zcuQ%CvRdGM)d?nP3e=FuOIAackaN8LwlUZ!QysG?6$13Nh;m-F}ozq6-!b<4@R@2aBg&N^p<1W_ujH?gDCC0W&76E_5QV(w1I7d)0AJn{q{`U z;c`Jurc^4Jy!Mb;wsUb& zl#_gyG!*Ud@oP735OwiV)%{6cb%59zC!az5y9MlP<{n%J_pQ5g9r1A%vL^rONjNz)1SP+} zxGgEpNyT)zQWT5X3YgPP%cBDzk;dWdB;~$r)6t-DX#r~qw8SiVjp$s+V_rnkGnZ|( zkZs%*mZe3`aTS*IN9a0kTjsAtj_pcVy~?nNgyb`&9S_W8M$;~a0-R~i7{Ii|oVq)cVT?xZ0p{cT;r{K1PY=Sua(PZVoIMPg!_48( zqn!PIDa4CXD8zv5ho+?mbO$c)-b|m$+;K-WcS4TGN8}vfDZN^q+G|95dA>8R@`|!x zQ>7#B|M~gC`^ZGw5*6?Wc9l^I_-o84f3%1ze30x!q$vC$!ourhBl+wfCGSprkOZxB zeYG;1EbTanh@V;}z7lqeJ>nN`6qV%EA1VJnK%yer{y~VqjW^0o0#E>GIPzbBjO6CU z<}u_HM7tXA*TYV{CiwM{J{X`xmdi$rB_jOPs;Ls_-ZFA1`Z!VcQM?DQkMnglnuwN;St*@>%10$2OUNTSvuaWX?x_z(eBp_{yvv2AQ@SK z1t^ev_) za}_UB{qSZ!M@t@MI6*{nqbaB8R|WvU~D~<^Vu}^{ZMgs(bK^Q`A z!O&7cg(2Go)T8XW6ifvJ5WU?dIoD`xtioyz!RbY}9K|&o8U=(N++V2Um;&+;x)l*F z6;y*7xte&`T?>jCa#0aG1r2_Hg`nfMYM14O%Z*^9$`SInlAd-2Sbk_?N?LtlOJ1x_ z&8!R8?yYg6&utYAK4qkJe9bN2cx=P$Bd^@LW>1(C?37l+FtkP_o^T{mDHophj< zdh3qI_6$epSb>ekQXZ$RHa1?9rcSNt!LBC% zNE+7Cu$p=}Mbx73K@*%Xz;P0e5m1Z!C16Vg6OSjJOlT4bSJ}@LC6^$|j6hRGMfM3` zCCH$%@RpiO)LOs_u=`qc8!(PmI#5vU1x*)3=xf69#E%~zw0Qh&;CHkkWA@NFeJPlRR2r zx;v^KQZb-k*(YS4lC;^8HR137U9IWYV9Ul38m^uv*>rq1KQ}SpmHOMpENZXbTS$^` z{(N**Sl2nur~3SzC!}iJ)j2ODd9x)sGkR4DC{-`TCtXJ%D`xjSmdrg6T8CXrgaVy+UVy1@x_$7&Na**SO zOc>R6%Mf@V1*mW3SRcC1PI!Leq)&gGK)Mkg%+zdtpYkETjezaXW6ObY=)5!MN( zg)@S#(cAc*pcMqUlsD=GeGy^dyySL91(%Giv2N@D+hn+7jx1n2ytT*z^X=XmA)?f! z`HfMO5PKss*slLFq7w$o@PC);LCPE+%y)u(DaCjE`e38l@l+ZCyIyI%wZ`APB5`3*x$70{)mS~j;%ob^zC-Lp*H%EHX=Vdapp(*3kQNkl?U%gX@MRlTH+e<9 z`sMaz+gmt}*}a}qr^9me8ryS)@mUHHJBwAXMq@HLs>7>9cQr4XoPGzx*=YmA8(EGb zjG;ye{*XK?osKqI9?*}b%A^(pl*A*0?>We4gl&2e#P$rK+~qf0U`2z)qPEn%arQt_YNJ0BaJ<4Yp#wr zB52J{Yv&bhV5Hs&W5TK=ek)mzJ0N2U`dO#ft0mtQ39Hbzv3{)cSEFm9CQ_38R)f{% zV&K-!>*7F)lO&(gA0S4Afvg~krqE!a^D79bpeWN;yO9)ocqG4$dN>S9O~%34?OrL! zdc#`D8w9u#@4`8|NoEBH>ZgVFMHLzCMe%#7umq1KtwLVYR>C+q8q^WUh5iB8n4FfS zo3315YR~^6&$7u;rTrOq$Z6EaHm>cwCkaZwpBM=ZD!h7kVMPD`KzEl1|FyY)3lATJ z@J^&TygVsp7)Ub#hF~~aW0X7u$0duoNHQcMkJ}~^qVKN&JPQ645W4{!1#&lC~s~jM!;WV7!o*7|QSS_9DS3^8j1H2IwC~x8_@u_Amd(!ljQ-wUh5m|>kQ;d9*w5+La6hC z#uja6JEtoByq5G1RLM8nDdG*$mmiC@|2aC6_eoNyvMNs8bmbM&*M7R#6wL?$nF>!( z1DmQou~#=+^BC&CDEKeUn~ik#^8nrh9s$H=0AmaUjGd7na6+Je9Ks<8Z7>M4kc7}4 zkMg0c%>zBO9UAN$FL;=f-M+VZC`vCFLkH}~?Zj3)oV3HL9oi!TFsdE*#)8^Kd1UK1 z-awn!=!eSo*2Px)vKp#DUtT`db9q`Vu8rMecnxoe*Rl=LbR{SfU$lf9Id~{oj<908 zVhRtHIuBI`e40=;9`~ZosltV>tTp&6nJYgc_3iKD_zhCOxrr39PfWPO(dGx@VH3!CIhSWM^qkAWZZyrl|jIh1`+(T>EJ^IbV z2VZ?;hRvT%;Xc3t$nw4J!JAD2niDM<5qtI=utC7 zn-WwAMe$czT9*7G=a&;QZ8jHKPH<6`qAad3vfJ}rN3Q3_U0gJ_Vb5t(tC%9HRoEL% z?InK=e#1StYww4OgWHdm8oRPaacrR74%Zc)3Xj&{MnfQybS767{L#Uwusq=;-y~_V zv1?*--;h9?I=`0(;w&C5>TlGDsmxH^A$F1)%AgDN&*mB%@~mEu=^O1_(F75FLtqfR z+nk=L!O0R_m4XM7$CJdK=trZ(codeRa4Y~T0eF)DC;YJN+UO$6cDRw*%iO_`B?eY4 zn=Q9m?zU)ZX1G}gA0m(`=fuRXWdFnmKjMSCd~n7GhkP*WgF2tf7x9s&No4ljMG`*J zM~1(geLhRrvcc@*Su&e#gz>q~Z&L6%3jPHJ4^j6~rzw)E$VlV@a6QL1-Bpmc4>cRp#lOU@_AIuymEV?U$-g z$Y9ajT1Kp*PV$Vqiopi9z1j`DsCgzKsMGZTe4V!L8afE!nyzYxE>{_Q){BrPeVxx) z9gM>)By)~VywC`ls8FK_p9!v6KP$&de5zbltPr{r$*jLUY)@oMej(|z`pat+U)ct~ zi#?&vZ>x2`R+5~l(JT>F#!{@Pp#!{sTYqM}C~;Oz=R3Zn;M7U*_sBW6TIpn=;z>}^gTFh^m|d$8R9$#(2)b%328%?oSYI2Hqfs+HHdp@|coh&I1D^!M z4sZw%831Vzz~Kl+{{RDLnO)3LhAc9pNVO^>e-eW;F&K(L5VOUISgfi%QiZK5?60n_ z5>;L=k4#Tbj$jBYG9QTqdF;dB?p^d^TIwiM6~Byqci;wAL;e418hqhIfEn^9B`i|Mi zP@m$odl-{O6zP1ezaVR+v0^Y#oh{~XUYixi_Kn8s*$Ct2OcCPQP7ecVhkxmtOix18haBx-QQg= zxv@P%sq0%ruJ>`et}T8_)HRjwtu+HWyF0Y)mff)*e2>EZ#Lvo&pn;%+{aZ)E(-RHW z?lK`sTFWBqn9q&GI>-C(j#npQ)^D7LPj$@$HZ_*(++?W?k5y$0p9=EjO2rSq5-dku zs?9{9_G?s&_<`nlg@-!c#YcG3ZKxO!ljsZviT68s1I7D8A-|vZGK_)Zyaq3o<-AS~ zeeiW4Vr001`U6Mqko-|y3q0su4F;k9k(DKE7KzZ|bw=Ioa|Gjy1vrz3 z6GfqaL%HoF#|wSD276iFtMBNLc?s&^d(HdrvcN)E&^8)wHSFgq<0tQRn*3GiV8JX?Tw65y)X+SqZi%+9g+5P#f1RuE@v zL5wAE_%0`g9brdwO2IIjpiarjo!I_ywk$V6ylvOWaAz=CMPM$JxlH$;(VuLJuIu z3d1O{3-hv%leYGPD2O<{(2P7APt zp6~^;Bo}xh7q?EC78*(>Cks41F^3F?bTTW3BOtQ~j)2RK_2&pUB728)2Rny!E?X(6 z7K|1!rNH(MxB#@09Dy5$Bk=a-aKHwNoyjM&NZSA|n>&ughm+8C)oi?!jgxFHJ1C2E z%gQ1_>gM8HZhC%hZdO)$N(z~dr>Ca~x^hFvFivPPJ1d9d!^RyvIbLrBGc17}5}0TE z#Zq@P=)*|UjSc9UF%G+Tqc3MF)}76-JGNYQbaZqEI|_7f1o;p*xy&=6#bs~^Sh>=B zZThYV$RX~g8_Nj^OytccZXq03mV=*n`9ynndn*SIZ#LfDr)ySW%=?F0CFeWOBV?RE9_bhCAIv1Q?b zfo`$AL@S#2XXb_2FyI{(`1T3nw;gr_m+R!{!e+q-Ky5mTdz{820!0a2=wEoB${MYL z>QjWSu6DpX)@(a#dJPm469E=H5o|jjU$(Cu>EjEw`M&P%K7qbL!4VO@uEAVR5LoQ^ zcv$0ht{k_vo`6gAM3ia|#k5K;UQUPmp%!a8)tDO=j*a+uv%@kgruq3;95pC5u(@1s zcgOv!{d^t016=T~UZLS6ahK&M?Aq+bPY$$ee&Fd(g!;pTW8>!H8S2@*5BG?S1N%od z6K=_p_{}qJ1`1<1K|wCzqS$@-oWQ6Kw$LUC=uOyXz+SaDCF(;P+{Vh<8uPKmZEW!( z9Jj?WJcw=U#%9|BW4Xf<3br*K%x1gT8Yjqu;D4AP4xvBSYKbampy{Dl_#PcRfIgeR z1JLB?$~@N87aw1!OzL1467s5)v9$TSygspBc0s<(Gp`f@NqJGtzcFXJME6X_?cnoL z=+w@lkr86wfWc#gbL(<3w-g&33--5mPrq^}wyzm^E&_5%0Acx4^o+>UVKBeEb5BZi+eRbT}p&9;d{%UW1}X;XC44 z!S>!EKFw?V!@O*)J;9XXX7PE`7{35VroX?1GvR}O8zr!{@dc9BVmN++ahqx7kb_}O z63l81W(}6bad2i!#xScF79+4?tU9Gj&z|)`8V zZ^?CZ>lqj0XqU$k*cY%f(|7^FRvp|e133i2VSqJWG+a`n;*kf1>HI zbF5QSjOpnhpd1XcK(l|S7Cv(aAGo#zE(u78b&gMHc?~pm#}h38S|0GF-pc1`_F6vH z3eV1pyfYnw3!Z!~*?zv6%}L2zcUwDFx7dsbx0jDc_=g7v3;gvgI|tW_et0s!NnaZnSlF-IvgR#C z0d}qqRzX2*S3g(yZ`e(bB%^$M@|%4Jx_de~1_im=cJM92Fsug+>+!DdfWZhJyc-ys z8Jk&t7H2HZSgx|#YrO}8O*uORzyUk=7O-FIU~rsBfzz4R;PS*R%w6ko*Xs|TBHvS- z5B(nogar-{8WQp}bXT}d#2DVe$fu;9zc$ueuqVMGu_5VY7sr1)P_>1wTaq9Bz0hrg z3GTK-JG4VP{B20F{7Zmnhj#cEgrpQ{il!afp&i&5t(F0;&VEHE#|ExK%p zahXOsR@e{|jx|OyNxE!&D9!-~-7|-2< zF0;&VEHFFwRJv@5IlJf4Wh<<+3C9}qay>^1P+f062C|d*zmF>*h9bh@a4VB@GCmbSzP}vofT_R#o+06{! z9qCAwJe*j@WrScj>-d3ITDpMuxxE6zqJ`JJF|8>qb^G@ z0*k>&^dT@QR*osbZ!V_AG~i>vYOyNRk_KuzP=?a~D&HK0QPx>`U}g~>r#4$4*F zn$E!?sa4YF-#)L)4(lvpL;2`#Na-vlj@5tl&ide8#xS7q{GiyrX00Z5?&J@vo} zQc$WyeF*dp!y05!EohUWew1D^BqfXq1(E@3QGxU={fFYIEoRc+lmkXu8L*lf@g}fP zKtqM})S9dth7}=;>P=cF0A3`MgPH zrgf@NT><()>+V^t2QSJGpoWsO6i8BI;cOsG-Dcl#EDvtFwi9e6v;%ti_{%cxuBnKyO( z6M887s!%^8_S>@?uT6F{+P-aXHCiXC+URYRt7Wy-GHP08BBKv^DBo)lmr_$*X%-kp|!&6$C&or%L zy2TPT$c~l&pdXF10bC7gXv-+gBv7Dl$g{;Rg8+uyZ0<1pNj|1BzXEAsobi%cWpNpx zs6`p)?dxl5?Pr{2TI^$-$y#h=o>N-)(xY5K`KpB0`|WeC>>VD}nY7o_D@p?zziK1_ zyly_D|DzFLoZqrA5xSa>Wq?{S2H$K7Te3kL0kWtVTqy#zbkLd(S~>y@34OgI@>DUJ zhq3@(A)4bT8e;IB2kO01j?2IZREM=506!1Vf_+6;FT_Ixs7Vl}7}3rJtvTRVMB~98 zX`rPL)L}UjWkHHx9_U5w7P9F%k&;vj+6j|}t)Uq(cU{PU#{-5*-nhUDOtp3)LPrF7+r6{qVyBe-)YEl`KU&DBMsH0$XDCu*JF^WVH!V)5lC8h|;K+#J{Z;VcgN4}XP6hG)&b6Jd0P5j9ip{N+I7t)@7 z(=O2JLSz#tm&C;RU6iAiz*mQmgtS~qxVwsj3pSN-k3qG)I7=1xyLO zwpu4wf}c`DjZCK`sx=CwjxazyvZX|hO0LxCm7NK_Qb{N)OOy(QQbDMxRzjiF%XO+M zs378@P#R<^wLVI!s#NNUJitY)t(0jXFBxIb$rQ>;nXZD+mcH9g#>S+!G{nlXYPC#9 zgyyQ`IxQ48tVpTTL)HmVBp;yzY=m4O$(5R@BeSOKWHl;H8IfOF3S=iD35h|bQ7dbK z5IPmG8jmPa$qhh-9GOm`)EEeUVr-0w6QQrJs#2?f=A~MVA&Tg&ttKjEwL~?rfdN_@ zHWLOdA=fEo1|^SBsPt9913W^eQ4m!+6}Tz~D3GvBPgE&&l`4Y)kSnQ0mNwec04@Nx z=!}I@$bkockv~l0ROz&eYPo?&z&HYZd9aU>fl5Qvl&j?BW|GwarYenGU9EsYXOv#6 zQP&cosxZp0X6S&zpGixF0ko!0sW<3=-GCEX7{Kl(TAh&vLREl^L0JjCtWyE53T=%> zt(7TSTTe!r3WOg}M+;bi?`lI82xEm3stIw*mFlY276n5~Q%j>mhX8`WKIN(s6_7c~ z-X4ZpsaCDlq5z;x%_B-=dLXM-V+wVn6GF=khN`6KXr(5qMpdDzQYuukD6Ot68rGr# z<^Xz7gaP-Wu+T%nAepz1;w=jdXka z0_Yq)8U;XaU;-uRRi={x%P4q6sSb=<5Iyp8nXU}z3~dZ-1w0D66WS6mS~bupGL&hI zarXCX0R@uj^;)?~2IE7al~-2+kI1Nmpi%?NghGm~v?U~TsyPvcBvL2=K^5hPx8e~s zDnmJJHVZc%9d1y1<7Kr9#3{ua($Z1M7H~j=2x`b9Dzyq#Dg0F;gH%-mh4kfUzyi`G z)o}3YVIv(SK*MODxLygeH=qE0O<5Pok_Qg5s+LHhFX_u=K&ujE ztC1@+Q&b1p>p`M4sDO*W;H3uopBVs-w=5AM$CHNSu}hs>0OloNQ@t9+8nP&4V;E08t@P zAQVfp(+YEhVxpi>T#zpj0ru&DY+iO=h8S=W<%;s8QGhFGBSb}@LP)ZNIXQ@xunto)pG5ok;m0m+1^IUF?(fR+SyHb;&EYaT7yKf%3>2JKTyuv*+6>_!HG`faq6 zGF~^HrYopZa^?)?YUWnvHt^ZR{EoT2{gk}@l>EP8+xAoPzwVTry3^TyQvUCGQciiV z{j|LOw7mVay#2JiZ8mQ|F>ifhZnRGOsd@XUdHbpPzu2j{+5S?7<_Kf`D%x)I+d@=@m2UdCWi8-5a8?3vultB{meLqy$%4_ zZ~qUce?#FD+zPd(j4pfVsd6&-aD}=`4Ub5x)v0+zrcPPGBXVR0jZh~m0bBGpuEBFN z3Iox{5fAX;rh#8KiXFEz*}zS(v^;5Cg~gj-w+L3C0i{rB%GX+A&~qw1X;w^l)CdL zuXB~Nt%mfPd?&JT^HjG^A?)2_%Qpva7_+wK_S^>BL1Y8#A=$t@HTOj91zAb5E$t+}9x-tqSd{@#LR;*pw#WH=lN>gUgYWPm1 z18lT%wGt~8m0FF0&mnzb3)|Jb-6K)z>p9P?E`B^}5|N@?V*hgA$h%*7E&6`&CZ56jF8g$bM1SsN_`r|8 zYY1DK6!o%kxce^K)#XR`o)s=7X8qK8)0q2PYv;ch>(q33;-2pWiNcp&mp-Tn)+dw~ zY)@?}dwP5J=9m2g`p;ypIKi2B&G6Be)p5smSAX+(NX4YZv*+8ta`JURfMjcJEH1g>XQS?}BYPRWP?99p~do0W*Gx?EZ1aye4Yfy_rvUN%|JSNSrQc`Zw z6FKlGS*hTAkZus&!qq+|HYPDHF+va*C+I|y{*Y=Q%Zv0F<#zMcfZ?0DSg-54ymgNQ z41qB>?=L5dAc8+Dp9GBOX3x!R6w!ATX(#`oL(jY%wxDEl z>b{N#=T#PkFgK0DOPkirvYp5LzWDO%^n3T_4nBP4;F7~k>rqvkPL9|d*qHiisboar z^?TaE*VMB`-VBY}VNE0d)~F+8*SE-P8&=`HCT~9f{=DZuoXFVneB#t$7siar-}lAS z1LKDFod5g5W#2gUe{ijQ(Sa)u<~>|JZ{n#J;Uib=e)-Db4mNK0!8*?Ey0V3LzMWWn zF>}L`jZZIm`DZD<8fG6I|Cn`hU&4VO>O}T;?nd_x={{lao%>ba1RZ&v^0Cy*%kZ^D z;>elH5BuLa=U$d!;}ia?Yf)fLGTC5R1u|bRD(kUjcH$iBPkP!WyZW(zNj3o)i^?Wp zE!V|bEkFXkRh6pbGJ}#3RvXH-I+dZeCGU}B0vR7e3P?W6dvR1X4p#r^dHHW;z}pUr zg4(imyRS9bU^#Q9E`L^3ekc9xkv+ZgmwYwk{YueyClWvXb|L5ab7jMI&*PJu(q~!U zQcgL{EBtWV2*0To!#V z?)zlXxho&pX4RY?oq5V>)81Q4fBR@qq{Vv!_kJ7RcUGY90MRucH;y*pNAK8CgU019 z7*r}BI^`63ucS*aHjl0j`rN2n6l`M z!`gtb8(+;+P2f&BXqf)Xyv($RlI7bSNmX6*@nE7-D?4%7>eNp%ZBo%anV*Saf>+qQ<*lJEhN*uV14YXSpwK!&x4#|fcw$|D)r*B5EPQcLnf^#_{`yN6Mbm~o zyDiRrTw{?xdhc1=-zVKmwK--#KYS89U@#|T z<#go_+*_#u!Q*`+2mX{WPB<>y!|{x)%hd9L;`^sFznD8J z$tjI>?U?8}bWHr=yj=sYME# z=GL!(X@?GE8z$e*e2W4kX{p)r9Z2)5?qqaJM;4#UBA!3#9lwH;RJ``ifo!|Y$Lu#& z8eR)J{QC6SV{zlzo3VLhs#$kCzJQFG>o&^e&6{cSd`5Y_Yecu1e^?DTHo4!m%a;eH zPCQr8E6-A1=QTv9IW7NfRbcI0)~>-B&(3YFsL+qc6hP;R?{juxCCDVoH|Gb7JSBAwc)bo^Wg4Pe*I4m9qDrQ zV83a7mt3CoWA23D4te~;0fi$vFaG0RpyLPq-6lT>?cx)%>e0gu)4qB{9CP?`!PTR$ zAD{Una+kqr_XfA*-}hWQy{uvANW~c61U2u-j|rFZtBLc~tnMTbV~z zpX#USdTdP8yav~GWP|H!lX00$oZsL&546p0wKH{{1e(S(aORsk(*|5(X=`+*6WE+K z;Hkj2U=@WYfoP^lf7qMN#NV*}sQm@yRJ(&hWWAGA1Sgtk63N;-X^ukgQP*{PEoSlMCyQk@biFk`WU|cBJ+Z zW>00A?H>xN!Ca&z#45R#A3_FEsD9FN8GIy0Dv=N(NnR3P&?zl4O^})x8AHa$^MlDi zY7qLikc3aQMM?~^$|^!aJ?b^L!I2BrpijW=`(n%P8@H6LOaJ1JI}h)ngA2>H3)1)h z5+l%>FHT1dtBllQHORrU+Xg@ zY;?$P^C$mutFKr0S+;#*XU`IsH>QgMQYYbwhGqjk52sV1M$jg6Oj3 z@z?4jEypyU2)Z&&vb96NqGhwh*6;OLHS6YkpLG6Y-L|pp(YgVr7wgjko;Ui}CHj{* zhJ_wZPg?$KaeUgurh7Amo+qWR`@FngcYKW3oC}NU&rP3ssqw|iEk`E4|Ki}_S&>7& z!LPf^4@XUvTlLO==5*~s|9J7Hxv|z0_D(L$zyImr@1|Y2_vppzDE5rV?O&7`7quOL zMLf-xu+LAf`)R<$;$K!<^bWBR*xbs~p2%sv5|t;tH=h6TqqO1gXLQ&TTi$1MW#RvH zB?|lpyw_?!%>Un7iN1q&+r9Q%4mPacwgI2?;Wy>$&ez{LKj7u7W!GP<-5)dm%)@<@yKl8y`(%Y`>YZ z?S4b#?Q`>2F8aXFLbv{$aOjNu`f0yjI^C_%KPu=0_j4ackDTyyy2aI+kyxjp+?@;6 z596ly;ivC8zH1WOf6l`p>&VC>Dg6@8&0YS~FMmb%n_>L=Q4_WgoDDYMgF*7^ZQOv{ z)Q3~=*Z4Qsfb(PcvGH+n#tk^A3Sjl0oSOeurdr}>zoo``d=hc@mFu)8FX~iND-2Sn zQ(v*MhS4wYyB!>y3p@CH=6Be}b<+DXviE8--SPK< zV?UIp*`3MV*vWcr>cuI`PwUCzkcI^!`=fWBWY!g8LgJmxsdGP$hoN$Gax=jkKwAMMekxiRCw>aQOT3up5moXfNPWc^s{xLZSg zSKa>NP?uk#)4yB%_<~~VLVO2dGiB(ZS6jCKklwGq$1L0;pPOsUR5yV|e*V+#_ghm_ z`}Vul_IobcenU0?jdtKxH^=t0|9S6~>D}rFGrP`SFfTy6_SZ!RpYXpwV)65m<`>14 z-sIYydrE&8bot3I4~A~t_w}6MYtOgbtB@93NAzq+cX_!L_xFsO9#quBa6i>)|H^H3d+Q!1jOf$N_u&_{-9HgyD?2%QK8dcaSN^nQUB&f% zho2r8hG~0{=|S8sNA5Ad`h0Tlur2GGSXZmQjOpI7sc>a<^ZFZ;VmFrzm~p;1<@WD~ zzbO59!s|gLbr<(^xBVpK=fu0--3QBO4-cr^HEY5AE-&gvd_L&op^GYyU!C>b?^UO> zQy=gBiv4-x!mV?BRMztU_C}r2fanHp>$?-`)G;*@ka*hx{II9F22Ez3eb9T$;G1@45Z< zg%ey zfpaN&VzVKp$7T8OCtr+@?yxUlW$$$>E~IyvRxtR?yyJTcucKge+}?EXX6{S- zZT<3Z&*pe{ir#XhmqpZl`?&>i=kD8d^gXq9WE{u(yL0j9PYjrR$uKtCWnKew71_X; zj&|V9^Zqlp74w7HKs_5viwFlM!v{pZJ0{=yhYzQ4^q}87e&2j!)s`dDxQEZ@)?Xs) z&o8V$OV*$IXB(g+8BC#aG-;|T_}3qKQpy5?bWvg?nHDFCj1`fI{0K6PvH-WW1qccC zr%$;uJ^Y)KG__V1}2wl8lRwi|u1-NTeIc8j!tAds-IG zj|QoaO%s4q1si-Z7~V%2AiZ0mvJZ1+gTlIOBXYuJrxHIJBQ zR&FCQS}5lI=Cb~i$l)%d=iqZ+ts%!36xtui+^-JF9W3p${sTMB#RSE!Jx*te?gh)L zRC~YrGYwHelO1}x?OcC!Q`H^U;{=ViLsS%; z?k*kU;4yi>Uw)W|6Pgz3X@s>ADxs@M-b)kvl9#=_G-<(uKZZRWD{eB!5BaGAjtY20 z=5fkF9rDA`GJ!)2NI7-0#RA>1RH)E&_r5eCO|s2$j_#i(r_J|%-uLI{-uHUH_nf9q z9NcnZaNg(4>1ChnSXa2GXYHzqb2oQPimYeLyDI*0VYj=pc=3X5_xC^f`NNO*?Ja1p zxWDGzofC!JA8vn1TygZ^KUyz#if3NU+qb)``|Z`o`<~%@`_JAw{hk5)$u?@q1bp!B zHPzbQQ*wXLE0^|dddTs1&*tXhm%FA{8n&1t;TCrMgGZm3x25|nX3v`qx3(2{zPY7k z$Fi}1YFxbIxuqu0u{~Z6EH-)fE+K24CA2jQZDSqHt@TYS*!gqlw##Qud_A^)TI?Qg zDSO_uh0{+xx5T z5bmy;^dWJjr^;=poVu%F!Rf7sr#Ef2+!V9e5`Z()dTzcmQ`+?vuMc^Yt-An493oKXRmy-{n!e=`NX`) z>QjyN9iOdbel-8rU6mClSDk2iET+9Yc;vZ1Z+NF|O8aiAzTjN_D#5IniDSW z`)Y6fD}yCnW}I2I_0_K{k6Rl;e?0KUmx~$;JZs+k-Of7)H}LPD>-f#3g;T!ZUUOUy z;HxjbTXSG$u)q^}sB5672IhYOCw#!ll^kuR5Rp)O92r`&n=6m>E5-;r?AU9WQLOto$*y@tEoK!G)ic zc0F>YV9X6OD)ZWNRD6I7$rA7ME28LnGDk(}01gz%0dNP<>GTxDy6{6u~bhJHw$UinGLF!AKIgw9ahq#nXiu8HgM3J5)^q*wteKngGV zYrG6`0fFpqbRE5s-~aqZmBiyC9fh+vONvb*S6pEv)k}h<1codVa{M!uBa{OgeWtSs!35m0U`!Akf*mFxu+i8E$%jw-SEOw*!4r@NY$ zfs3#51@hJZd~dfJ__%!aT*ijmf(5+S_eh<@m(@LLch^=Oe3d33tOu$-?}gCW1c6u*Ix zR!L0j7X28!fDu{>Lyn+qoGpo5*v(63o6imZgT850z*X zoPnx*byUOg9%opP)n*H@^HCWTMrFzAR(P@VAsJXjcNqCq%CVNgdFJDZ3qEsPP&{MT zqCv?GA4;+)#RHb{zf43n+niE0WJl!Ed?-4|N7C{-$$?8M$M4W~4u!A{UdiZn8C$QW_lgr#XJmNt>|d1}9nWs2tY zt{9kr&Gz4E@%t0Z*lfncoX@nH!(kt1wCG8aE@TM}SxD$KbdklN(NhGYu`=+>XDqZu zH(gICWe&GRg+gRzAS62jZvJ|_rPyXE9()fk)%Oqsj0PDI%cFFS(lsImMx;Etx<=_5 z5d$Mq9$j4{qbvSrA8z>JZZp|2n6Gslum{dpM_@mZwH>BWN0sPntiDiCaMr@zjUDZa zfh>i2)W$#;iV97nC@W1+c`5I~%6xS^pb2cQ0}y})u(!oZMNx(W9F$@+10E3&s1<~= z)nQ@@x{xF-dO}AQS`2g%+9;*&lg$5H0|qGa2{JEXMFQMfT6wp~@rmAxporAgE4rFF zv@7>U=eWfkz2{wnZ=8bPwBOTGICb*8PtV-aP<+?;mwvIiVqGu!v1ujO`P00QyRVE} zFy*PoKm5zDmLKR0GW`##r$Iuwr87q7G2c`FZ zFs|sgmfV|vk3aulk#6D9ZOt9R(pjP6=JVR8Zupn>E-5SVQHl5Ds9^6(!rwzLf&nbU zPz*p<5*R$MKrawTVg$=(U?ggi)AVQqla3L33eLoEJrc(=^x$?D&R&`xX~?vt=#2bG zTAyjlz}7$AAFIpsr=#IH#PGNjj%U_}6YwY{3s;$#K8v0q6C>e4M7lqc$b!M6pA0=J z3r1(bvdk};r4CY;#WqVFWR^OpEOk&>>Y%dJ0gsz9YJ=+~1B0tE4Wsn>j5(r79elV9 zn@fhE@$@-o2v(no8FcCEg<(hr9>NUw$55&MU}NqRczD0TGn)8AiTuu=MDb^hQ Date: Fri, 26 May 2023 15:01:43 -0500 Subject: [PATCH 211/510] fix checkstyle --- .../src/main/java/org/dspace/app/itemimport/ItemImport.java | 2 +- .../test/java/org/dspace/app/itemimport/ItemImportCLIIT.java | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImport.java b/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImport.java index bcf7afed38b5..c94e163243c1 100644 --- a/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImport.java +++ b/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImport.java @@ -78,7 +78,7 @@ public class ItemImport extends DSpaceRunnable { protected boolean zip = false; protected boolean remoteUrl = false; protected String zipfilename = null; - protected boolean zipvalid= false; + protected boolean zipvalid = false; protected boolean help = false; protected File workDir = null; protected File workFile = null; diff --git a/dspace-api/src/test/java/org/dspace/app/itemimport/ItemImportCLIIT.java b/dspace-api/src/test/java/org/dspace/app/itemimport/ItemImportCLIIT.java index 02a0a8aee048..08ae3af4ae06 100644 --- a/dspace-api/src/test/java/org/dspace/app/itemimport/ItemImportCLIIT.java +++ b/dspace-api/src/test/java/org/dspace/app/itemimport/ItemImportCLIIT.java @@ -269,7 +269,8 @@ public void importItemByZipSafInvalidMimetype() throws Exception { Path.of(tempDir.toString() + "/" + PDF_NAME)); String[] args = new String[] { "import", "-a", "-e", admin.getEmail(), "-c", collection.getID().toString(), - "-s", tempDir.toString(), "-z", PDF_NAME, "-m", tempDir.toString() + "/mapfile.out" }; + "-s", tempDir.toString(), "-z", PDF_NAME, "-m", tempDir.toString() + + "/mapfile.out" }; try { perfomImportScript(args); } catch (Exception e) { From 43ab705568b136651d9086119a11001c73cc08e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paulo=20Gra=C3=A7a?= Date: Fri, 26 May 2023 22:47:02 +0100 Subject: [PATCH 212/510] fixing code style errors --- .../plugins/AccessStatusElementItemCompilePlugin.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/dspace-oai/src/main/java/org/dspace/xoai/app/plugins/AccessStatusElementItemCompilePlugin.java b/dspace-oai/src/main/java/org/dspace/xoai/app/plugins/AccessStatusElementItemCompilePlugin.java index 65ec251b21ce..a854b4b42d38 100644 --- a/dspace-oai/src/main/java/org/dspace/xoai/app/plugins/AccessStatusElementItemCompilePlugin.java +++ b/dspace-oai/src/main/java/org/dspace/xoai/app/plugins/AccessStatusElementItemCompilePlugin.java @@ -9,18 +9,19 @@ import java.sql.SQLException; import java.util.List; + +import com.lyncode.xoai.dataprovider.xml.xoai.Element; +import com.lyncode.xoai.dataprovider.xml.xoai.Metadata; import org.dspace.access.status.factory.AccessStatusServiceFactory; import org.dspace.access.status.service.AccessStatusService; import org.dspace.content.Item; import org.dspace.core.Context; import org.dspace.xoai.app.XOAIExtensionItemCompilePlugin; import org.dspace.xoai.util.ItemUtils; -import com.lyncode.xoai.dataprovider.xml.xoai.Element; -import com.lyncode.xoai.dataprovider.xml.xoai.Metadata; /** * AccessStatusElementItemCompilePlugin aims to add structured information about the - * Access Status of the item (if any). + * Access Status of the item (if any). * The xoai document will be enriched with a structure like that *
@@ -32,7 +33,7 @@
  *   ;
  * }
  * 
- * Returning Values are based on: + * Returning Values are based on: * @see org.dspace.access.status.DefaultAccessStatusHelper DefaultAccessStatusHelper */ public class AccessStatusElementItemCompilePlugin implements XOAIExtensionItemCompilePlugin { From a9eab4a254b6fa151da0a86dfdf02217afdcf965 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paulo=20Gra=C3=A7a?= Date: Sat, 27 May 2023 08:23:46 +0100 Subject: [PATCH 213/510] also add support for access status at bitstream level --- .../crosswalks/oai/metadataFormats/oai_openaire.xsl | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/dspace/config/crosswalks/oai/metadataFormats/oai_openaire.xsl b/dspace/config/crosswalks/oai/metadataFormats/oai_openaire.xsl index 19b1486f4cbd..3a1d75eb56c6 100644 --- a/dspace/config/crosswalks/oai/metadataFormats/oai_openaire.xsl +++ b/dspace/config/crosswalks/oai/metadataFormats/oai_openaire.xsl @@ -75,6 +75,9 @@ + + @@ -93,9 +96,6 @@ - - @@ -1162,11 +1162,11 @@ - + + select="/doc:metadata/doc:element[@name='others']/doc:element[@name='access-status']/doc:field[@name='value']/text()"/> From c11679c6defd3d68496006fdbb3c01869ef5a3ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paulo=20Gra=C3=A7a?= Date: Sat, 27 May 2023 09:19:15 +0100 Subject: [PATCH 214/510] removing tailing semicolon --- .../app/plugins/AccessStatusElementItemCompilePlugin.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dspace-oai/src/main/java/org/dspace/xoai/app/plugins/AccessStatusElementItemCompilePlugin.java b/dspace-oai/src/main/java/org/dspace/xoai/app/plugins/AccessStatusElementItemCompilePlugin.java index a854b4b42d38..6b3c5ded9882 100644 --- a/dspace-oai/src/main/java/org/dspace/xoai/app/plugins/AccessStatusElementItemCompilePlugin.java +++ b/dspace-oai/src/main/java/org/dspace/xoai/app/plugins/AccessStatusElementItemCompilePlugin.java @@ -29,8 +29,8 @@ * * * open.access - * ; - * ; + *
+ * * } * * Returning Values are based on: From d0d67768100f3c3adc7134c0b2af7d02c2fae4a5 Mon Sep 17 00:00:00 2001 From: Nikita Krivonosov Date: Mon, 29 May 2023 09:27:56 +0200 Subject: [PATCH 215/510] [CST-5728] LYRASIS [Donated]: DSpace7: Implement signposting pattern in DSpace (REST) --- .../org/dspace/util/FrontendUrlService.java | 23 +- .../controller/LinksetRestController.java | 119 ++--- .../LinksetHttpMessageConverter.java | 19 +- ...nverter.java => LinksetNodeConverter.java} | 12 +- .../app/rest/signposting/model/Linkset.java | 54 +- .../model/{Lset.java => LinksetNode.java} | 12 +- .../{Relation.java => LinksetRelation.java} | 4 +- .../model/LinksetRelationType.java | 35 ++ .../rest/signposting/model/LinksetRest.java | 14 +- .../rest/signposting/model/TypedLinkRest.java | 31 +- .../AbstractSignPostingProcessor.java} | 12 +- .../BitstreamSignPostingProcessor.java | 28 -- .../processor/ItemSignPostingProcessor.java | 30 -- .../processor/SignPostingProcessor.java | 17 +- .../bitstream/BitstreamLinksetProcessor.java | 66 +++ .../BitstreamParentItemProcessor.java | 56 +++ .../BitstreamSignpostingProcessor.java | 33 ++ .../bitstream/BitstreamTypeProcessor.java | 60 +++ .../processor/item/ItemAuthorProcessor.java | 109 ++++ .../item/ItemContentBitstreamsProcessor.java | 61 +++ .../item/ItemIdentifierProcessor.java | 67 +++ .../processor/item/ItemLicenseProcessor.java | 51 ++ .../item/ItemSignpostingProcessor.java | 34 ++ .../processor/item/ItemTypeProcessor.java | 61 +++ .../relation/BitstreamLicenseProcessor.java | 74 --- .../relation/BitstreamLinksetProcessor.java | 85 ---- .../BitstreamPublicationBundaryProcessor.java | 76 --- .../relation/BitstreamTypeProcessor.java | 74 --- .../relation/ItemAuthorProcessor.java | 109 ---- .../relation/ItemIdentifierProcessor.java | 89 ---- .../relation/ItemLandingPageProcessor.java | 59 --- .../relation/ItemLicenseProcessor.java | 79 --- .../ItemPublicationBundaryProcessor.java | 87 ---- .../relation/ItemTypeProcessor.java | 79 --- .../rest/signposting/utils/LinksetMapper.java | 54 ++ .../controller/LinksetRestControllerIT.java | 469 +++++++++++++++--- ...verter-dspace-to-schema-org-uri.properties | 23 + dspace/config/spring/rest/signposting.xml | 58 +-- 38 files changed, 1266 insertions(+), 1157 deletions(-) rename dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/converter/{LsetConverter.java => LinksetNodeConverter.java} (70%) rename dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/model/{Lset.java => LinksetNode.java} (78%) rename dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/model/{Relation.java => LinksetRelation.java} (89%) create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/model/LinksetRelationType.java rename dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/{relation/ASignPostingProcessor.java => processor/AbstractSignPostingProcessor.java} (74%) delete mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/BitstreamSignPostingProcessor.java delete mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/ItemSignPostingProcessor.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/bitstream/BitstreamLinksetProcessor.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/bitstream/BitstreamParentItemProcessor.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/bitstream/BitstreamSignpostingProcessor.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/bitstream/BitstreamTypeProcessor.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemAuthorProcessor.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemContentBitstreamsProcessor.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemIdentifierProcessor.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemLicenseProcessor.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemSignpostingProcessor.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemTypeProcessor.java delete mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/relation/BitstreamLicenseProcessor.java delete mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/relation/BitstreamLinksetProcessor.java delete mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/relation/BitstreamPublicationBundaryProcessor.java delete mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/relation/BitstreamTypeProcessor.java delete mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/relation/ItemAuthorProcessor.java delete mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/relation/ItemIdentifierProcessor.java delete mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/relation/ItemLandingPageProcessor.java delete mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/relation/ItemLicenseProcessor.java delete mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/relation/ItemPublicationBundaryProcessor.java delete mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/relation/ItemTypeProcessor.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/utils/LinksetMapper.java create mode 100644 dspace/config/crosswalks/signposting/mapConverter-dspace-to-schema-org-uri.properties diff --git a/dspace-api/src/main/java/org/dspace/util/FrontendUrlService.java b/dspace-api/src/main/java/org/dspace/util/FrontendUrlService.java index 5e15c9c50a9e..a50baf910e77 100644 --- a/dspace-api/src/main/java/org/dspace/util/FrontendUrlService.java +++ b/dspace-api/src/main/java/org/dspace/util/FrontendUrlService.java @@ -14,7 +14,7 @@ import java.util.List; import java.util.Optional; -import org.apache.logging.log4j.Logger; +import org.dspace.content.Bitstream; import org.dspace.content.Item; import org.dspace.core.Context; import org.dspace.discovery.DiscoverQuery; @@ -22,6 +22,8 @@ import org.dspace.discovery.SearchService; import org.dspace.discovery.SearchServiceException; import org.dspace.services.ConfigurationService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -31,7 +33,7 @@ @Component public class FrontendUrlService { - private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(FrontendUrlService.class); + private static final Logger log = LoggerFactory.getLogger(FrontendUrlService.class); @Autowired private ConfigurationService configurationService; @@ -42,16 +44,27 @@ public class FrontendUrlService { /** * Generates front-end url for specified item. * - * @param item item + * @param context context + * @param item item * @return front-end url */ - public String generateUrl(Item item) { + public String generateUrl(Context context, Item item) { String uiURL = configurationService.getProperty("dspace.ui.url"); - Context context = new Context(Context.Mode.READ_ONLY); return generateUrlWithSearchService(item, uiURL, context) .orElseGet(() -> uiURL + "/items/" + item.getID()); } + /** + * Generates front-end url for specified bitstream. + * + * @param bitstream bitstream + * @return front-end url + */ + public String generateUrl(Bitstream bitstream) { + String uiURL = configurationService.getProperty("dspace.ui.url"); + return uiURL + "/bitstreams/" + bitstream.getID() + "/download"; + } + private Optional generateUrlWithSearchService(Item item, String uiURLStem, Context context) { DiscoverQuery entityQuery = new DiscoverQuery(); entityQuery.setQuery("search.uniqueid:\"Item-" + item.getID() + "\" and entityType:*"); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/controller/LinksetRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/controller/LinksetRestController.java index 9197e2cdf407..e828820c0c5f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/controller/LinksetRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/controller/LinksetRestController.java @@ -14,17 +14,15 @@ import java.util.List; import java.util.UUID; import java.util.stream.Collectors; -import java.util.stream.Stream; import javax.servlet.http.HttpServletRequest; import org.dspace.app.rest.converter.ConverterService; -import org.dspace.app.rest.signposting.model.Linkset; +import org.dspace.app.rest.signposting.model.LinksetNode; import org.dspace.app.rest.signposting.model.LinksetRest; -import org.dspace.app.rest.signposting.model.Lset; -import org.dspace.app.rest.signposting.model.Relation; import org.dspace.app.rest.signposting.model.TypedLinkRest; -import org.dspace.app.rest.signposting.processor.BitstreamSignPostingProcessor; -import org.dspace.app.rest.signposting.processor.ItemSignPostingProcessor; +import org.dspace.app.rest.signposting.processor.bitstream.BitstreamSignpostingProcessor; +import org.dspace.app.rest.signposting.processor.item.ItemSignpostingProcessor; +import org.dspace.app.rest.signposting.utils.LinksetMapper; import org.dspace.app.rest.utils.ContextUtil; import org.dspace.app.rest.utils.Utils; import org.dspace.content.Bitstream; @@ -34,8 +32,6 @@ import org.dspace.content.service.ItemService; import org.dspace.core.Constants; import org.dspace.core.Context; -import org.dspace.handle.service.HandleService; -import org.dspace.services.ConfigurationService; import org.dspace.utils.DSpace; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; @@ -66,10 +62,6 @@ public class LinksetRestController { @Autowired private ItemService itemService; @Autowired - private HandleService handleService; - @Autowired - private ConfigurationService configurationService; - @Autowired private ConverterService converter; @PreAuthorize("permitAll()") @@ -91,35 +83,20 @@ public LinksetRest getJson(HttpServletRequest request, @PathVariable UUID uuid) try { Context context = ContextUtil.obtainContext(request); - DSpaceObject dso = null; - dso = itemService.find(context, uuid); + Item dso = itemService.find(context, uuid); if (dso == null) { throw new ResourceNotFoundException("No such Item: " + uuid); } - List linksets = new ArrayList<>(); - Linkset primaryLinkset = new Linkset(); - linksets.add(primaryLinkset); - + List linksetNodes = new ArrayList<>(); if (dso.getType() == Constants.ITEM) { - primaryLinkset.setAnchor(handleService.resolveToURL( - context, dso.getHandle())); - List ispp = new DSpace().getServiceManager() - .getServicesByType(ItemSignPostingProcessor.class); - for (ItemSignPostingProcessor sp : ispp) { - sp.buildRelation(context, request, (Item) dso, linksets, primaryLinkset); - } - } - - LinksetRest linksetRest = null; - for (Linkset linkset : linksets) { - if (linksetRest == null) { - linksetRest = converter.toRest(linkset, utils.obtainProjection()); - } else { - linksetRest.getLinkset().add(linkset); + List ispp = new DSpace().getServiceManager() + .getServicesByType(ItemSignpostingProcessor.class); + for (ItemSignpostingProcessor sp : ispp) { + sp.addLinkSetNodes(context, request, dso, linksetNodes); } } - return linksetRest; + return converter.toRest(LinksetMapper.map(linksetNodes), utils.obtainProjection()); } catch (SQLException e) { throw new RuntimeException(e); } @@ -132,27 +109,24 @@ public LinksetRest getLset(HttpServletRequest request, @PathVariable UUID uuid) try { Context context = ContextUtil.obtainContext(request); - DSpaceObject dso = null; - dso = itemService.find(context, uuid); - if (dso == null) { + Item item = itemService.find(context, uuid); + if (item == null) { throw new ResourceNotFoundException("No such Item: " + uuid); } - List lsets = new ArrayList<>(); - if (dso.getType() == Constants.ITEM) { - List ispp = new DSpace().getServiceManager() - .getServicesByType(ItemSignPostingProcessor.class); - for (ItemSignPostingProcessor sp : ispp) { - sp.buildLset(context, request, (Item) dso, lsets); - } + List linksetNodes = new ArrayList<>(); + List ispp = new DSpace().getServiceManager() + .getServicesByType(ItemSignpostingProcessor.class); + for (ItemSignpostingProcessor sp : ispp) { + sp.addLinkSetNodes(context, request, item, linksetNodes); } LinksetRest linksetRest = null; - for (Lset lset : lsets) { + for (LinksetNode linksetNode : linksetNodes) { if (linksetRest == null) { - linksetRest = converter.toRest(lset, utils.obtainProjection()); + linksetRest = converter.toRest(linksetNode, utils.obtainProjection()); } else { - linksetRest.getLset().add(lset); + linksetRest.getLinksetNodes().add(linksetNode); } } return linksetRest; @@ -167,8 +141,7 @@ public List getHeader(HttpServletRequest request, @PathVariable U try { Context context = ContextUtil.obtainContext(request); - DSpaceObject dso = null; - dso = bitstreamService.find(context, uuid); + DSpaceObject dso = bitstreamService.find(context, uuid); if (dso == null) { dso = itemService.find(context, uuid); if (dso == null) { @@ -176,52 +149,26 @@ public List getHeader(HttpServletRequest request, @PathVariable U } } - List linksets = new ArrayList<>(); - Linkset primaryLinkset = new Linkset(); - linksets.add(primaryLinkset); - + List linksetNodes = new ArrayList<>(); if (dso.getType() == Constants.ITEM) { - primaryLinkset.setAnchor(handleService.resolveToURL( - context, dso.getHandle())); - List ispp = new DSpace().getServiceManager() - .getServicesByType(ItemSignPostingProcessor.class); - for (ItemSignPostingProcessor sp : ispp) { - sp.buildRelation(context, request, (Item) dso, linksets, primaryLinkset); + List ispp = new DSpace().getServiceManager() + .getServicesByType(ItemSignpostingProcessor.class); + for (ItemSignpostingProcessor sp : ispp) { + sp.addLinkSetNodes(context, request, (Item) dso, linksetNodes); } } else { - List bspp = new DSpace().getServiceManager() - .getServicesByType(BitstreamSignPostingProcessor.class); - for (BitstreamSignPostingProcessor sp : bspp) { - sp.buildRelation(context, request, (Bitstream) dso, linksets, primaryLinkset); + List bspp = new DSpace().getServiceManager() + .getServicesByType(BitstreamSignpostingProcessor.class); + for (BitstreamSignpostingProcessor sp : bspp) { + sp.addLinkSetNodes(context, request, (Bitstream) dso, linksetNodes); } - String url = configurationService.getProperty("dspace.ui.url"); - primaryLinkset.setAnchor(url + "/bitstreams/" + dso.getID() + "/download"); } - return linksets.stream() - .flatMap(linkset -> mapTypedLinks(linkset).stream()) + return linksetNodes.stream() + .map(node -> new TypedLinkRest(node.getLink(), node.getRelation(), node.getType())) .collect(Collectors.toList()); } catch (SQLException e) { throw new RuntimeException(e); } } - - private static List mapTypedLinks(Linkset linkset) { - return Stream.of( - mapTypedLinks(TypedLinkRest.Relation.LANDING_PAGE, linkset.getLandingPage()), - mapTypedLinks(TypedLinkRest.Relation.ITEM, linkset.getItem()), - mapTypedLinks(TypedLinkRest.Relation.CITE_AS, linkset.getCiteAs()), - mapTypedLinks(TypedLinkRest.Relation.AUTHOR, linkset.getAuthor()), - mapTypedLinks(TypedLinkRest.Relation.TYPE, linkset.getType()), - mapTypedLinks(TypedLinkRest.Relation.LICENSE, linkset.getLicense()), - mapTypedLinks(TypedLinkRest.Relation.COLLECTION, linkset.getCollection()), - mapTypedLinks(TypedLinkRest.Relation.LINKSET, linkset.getLinkset()) - ).flatMap(List::stream).collect(Collectors.toList()); - } - - private static List mapTypedLinks(TypedLinkRest.Relation relationType, List relations) { - return relations.stream() - .map(relation -> new TypedLinkRest(relation.getHref(), relationType, relation.getType())) - .collect(Collectors.toList()); - } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/converter/LinksetHttpMessageConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/converter/LinksetHttpMessageConverter.java index 0cc6628c3bc8..e5c187c9554b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/converter/LinksetHttpMessageConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/converter/LinksetHttpMessageConverter.java @@ -8,6 +8,7 @@ package org.dspace.app.rest.signposting.converter; import static java.lang.String.format; +import static java.util.Objects.nonNull; import static org.apache.commons.lang.StringUtils.isNotBlank; import java.io.IOException; @@ -35,18 +36,18 @@ public LinksetHttpMessageConverter() { protected void writeInternal(LinksetRest linksetRest, Type type, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException { StringBuilder responseBody = new StringBuilder(); - linksetRest.getLset().forEach(lset -> { - if (isNotBlank(lset.getLink())) { - responseBody.append(format("<%s> ", lset.getLink())); + linksetRest.getLinksetNodes().forEach(linksetNodes -> { + if (isNotBlank(linksetNodes.getLink())) { + responseBody.append(format("<%s> ", linksetNodes.getLink())); } - if (isNotBlank(lset.getRelation())) { - responseBody.append(format("; rel=\"%s\" ", lset.getRelation())); + if (nonNull(linksetNodes.getRelation())) { + responseBody.append(format("; rel=\"%s\" ", linksetNodes.getRelation().getName())); } - if (isNotBlank(lset.getType())) { - responseBody.append(format("; type=\"%s\" ", lset.getType())); + if (isNotBlank(linksetNodes.getType())) { + responseBody.append(format("; type=\"%s\" ", linksetNodes.getType())); } - if (isNotBlank(lset.getAnchor())) { - responseBody.append(format("; anchor=\"%s\" ", lset.getAnchor())); + if (isNotBlank(linksetNodes.getAnchor())) { + responseBody.append(format("; anchor=\"%s\" ", linksetNodes.getAnchor())); } responseBody.append(", "); }); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/converter/LsetConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/converter/LinksetNodeConverter.java similarity index 70% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/converter/LsetConverter.java rename to dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/converter/LinksetNodeConverter.java index 1e2e3884a434..e73aa260ad3e 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/converter/LsetConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/converter/LinksetNodeConverter.java @@ -9,8 +9,8 @@ import org.dspace.app.rest.converter.DSpaceConverter; import org.dspace.app.rest.projection.Projection; +import org.dspace.app.rest.signposting.model.LinksetNode; import org.dspace.app.rest.signposting.model.LinksetRest; -import org.dspace.app.rest.signposting.model.Lset; import org.springframework.stereotype.Component; @@ -20,18 +20,18 @@ * @author Francesco Pio Scognamiglio (francescopio.scognamiglio at 4science.com) */ @Component -public class LsetConverter implements DSpaceConverter { +public class LinksetNodeConverter implements DSpaceConverter { @Override - public LinksetRest convert(Lset lset, Projection projection) { + public LinksetRest convert(LinksetNode linkSetNode, Projection projection) { LinksetRest linksetRest = new LinksetRest(); linksetRest.setProjection(projection); - linksetRest.getLset().add(lset); + linksetRest.getLinksetNodes().add(linkSetNode); return linksetRest; } @Override - public Class getModelClass() { - return Lset.class; + public Class getModelClass() { + return LinksetNode.class; } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/model/Linkset.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/model/Linkset.java index f97d7a5df39c..c70e4916f69c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/model/Linkset.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/model/Linkset.java @@ -19,102 +19,90 @@ public class Linkset { @JsonInclude(JsonInclude.Include.NON_EMPTY) - private List author; + private List author; @JsonProperty("cite-as") @JsonInclude(JsonInclude.Include.NON_EMPTY) - private List citeAs; + private List citeAs; @JsonInclude(JsonInclude.Include.NON_EMPTY) - private List item; + private List item; @JsonInclude(JsonInclude.Include.NON_EMPTY) - private List collection; + private List collection; @JsonInclude(JsonInclude.Include.NON_EMPTY) - private List landingPage; + private List type; @JsonInclude(JsonInclude.Include.NON_EMPTY) - private List type; + private List license; @JsonInclude(JsonInclude.Include.NON_EMPTY) - private List license; - @JsonInclude(JsonInclude.Include.NON_EMPTY) - private List linkset; + private List linkset; @JsonInclude(JsonInclude.Include.NON_NULL) private String anchor; - public List getAuthor() { + public List getAuthor() { if (this.author == null) { this.author = new ArrayList<>(); } return author; } - public void setAuthor(List author) { + public void setAuthor(List author) { this.author = author; } - public List getCiteAs() { + public List getCiteAs() { if (this.citeAs == null) { this.citeAs = new ArrayList<>(); } return citeAs; } - public void setCiteAs(List citeAs) { + public void setCiteAs(List citeAs) { this.citeAs = citeAs; } - public List getItem() { + public List getItem() { if (this.item == null) { this.item = new ArrayList<>(); } return item; } - public void setItem(List item) { + public void setItem(List item) { this.item = item; } - public List getCollection() { + public List getCollection() { if (this.collection == null) { this.collection = new ArrayList<>(); } return collection; } - public void setCollection(List collection) { + public void setCollection(List collection) { this.collection = collection; } - public List getLandingPage() { - if (landingPage == null) { - landingPage = new ArrayList<>(); - } - return landingPage; - } - public void setLandingPage(List landingPage) { - this.landingPage = landingPage; - } - - public List getType() { + public List getType() { if (type == null) { type = new ArrayList<>(); } return type; } - public void setType(List type) { + public void setType(List type) { this.type = type; } - public List getLicense() { + public List getLicense() { if (license == null) { license = new ArrayList<>(); } return license; } - public void setLicense(List license) { + public void setLicense(List license) { this.license = license; } - public List getLinkset() { + public List getLinkset() { if (linkset == null) { linkset = new ArrayList<>(); } return linkset; } - public void setLinkset(List linkset) { + public void setLinkset(List linkset) { this.linkset = linkset; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/model/Lset.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/model/LinksetNode.java similarity index 78% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/model/Lset.java rename to dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/model/LinksetNode.java index dbfabfcb00ae..8c7347350faa 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/model/Lset.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/model/LinksetNode.java @@ -12,23 +12,23 @@ /** * DTO object represents a node of a link set. */ -public class Lset { +public class LinksetNode { @JsonInclude(JsonInclude.Include.NON_NULL) private String link; @JsonInclude(JsonInclude.Include.NON_NULL) - private String relation; + private LinksetRelationType relation; @JsonInclude(JsonInclude.Include.NON_NULL) private String type; @JsonInclude(JsonInclude.Include.NON_NULL) private String anchor; - public Lset(String link, String relation, String type, String anchor) { + public LinksetNode(String link, LinksetRelationType relation, String type, String anchor) { this(link, relation, anchor); this.type = type; } - public Lset(String link, String relation, String anchor) { + public LinksetNode(String link, LinksetRelationType relation, String anchor) { this.link = link; this.relation = relation; this.anchor = anchor; @@ -42,11 +42,11 @@ public void setLink(String link) { this.link = link; } - public String getRelation() { + public LinksetRelationType getRelation() { return relation; } - public void setRelation(String relation) { + public void setRelation(LinksetRelationType relation) { this.relation = relation; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/model/Relation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/model/LinksetRelation.java similarity index 89% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/model/Relation.java rename to dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/model/LinksetRelation.java index 9b3139fe1ee6..ecbb786079d0 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/model/Relation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/model/LinksetRelation.java @@ -12,14 +12,14 @@ /** * DTO object represents a relation to specific resource. */ -public class Relation { +public class LinksetRelation { @JsonInclude(JsonInclude.Include.NON_NULL) private String href; @JsonInclude(JsonInclude.Include.NON_NULL) private String type; - public Relation(String href, String type) { + public LinksetRelation(String href, String type) { this.href = href; this.type = type; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/model/LinksetRelationType.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/model/LinksetRelationType.java new file mode 100644 index 000000000000..7a513b5d998a --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/model/LinksetRelationType.java @@ -0,0 +1,35 @@ +/** + * 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.signposting.model; + +import com.fasterxml.jackson.annotation.JsonValue; + +/** + * An enumeration that holds track of linkset relation types. + */ +public enum LinksetRelationType { + + ITEM("item"), + CITE_AS("cite-as"), + AUTHOR("author"), + TYPE("type"), + LICENSE("license"), + COLLECTION("collection"), + LINKSET("linkset"); + + private final String name; + + LinksetRelationType(String name) { + this.name = name; + } + + @JsonValue + public String getName() { + return name; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/model/LinksetRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/model/LinksetRest.java index 6558d75d5851..437e486ad097 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/model/LinksetRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/model/LinksetRest.java @@ -33,7 +33,7 @@ public class LinksetRest extends RestAddressableModel { @JsonInclude(Include.NON_EMPTY) private List linkset; @JsonInclude(Include.NON_EMPTY) - private List lset; + private List linkSetNode; public List getLinkset() { if (this.linkset == null) { @@ -45,14 +45,14 @@ public void setLinkset(List linkset) { this.linkset = linkset; } - public List getLset() { - if (this.lset == null) { - this.lset = new ArrayList<>(); + public List getLinksetNodes() { + if (this.linkSetNode == null) { + this.linkSetNode = new ArrayList<>(); } - return lset; + return linkSetNode; } - public void setLset(List lset) { - this.lset = lset; + public void setLset(List linkSetNode) { + this.linkSetNode = linkSetNode; } @JsonIgnore diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/model/TypedLinkRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/model/TypedLinkRest.java index 2a6bcc4a012f..3ba09bf1094c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/model/TypedLinkRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/model/TypedLinkRest.java @@ -8,7 +8,6 @@ package org.dspace.app.rest.signposting.model; import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonValue; import org.dspace.app.rest.RestResourceController; import org.dspace.app.rest.model.LinksRest; import org.dspace.app.rest.model.RestAddressableModel; @@ -25,14 +24,14 @@ public class TypedLinkRest extends RestAddressableModel { private String href; - private Relation rel; + private LinksetRelationType rel; private String type; public TypedLinkRest() { } - public TypedLinkRest(String href, Relation rel, String type) { + public TypedLinkRest(String href, LinksetRelationType rel, String type) { this.href = href; this.rel = rel; this.type = type; @@ -46,11 +45,11 @@ public void setHref(String href) { this.href = href; } - public Relation getRel() { + public LinksetRelationType getRel() { return rel; } - public void setRel(Relation rel) { + public void setRel(LinksetRelationType rel) { this.rel = rel; } @@ -72,26 +71,4 @@ public String getCategory() { public Class getController() { return RestResourceController.class; } - - public enum Relation { - LANDING_PAGE("landing page"), - ITEM("item"), - CITE_AS("cite-as"), - AUTHOR("author"), - TYPE("type"), - LICENSE("license"), - COLLECTION("collection"), - LINKSET("linkset"); - - private final String name; - - Relation(String name) { - this.name = name; - } - - @JsonValue - public String getName() { - return name; - } - } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/relation/ASignPostingProcessor.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/AbstractSignPostingProcessor.java similarity index 74% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/relation/ASignPostingProcessor.java rename to dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/AbstractSignPostingProcessor.java index 22ae0a72b704..32368a57d595 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/relation/ASignPostingProcessor.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/AbstractSignPostingProcessor.java @@ -5,7 +5,9 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.rest.signposting.relation; +package org.dspace.app.rest.signposting.processor; + +import org.dspace.app.rest.signposting.model.LinksetRelationType; /** * An abstract class of generic signposting relation. @@ -13,11 +15,11 @@ * @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.com) * @author Francesco Pio Scognamiglio (francescopio.scognamiglio at 4science.com) */ -public abstract class ASignPostingProcessor { +public abstract class AbstractSignPostingProcessor { private String metadataField; - private String relation; + private LinksetRelationType relation; private String pattern; @@ -29,11 +31,11 @@ public void setMetadataField(String metadataField) { this.metadataField = metadataField; } - public String getRelation() { + public LinksetRelationType getRelation() { return relation; } - public void setRelation(String relation) { + public void setRelation(LinksetRelationType relation) { this.relation = relation; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/BitstreamSignPostingProcessor.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/BitstreamSignPostingProcessor.java deleted file mode 100644 index e80c3b4c39c2..000000000000 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/BitstreamSignPostingProcessor.java +++ /dev/null @@ -1,28 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.app.rest.signposting.processor; - -import org.dspace.content.Bitstream; -import org.dspace.services.ConfigurationService; -import org.dspace.services.factory.DSpaceServicesFactory; - -/** - * BitstreamSignPostingProcessor interface represents SignPostingProcessor for a bitstream. - * - * @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.com) - * @author Francesco Pio Scognamiglio (francescopio.scognamiglio at 4science.com) - */ -public interface BitstreamSignPostingProcessor extends SignPostingProcessor { - - default String buildAnchor(Bitstream bitstream) { - ConfigurationService configurationService = - DSpaceServicesFactory.getInstance().getConfigurationService(); - String url = configurationService.getProperty("dspace.ui.url"); - return url + "/bitstreams/" + bitstream.getID() + "/download"; - } -} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/ItemSignPostingProcessor.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/ItemSignPostingProcessor.java deleted file mode 100644 index 2d4b21464cb3..000000000000 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/ItemSignPostingProcessor.java +++ /dev/null @@ -1,30 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.app.rest.signposting.processor; - -import java.sql.SQLException; - -import org.dspace.content.Item; -import org.dspace.core.Context; -import org.dspace.handle.factory.HandleServiceFactory; -import org.dspace.handle.service.HandleService; - -/** - * ItemSignPostingProcessor interface represents SignPostingProcessor for an item. - * - * @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.com) - * @author Francesco Pio Scognamiglio (francescopio.scognamiglio at 4science.com) - */ -public interface ItemSignPostingProcessor extends SignPostingProcessor { - - default String buildAnchor(Context context, Item item) throws SQLException { - HandleService handleService = - HandleServiceFactory.getInstance().getHandleService(); - return handleService.resolveToURL(context, item.getHandle()); - } -} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/SignPostingProcessor.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/SignPostingProcessor.java index aab13b6626b1..efcfd50ab512 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/SignPostingProcessor.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/SignPostingProcessor.java @@ -10,8 +10,7 @@ import java.util.List; import javax.servlet.http.HttpServletRequest; -import org.dspace.app.rest.signposting.model.Linkset; -import org.dspace.app.rest.signposting.model.Lset; +import org.dspace.app.rest.signposting.model.LinksetNode; import org.dspace.content.DSpaceObject; import org.dspace.core.Context; @@ -22,9 +21,15 @@ * @author Francesco Pio Scognamiglio (francescopio.scognamiglio at 4science.com) */ public interface SignPostingProcessor { - void buildRelation(Context context, HttpServletRequest request, - T object, List linksets, Linkset primaryLinkset); - void buildLset(Context context, HttpServletRequest request, - T object, List lsets); + /** + * Method for adding new linkset nodes into {@code linksetNodes}. + * + * @param context context + * @param request request + * @param object object + * @param linksetNodes linkset nodes + */ + void addLinkSetNodes(Context context, HttpServletRequest request, + T object, List linksetNodes); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/bitstream/BitstreamLinksetProcessor.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/bitstream/BitstreamLinksetProcessor.java new file mode 100644 index 000000000000..c65191cb0749 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/bitstream/BitstreamLinksetProcessor.java @@ -0,0 +1,66 @@ +/** + * 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.signposting.processor.bitstream; + +import java.util.List; +import javax.servlet.http.HttpServletRequest; + +import org.apache.log4j.Logger; +import org.dspace.app.rest.signposting.model.LinksetNode; +import org.dspace.app.rest.signposting.model.LinksetRelationType; +import org.dspace.content.Bitstream; +import org.dspace.content.Item; +import org.dspace.content.service.BitstreamService; +import org.dspace.core.Context; +import org.dspace.services.ConfigurationService; +import org.dspace.util.FrontendUrlService; + +/** + * An extension of {@link BitstreamSignpostingProcessor} for the linkset relation. + */ +public class BitstreamLinksetProcessor extends BitstreamSignpostingProcessor { + + private static final Logger log = Logger.getLogger(BitstreamLinksetProcessor.class); + + private final BitstreamService bitstreamService; + + private final ConfigurationService configurationService; + + public BitstreamLinksetProcessor(FrontendUrlService frontendUrlService, + BitstreamService bitstreamService, + ConfigurationService configurationService) { + super(frontendUrlService); + this.bitstreamService = bitstreamService; + this.configurationService = configurationService; + setRelation(LinksetRelationType.LINKSET); + } + + @Override + public void addLinkSetNodes(Context context, HttpServletRequest request, + Bitstream bitstream, List linksetNodes) { + try { + Item item = (Item) bitstreamService.getParentObject(context, bitstream); + if (item != null) { + String signpostingPath = configurationService.getProperty("signposting.path"); + String baseUrl = configurationService.getProperty("dspace.ui.url"); + + String linksetUrl = String.format("%s/%s/linksets/%s", baseUrl, signpostingPath, item.getID()); + String linksetJsonUrl = linksetUrl + "/json"; + List links = List.of( + new LinksetNode(linksetUrl, getRelation(), "application/linkset", buildAnchor(bitstream)), + new LinksetNode(linksetJsonUrl, getRelation(), "application/linkset+json", + buildAnchor(bitstream)) + ); + linksetNodes.addAll(links); + } + } catch (Exception e) { + log.error(e.getMessage(), e); + } + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/bitstream/BitstreamParentItemProcessor.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/bitstream/BitstreamParentItemProcessor.java new file mode 100644 index 000000000000..815d7817d4cf --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/bitstream/BitstreamParentItemProcessor.java @@ -0,0 +1,56 @@ +/** + * 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.signposting.processor.bitstream; + +import java.util.List; +import javax.servlet.http.HttpServletRequest; + +import org.apache.log4j.Logger; +import org.dspace.app.rest.signposting.model.LinksetNode; +import org.dspace.app.rest.signposting.model.LinksetRelationType; +import org.dspace.content.Bitstream; +import org.dspace.content.Item; +import org.dspace.content.service.BitstreamService; +import org.dspace.core.Context; +import org.dspace.util.FrontendUrlService; + +/** + * An extension of {@link BitstreamSignpostingProcessor} for the collection relation. + * It links the Bitstream to the parent Item. + * + * @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.com) + * @author Francesco Pio Scognamiglio (francescopio.scognamiglio at 4science.com) + */ +public class BitstreamParentItemProcessor extends BitstreamSignpostingProcessor { + + private static final Logger log = Logger.getLogger(BitstreamParentItemProcessor.class); + + private final BitstreamService bitstreamService; + + public BitstreamParentItemProcessor(FrontendUrlService frontendUrlService, + BitstreamService bitstreamService) { + super(frontendUrlService); + this.bitstreamService = bitstreamService; + setRelation(LinksetRelationType.COLLECTION); + } + + @Override + public void addLinkSetNodes(Context context, HttpServletRequest request, + Bitstream bitstream, List linksetNodes) { + try { + Item item = (Item) bitstreamService.getParentObject(context, bitstream); + if (item != null) { + String itemUiUrl = frontendUrlService.generateUrl(context, item); + linksetNodes.add(new LinksetNode(itemUiUrl, getRelation(), "text/html", buildAnchor(bitstream))); + } + } catch (Exception e) { + log.error(e.getMessage(), e); + } + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/bitstream/BitstreamSignpostingProcessor.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/bitstream/BitstreamSignpostingProcessor.java new file mode 100644 index 000000000000..b0f251edb5ee --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/bitstream/BitstreamSignpostingProcessor.java @@ -0,0 +1,33 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.signposting.processor.bitstream; + +import org.dspace.app.rest.signposting.processor.AbstractSignPostingProcessor; +import org.dspace.app.rest.signposting.processor.SignPostingProcessor; +import org.dspace.content.Bitstream; +import org.dspace.util.FrontendUrlService; + +/** + * An abstract class represents {@link SignPostingProcessor } for a bitstream. + * + * @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.com) + * @author Francesco Pio Scognamiglio (francescopio.scognamiglio at 4science.com) + */ +public abstract class BitstreamSignpostingProcessor extends AbstractSignPostingProcessor + implements SignPostingProcessor { + + protected final FrontendUrlService frontendUrlService; + + public BitstreamSignpostingProcessor(FrontendUrlService frontendUrlService) { + this.frontendUrlService = frontendUrlService; + } + + public String buildAnchor(Bitstream bitstream) { + return frontendUrlService.generateUrl(bitstream); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/bitstream/BitstreamTypeProcessor.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/bitstream/BitstreamTypeProcessor.java new file mode 100644 index 000000000000..2f46193ca81d --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/bitstream/BitstreamTypeProcessor.java @@ -0,0 +1,60 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.signposting.processor.bitstream; + +import java.util.List; +import javax.servlet.http.HttpServletRequest; + +import org.apache.commons.lang3.StringUtils; +import org.apache.log4j.Logger; +import org.dspace.app.rest.signposting.model.LinksetNode; +import org.dspace.app.rest.signposting.model.LinksetRelationType; +import org.dspace.content.Bitstream; +import org.dspace.content.Item; +import org.dspace.content.service.BitstreamService; +import org.dspace.core.Context; +import org.dspace.util.FrontendUrlService; +import org.dspace.util.SimpleMapConverter; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * An extension of {@link BitstreamSignpostingProcessor} for the type relation. + * Provides links to a specific type from schema.org. + */ +public class BitstreamTypeProcessor extends BitstreamSignpostingProcessor { + + private static final Logger log = Logger.getLogger(BitstreamTypeProcessor.class); + + @Autowired + private SimpleMapConverter mapConverterDSpaceToSchemaOrgUri; + + @Autowired + private BitstreamService bitstreamService; + + public BitstreamTypeProcessor(FrontendUrlService frontendUrlService) { + super(frontendUrlService); + setRelation(LinksetRelationType.TYPE); + } + + @Override + public void addLinkSetNodes(Context context, HttpServletRequest request, + Bitstream bitstream, List linksetNodes) { + try { + String type = bitstreamService.getMetadataFirstValue(bitstream, "dc", "type", null, Item.ANY); + if (StringUtils.isNotBlank(type)) { + String typeSchemeUri = mapConverterDSpaceToSchemaOrgUri.getValue(type); + linksetNodes.add( + new LinksetNode(typeSchemeUri, getRelation(), "text/html", buildAnchor(bitstream)) + ); + } + } catch (Exception e) { + log.error(e.getMessage(), e); + } + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemAuthorProcessor.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemAuthorProcessor.java new file mode 100644 index 000000000000..b935e50bfad8 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemAuthorProcessor.java @@ -0,0 +1,109 @@ +/** + * 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.signposting.processor.item; + +import static org.dspace.content.Item.ANY; + +import java.text.MessageFormat; +import java.util.List; +import javax.servlet.http.HttpServletRequest; + +import org.apache.commons.lang.StringUtils; +import org.apache.log4j.Logger; +import org.dspace.app.rest.signposting.model.LinksetNode; +import org.dspace.app.rest.signposting.model.LinksetRelationType; +import org.dspace.content.EntityType; +import org.dspace.content.Item; +import org.dspace.content.MetadataFieldName; +import org.dspace.content.Relationship; +import org.dspace.content.RelationshipType; +import org.dspace.content.service.EntityTypeService; +import org.dspace.content.service.ItemService; +import org.dspace.content.service.RelationshipService; +import org.dspace.core.Context; +import org.dspace.util.FrontendUrlService; + +/** + * An extension of {@link ItemSignpostingProcessor} for the author relation. + * + * @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.com) + * @author Francesco Pio Scognamiglio (francescopio.scognamiglio at 4science.com) + */ +public class ItemAuthorProcessor extends ItemSignpostingProcessor { + + private static final String IS_AUTHOR_OF = "isAuthorOf"; + + /** + * log4j category + */ + private static final Logger log = Logger.getLogger(ItemAuthorProcessor.class); + + private final ItemService itemService; + + private final RelationshipService relationshipService; + + private final EntityTypeService entityTypeService; + + private String orcidMetadata; + + public ItemAuthorProcessor(FrontendUrlService frontendUrlService, + ItemService itemService, + RelationshipService relationshipService, + EntityTypeService entityTypeService) { + super(frontendUrlService); + this.itemService = itemService; + this.relationshipService = relationshipService; + this.entityTypeService = entityTypeService; + setRelation(LinksetRelationType.AUTHOR); + } + + public String getOrcidMetadata() { + return orcidMetadata; + } + + public void setOrcidMetadata(String orcidMetadata) { + this.orcidMetadata = orcidMetadata; + } + + @Override + public void addLinkSetNodes(Context context, HttpServletRequest request, + Item item, List linksetNodes) { + try { + EntityType personType = entityTypeService.findByEntityType(context, "Author"); + List itemRelationships = relationshipService.findByItem(context, item); + for (Relationship relationship : itemRelationships) { + + RelationshipType relationshipType = relationship.getRelationshipType(); + boolean hasPersonType = relationshipType.getLeftType().equals(personType) + || relationshipType.getRightType().equals(personType); + boolean isAuthor = relationshipType.getLeftwardType().startsWith(IS_AUTHOR_OF) + || relationshipType.getRightwardType().startsWith(IS_AUTHOR_OF); + + if (hasPersonType && isAuthor) { + Item authorItem = relationship.getLeftItem().getID().equals(item.getID()) + ? relationship.getRightItem() + : relationship.getLeftItem(); + + String authorOrcid = itemService.getMetadataFirstValue( + authorItem, new MetadataFieldName(getOrcidMetadata()), ANY + ); + if (StringUtils.isNotBlank(authorOrcid)) { + String authorLink = StringUtils.isBlank(getPattern()) + ? authorOrcid + : MessageFormat.format(getPattern(), authorOrcid); + linksetNodes.add( + new LinksetNode(authorLink, getRelation(), "text/html", buildAnchor(context, item)) + ); + } + } + } + } catch (Exception e) { + log.error("Problem to add signposting pattern", e); + } + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemContentBitstreamsProcessor.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemContentBitstreamsProcessor.java new file mode 100644 index 000000000000..61bf371adbdf --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemContentBitstreamsProcessor.java @@ -0,0 +1,61 @@ +/** + * 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.signposting.processor.item; + +import java.sql.SQLException; +import java.util.List; +import javax.servlet.http.HttpServletRequest; + +import org.apache.log4j.Logger; +import org.dspace.app.rest.signposting.model.LinksetNode; +import org.dspace.app.rest.signposting.model.LinksetRelationType; +import org.dspace.content.Bitstream; +import org.dspace.content.Bundle; +import org.dspace.content.Item; +import org.dspace.core.Constants; +import org.dspace.core.Context; +import org.dspace.util.FrontendUrlService; + +/** + * An extension of {@link ItemSignpostingProcessor} for the item relation. + * It links item with its content. + * + * @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.com) + * @author Francesco Pio Scognamiglio (francescopio.scognamiglio at 4science.com) + */ +public class ItemContentBitstreamsProcessor extends ItemSignpostingProcessor { + + /** + * log4j category + */ + private static final Logger log = Logger.getLogger(ItemContentBitstreamsProcessor.class); + + public ItemContentBitstreamsProcessor(FrontendUrlService frontendUrlService) { + super(frontendUrlService); + setRelation(LinksetRelationType.ITEM); + } + + @Override + public void addLinkSetNodes(Context context, HttpServletRequest request, + Item item, List linksetNodes) { + try { + for (Bundle bundle : item.getBundles(Constants.CONTENT_BUNDLE_NAME)) { + for (Bitstream bitstream : bundle.getBitstreams()) { + String mimeType = bitstream.getFormat(context).getMIMEType(); + String bitstreamUrl = frontendUrlService.generateUrl(bitstream); + linksetNodes.add( + new LinksetNode(bitstreamUrl, getRelation(), mimeType, buildAnchor(context, item)) + ); + } + } + } catch (SQLException e) { + log.error(e.getMessage(), e); + } + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemIdentifierProcessor.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemIdentifierProcessor.java new file mode 100644 index 000000000000..01151fd7d37c --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemIdentifierProcessor.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.app.rest.signposting.processor.item; + +import java.text.MessageFormat; +import java.util.List; +import javax.servlet.http.HttpServletRequest; + +import org.apache.commons.lang3.StringUtils; +import org.apache.log4j.Logger; +import org.dspace.app.rest.signposting.model.LinksetNode; +import org.dspace.app.rest.signposting.model.LinksetRelationType; +import org.dspace.content.Item; +import org.dspace.content.MetadataValue; +import org.dspace.content.service.ItemService; +import org.dspace.core.Context; +import org.dspace.util.FrontendUrlService; + +/** + * An extension of {@link ItemSignpostingProcessor} for the identifier relation. + * Identifier metadata can be specified with metadataField in configuration. + * + * @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.com) + * @author Francesco Pio Scognamiglio (francescopio.scognamiglio at 4science.com) + */ +public class ItemIdentifierProcessor extends ItemSignpostingProcessor { + + /** + * log4j category + */ + private static final Logger log = Logger.getLogger(ItemIdentifierProcessor.class); + + private final ItemService itemService; + + public ItemIdentifierProcessor(FrontendUrlService frontendUrlService, ItemService itemService) { + super(frontendUrlService); + this.itemService = itemService; + setRelation(LinksetRelationType.CITE_AS); + } + + @Override + public void addLinkSetNodes(Context context, HttpServletRequest request, + Item item, List linksetNodes) { + try { + List identifiers = itemService.getMetadataByMetadataString(item, getMetadataField()); + for (MetadataValue identifier : identifiers) { + if (identifier != null) { + String identifierValue = identifier.getValue(); + if (StringUtils.isNotBlank(identifierValue)) { + if (StringUtils.isNotBlank(getPattern())) { + identifierValue = MessageFormat.format(getPattern(), identifierValue); + } + linksetNodes.add(new LinksetNode(identifierValue, getRelation(), buildAnchor(context, item))); + } + } + } + } catch (Exception e) { + log.error(e.getMessage(), e); + } + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemLicenseProcessor.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemLicenseProcessor.java new file mode 100644 index 000000000000..2c356129963e --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemLicenseProcessor.java @@ -0,0 +1,51 @@ +/** + * 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.signposting.processor.item; + +import java.util.List; +import javax.servlet.http.HttpServletRequest; + +import org.apache.commons.lang3.StringUtils; +import org.apache.log4j.Logger; +import org.dspace.app.rest.signposting.model.LinksetNode; +import org.dspace.app.rest.signposting.model.LinksetRelationType; +import org.dspace.content.Item; +import org.dspace.core.Context; +import org.dspace.license.factory.LicenseServiceFactory; +import org.dspace.license.service.CreativeCommonsService; +import org.dspace.util.FrontendUrlService; + +/** + * An extension of {@link ItemSignpostingProcessor} for the license relation. + */ +public class ItemLicenseProcessor extends ItemSignpostingProcessor { + + private static final Logger log = Logger.getLogger(ItemLicenseProcessor.class); + + private final CreativeCommonsService creativeCommonsService = + LicenseServiceFactory.getInstance().getCreativeCommonsService(); + + public ItemLicenseProcessor(FrontendUrlService frontendUrlService) { + super(frontendUrlService); + setRelation(LinksetRelationType.LICENSE); + } + + @Override + public void addLinkSetNodes(Context context, HttpServletRequest request, + Item item, List linksetNodes) { + try { + String licenseUrl = creativeCommonsService.getLicenseURL(context, item); + if (StringUtils.isNotBlank(licenseUrl)) { + linksetNodes.add(new LinksetNode(licenseUrl, getRelation(), "text/html", buildAnchor(context, item))); + } + } catch (Exception e) { + log.error(e.getMessage(), e); + } + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemSignpostingProcessor.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemSignpostingProcessor.java new file mode 100644 index 000000000000..2ec26632a7e0 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemSignpostingProcessor.java @@ -0,0 +1,34 @@ +/** + * 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.signposting.processor.item; + +import org.dspace.app.rest.signposting.processor.AbstractSignPostingProcessor; +import org.dspace.app.rest.signposting.processor.SignPostingProcessor; +import org.dspace.content.Item; +import org.dspace.core.Context; +import org.dspace.util.FrontendUrlService; + +/** + * An abstract class represents {@link SignPostingProcessor } for an item. + * + * @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.com) + * @author Francesco Pio Scognamiglio (francescopio.scognamiglio at 4science.com) + */ +public abstract class ItemSignpostingProcessor extends AbstractSignPostingProcessor + implements SignPostingProcessor { + + protected final FrontendUrlService frontendUrlService; + + public ItemSignpostingProcessor(FrontendUrlService frontendUrlService) { + this.frontendUrlService = frontendUrlService; + } + + public String buildAnchor(Context context, Item item) { + return frontendUrlService.generateUrl(context, item); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemTypeProcessor.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemTypeProcessor.java new file mode 100644 index 000000000000..f1f41de055ee --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemTypeProcessor.java @@ -0,0 +1,61 @@ +/** + * 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.signposting.processor.item; + +import java.util.List; +import javax.servlet.http.HttpServletRequest; + +import org.apache.commons.lang3.StringUtils; +import org.apache.log4j.Logger; +import org.dspace.app.rest.signposting.model.LinksetNode; +import org.dspace.app.rest.signposting.model.LinksetRelationType; +import org.dspace.content.Item; +import org.dspace.content.service.ItemService; +import org.dspace.core.Context; +import org.dspace.util.FrontendUrlService; +import org.dspace.util.SimpleMapConverter; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * An extension of {@link ItemSignpostingProcessor} for the type relation. + * Provides links to a specific type from schema.org. + */ +public class ItemTypeProcessor extends ItemSignpostingProcessor { + + private static final Logger log = Logger.getLogger(ItemTypeProcessor.class); + private static final String ABOUT_PAGE_URI = "https://schema.org/AboutPage"; + + @Autowired + private SimpleMapConverter mapConverterDSpaceToSchemaOrgUri; + + @Autowired + private ItemService itemService; + + public ItemTypeProcessor(FrontendUrlService frontendUrlService) { + super(frontendUrlService); + setRelation(LinksetRelationType.TYPE); + } + + @Override + public void addLinkSetNodes(Context context, HttpServletRequest request, + Item item, List linksetNodes) { + try { + linksetNodes.add(new LinksetNode(ABOUT_PAGE_URI, getRelation(), "text/html", buildAnchor(context, item))); + String type = itemService.getMetadataFirstValue(item, "dc", "type", null, Item.ANY); + if (StringUtils.isNotBlank(type)) { + String typeSchemeUri = mapConverterDSpaceToSchemaOrgUri.getValue(type); + linksetNodes.add( + new LinksetNode(typeSchemeUri, getRelation(), "text/html", buildAnchor(context, item)) + ); + } + } catch (Exception e) { + log.error(e.getMessage(), e); + } + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/relation/BitstreamLicenseProcessor.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/relation/BitstreamLicenseProcessor.java deleted file mode 100644 index 99439770417c..000000000000 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/relation/BitstreamLicenseProcessor.java +++ /dev/null @@ -1,74 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.app.rest.signposting.relation; - -import java.text.MessageFormat; -import java.util.List; -import javax.servlet.http.HttpServletRequest; - -import org.apache.commons.lang.StringUtils; -import org.apache.log4j.Logger; -import org.dspace.app.rest.signposting.model.Linkset; -import org.dspace.app.rest.signposting.model.Lset; -import org.dspace.app.rest.signposting.model.Relation; -import org.dspace.app.rest.signposting.processor.BitstreamSignPostingProcessor; -import org.dspace.app.rest.signposting.processor.ItemSignPostingProcessor; -import org.dspace.content.Bitstream; -import org.dspace.content.service.BitstreamService; -import org.dspace.core.Context; -import org.springframework.beans.factory.annotation.Autowired; - -/** - * An implementation of {@link ItemSignPostingProcessor} for the license relation. - */ -public class BitstreamLicenseProcessor extends ASignPostingProcessor - implements BitstreamSignPostingProcessor { - - private static Logger log = Logger.getLogger(BitstreamLicenseProcessor.class); - - @Autowired - private BitstreamService bitstreamService; - - public BitstreamLicenseProcessor() { - setRelation("license"); - } - - @Override - public void buildRelation(Context context, HttpServletRequest request, - Bitstream bitstream, List linksets, - Linkset primaryLinkset) { - try { - String license = bitstreamService.getMetadata(bitstream, getMetadataField()); - if (StringUtils.isNotBlank(license)) { - if (StringUtils.isNotBlank(getPattern())) { - license = MessageFormat.format(getPattern(), license); - } - primaryLinkset.getLicense().add(new Relation(license, null)); - } - } catch (Exception e) { - log.error(e.getMessage(), e); - } - } - - @Override - public void buildLset(Context context, HttpServletRequest request, - Bitstream bitstream, List lsets) { - try { - String license = bitstreamService.getMetadata(bitstream, getMetadataField()); - if (StringUtils.isNotBlank(license)) { - if (StringUtils.isNotBlank(getPattern())) { - license = MessageFormat.format(getPattern(), license); - } - lsets.add(new Lset(license, getRelation(), buildAnchor(bitstream))); - } - } catch (Exception e) { - log.error(e.getMessage(), e); - } - } - -} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/relation/BitstreamLinksetProcessor.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/relation/BitstreamLinksetProcessor.java deleted file mode 100644 index 3c13b767ee0c..000000000000 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/relation/BitstreamLinksetProcessor.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.dspace.app.rest.signposting.relation; - -import java.util.List; -import javax.servlet.http.HttpServletRequest; - -import org.apache.log4j.Logger; -import org.dspace.app.rest.signposting.model.Linkset; -import org.dspace.app.rest.signposting.model.Lset; -import org.dspace.app.rest.signposting.model.Relation; -import org.dspace.app.rest.signposting.processor.BitstreamSignPostingProcessor; -import org.dspace.app.rest.signposting.processor.ItemSignPostingProcessor; -import org.dspace.content.Bitstream; -import org.dspace.content.Item; -import org.dspace.content.service.BitstreamService; -import org.dspace.core.Context; -import org.dspace.services.ConfigurationService; -import org.springframework.beans.factory.annotation.Autowired; - -/** - * An implementation of {@link ItemSignPostingProcessor} for the linkset relation. - */ -public class BitstreamLinksetProcessor extends ASignPostingProcessor - implements BitstreamSignPostingProcessor { - - private static Logger log = Logger.getLogger(BitstreamLinksetProcessor.class); - - @Autowired - private BitstreamService bitstreamService; - - @Autowired - private ConfigurationService configurationService; - - public BitstreamLinksetProcessor() { - setRelation("linkset"); - } - - @Override - public void buildRelation(Context context, HttpServletRequest request, - Bitstream bitstream, List linksets, - Linkset primaryLinkset) { - try { - Item item = (Item) bitstreamService.getParentObject(context, bitstream); - if (item != null) { - String baseUrl = configurationService.getProperty("dspace.server.url"); - String linksetUrl = baseUrl + "/signposting/linksets/" + item.getID(); - String linksetJsonUrl = baseUrl + "/signposting/linksets/" + item.getID() + "/json"; - List relations = List.of( - new Relation(linksetUrl, "application/linkset"), - new Relation(linksetJsonUrl, "application/linkset+json") - ); - primaryLinkset.getLinkset().addAll(relations); - } - } catch (Exception e) { - log.error(e.getMessage(), e); - } - } - - @Override - public void buildLset(Context context, HttpServletRequest request, - Bitstream bitstream, List lsets) { - try { - Item item = (Item) bitstreamService.getParentObject(context, bitstream); - if (item != null) { - String baseUrl = configurationService.getProperty("dspace.server.url"); - String linksetUrl = baseUrl + "/signposting/linksets/" + item.getID(); - String linksetJsonUrl = baseUrl + "/signposting/linksets/" + item.getID() + "/json"; - List links = List.of( - new Lset(linksetUrl, getRelation(), "application/linkset", buildAnchor(bitstream)), - new Lset(linksetJsonUrl, getRelation(), "application/linkset+json", buildAnchor(bitstream)) - ); - lsets.addAll(links); - } - } catch (Exception e) { - log.error(e.getMessage(), e); - } - } - -} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/relation/BitstreamPublicationBundaryProcessor.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/relation/BitstreamPublicationBundaryProcessor.java deleted file mode 100644 index 9d86856d6f90..000000000000 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/relation/BitstreamPublicationBundaryProcessor.java +++ /dev/null @@ -1,76 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.app.rest.signposting.relation; - -import java.util.List; -import javax.servlet.http.HttpServletRequest; - -import org.apache.log4j.Logger; -import org.dspace.app.rest.signposting.model.Linkset; -import org.dspace.app.rest.signposting.model.Lset; -import org.dspace.app.rest.signposting.model.Relation; -import org.dspace.app.rest.signposting.processor.BitstreamSignPostingProcessor; -import org.dspace.app.rest.signposting.processor.ItemSignPostingProcessor; -import org.dspace.content.Bitstream; -import org.dspace.content.Item; -import org.dspace.content.service.BitstreamService; -import org.dspace.core.Context; -import org.dspace.util.FrontendUrlService; -import org.springframework.beans.factory.annotation.Autowired; - -/** - * An implementation of {@link ItemSignPostingProcessor} for the collection relation. - * - * @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.com) - * @author Francesco Pio Scognamiglio (francescopio.scognamiglio at 4science.com) - */ -public class BitstreamPublicationBundaryProcessor extends ASignPostingProcessor - implements BitstreamSignPostingProcessor { - - private static Logger log = Logger.getLogger(BitstreamPublicationBundaryProcessor.class); - - @Autowired - private BitstreamService bitstreamService; - - @Autowired - private FrontendUrlService frontendUrlService; - - public BitstreamPublicationBundaryProcessor() { - setRelation("collection"); - } - - @Override - public void buildRelation(Context context, HttpServletRequest request, - Bitstream bitstream, List linksets, - Linkset primaryLinkset) { - try { - Item item = (Item) bitstreamService.getParentObject(context, bitstream); - if (item != null) { - String itemUiUrl = frontendUrlService.generateUrl(item); - primaryLinkset.getCollection().add(new Relation(itemUiUrl, "text/html")); - } - } catch (Exception e) { - log.error(e.getMessage(), e); - } - } - - @Override - public void buildLset(Context context, HttpServletRequest request, - Bitstream bitstream, List lsets) { - try { - Item item = (Item) bitstreamService.getParentObject(context, bitstream); - if (item != null) { - String itemUiUrl = frontendUrlService.generateUrl(item); - lsets.add(new Lset(itemUiUrl, getRelation(), "text/html", buildAnchor(bitstream))); - } - } catch (Exception e) { - log.error(e.getMessage(), e); - } - } - -} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/relation/BitstreamTypeProcessor.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/relation/BitstreamTypeProcessor.java deleted file mode 100644 index 5c4569e4c165..000000000000 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/relation/BitstreamTypeProcessor.java +++ /dev/null @@ -1,74 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.app.rest.signposting.relation; - -import java.text.MessageFormat; -import java.util.List; -import javax.servlet.http.HttpServletRequest; - -import org.apache.commons.lang.StringUtils; -import org.apache.log4j.Logger; -import org.dspace.app.rest.signposting.model.Linkset; -import org.dspace.app.rest.signposting.model.Lset; -import org.dspace.app.rest.signposting.model.Relation; -import org.dspace.app.rest.signposting.processor.BitstreamSignPostingProcessor; -import org.dspace.app.rest.signposting.processor.ItemSignPostingProcessor; -import org.dspace.content.Bitstream; -import org.dspace.content.service.BitstreamService; -import org.dspace.core.Context; -import org.springframework.beans.factory.annotation.Autowired; - -/** - * An implementation of {@link ItemSignPostingProcessor} for the type relation. - */ -public class BitstreamTypeProcessor extends ASignPostingProcessor - implements BitstreamSignPostingProcessor { - - private static Logger log = Logger.getLogger(BitstreamTypeProcessor.class); - - @Autowired - private BitstreamService bitstreamService; - - public BitstreamTypeProcessor() { - setRelation("type"); - } - - @Override - public void buildRelation(Context context, HttpServletRequest request, - Bitstream bitstream, List linksets, - Linkset primaryLinkset) { - try { - String type = bitstreamService.getMetadata(bitstream, getMetadataField()); - if (StringUtils.isNotBlank(type)) { - if (StringUtils.isNotBlank(getPattern())) { - type = MessageFormat.format(getPattern(), type); - } - primaryLinkset.getType().add(new Relation(type, null)); - } - } catch (Exception e) { - log.error(e.getMessage(), e); - } - } - - @Override - public void buildLset(Context context, HttpServletRequest request, - Bitstream bitstream, List lsets) { - try { - String type = bitstreamService.getMetadata(bitstream, getMetadataField()); - if (StringUtils.isNotBlank(type)) { - if (StringUtils.isNotBlank(getPattern())) { - type = MessageFormat.format(getPattern(), type); - } - lsets.add(new Lset(type, getRelation(), buildAnchor(bitstream))); - } - } catch (Exception e) { - log.error(e.getMessage(), e); - } - } - -} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/relation/ItemAuthorProcessor.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/relation/ItemAuthorProcessor.java deleted file mode 100644 index bdaecd41f201..000000000000 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/relation/ItemAuthorProcessor.java +++ /dev/null @@ -1,109 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.app.rest.signposting.relation; - -import static org.dspace.content.Item.ANY; -import static org.dspace.content.MetadataSchemaEnum.PERSON; - -import java.util.List; -import java.util.UUID; -import javax.servlet.http.HttpServletRequest; - -import org.apache.commons.lang3.StringUtils; -import org.apache.log4j.Logger; -import org.dspace.app.rest.signposting.model.Linkset; -import org.dspace.app.rest.signposting.model.Lset; -import org.dspace.app.rest.signposting.model.Relation; -import org.dspace.app.rest.signposting.processor.ItemSignPostingProcessor; -import org.dspace.content.Item; -import org.dspace.content.MetadataSchemaEnum; -import org.dspace.content.MetadataValue; -import org.dspace.content.service.ItemService; -import org.dspace.core.Context; -import org.dspace.util.FrontendUrlService; -import org.springframework.beans.factory.annotation.Autowired; - -/** - * An implementation of {@link ItemSignPostingProcessor} for the author relation. - * - * @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.com) - * @author Francesco Pio Scognamiglio (francescopio.scognamiglio at 4science.com) - */ -public class ItemAuthorProcessor extends ASignPostingProcessor - implements ItemSignPostingProcessor { - - /** log4j category */ - private static Logger log = Logger.getLogger(ItemAuthorProcessor.class); - - @Autowired - private ItemService itemService; - - @Autowired - private FrontendUrlService frontendUrlService; - - private String retrievedExternally; - - public String getRetrievedExternally() { - return retrievedExternally; - } - - public void setRetrievedExternally(String retrievedExternally) { - this.retrievedExternally = retrievedExternally; - } - - public ItemAuthorProcessor() { - setRelation("author"); - } - - @Override - public void buildRelation(Context context, HttpServletRequest request, - Item item, List linksets, Linkset primaryLinkset) { - try { - List authors = itemService - .getMetadata(item, MetadataSchemaEnum.DC.getName(), "contributor", ANY, ANY); - for (MetadataValue author : authors) { - if (author.getAuthority() != null) { - String authorUuid = author.getAuthority(); - Item authorItem = itemService.find(context, UUID.fromString(authorUuid)); - String authorOrcid = itemService - .getMetadataFirstValue(authorItem, PERSON.getName(), "identifier", "orcid", ANY); - if (StringUtils.isNotBlank(authorOrcid)) { - String href = frontendUrlService.generateUrl(authorItem); - primaryLinkset.getAuthor().add(new Relation(href, authorOrcid)); - } - } - } - } catch (Exception e) { - log.error("Problem to add signposting pattern", e); - } - } - - @Override - public void buildLset(Context context, HttpServletRequest request, - Item item, List lsets) { - try { - List authors = itemService - .getMetadata(item, MetadataSchemaEnum.DC.getName(), "contributor", ANY, ANY); - for (MetadataValue author : authors) { - if (author.getAuthority() != null) { - String authorUuid = author.getAuthority(); - Item authorItem = itemService.find(context, UUID.fromString(authorUuid)); - String authorOrcid = itemService - .getMetadataFirstValue(authorItem, PERSON.getName(), "identifier", "orcid", ANY); - if (StringUtils.isNotBlank(authorOrcid)) { - String href = frontendUrlService.generateUrl(authorItem); - lsets.add(new Lset(href, getRelation(), authorOrcid, buildAnchor(context, item))); - } - } - } - } catch (Exception e) { - log.error("Problem to add signposting pattern", e); - } - } - -} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/relation/ItemIdentifierProcessor.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/relation/ItemIdentifierProcessor.java deleted file mode 100644 index 5f5f5d86b6a7..000000000000 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/relation/ItemIdentifierProcessor.java +++ /dev/null @@ -1,89 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.app.rest.signposting.relation; - -import java.text.MessageFormat; -import java.util.List; -import javax.servlet.http.HttpServletRequest; - -import org.apache.commons.lang3.StringUtils; -import org.apache.log4j.Logger; -import org.dspace.app.rest.signposting.model.Linkset; -import org.dspace.app.rest.signposting.model.Lset; -import org.dspace.app.rest.signposting.model.Relation; -import org.dspace.app.rest.signposting.processor.ItemSignPostingProcessor; -import org.dspace.content.Item; -import org.dspace.content.MetadataValue; -import org.dspace.content.service.ItemService; -import org.dspace.core.Context; -import org.springframework.beans.factory.annotation.Autowired; - -/** - * An implementation of {@link ItemSignPostingProcessor} for the author relation. - * - * @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.com) - * @author Francesco Pio Scognamiglio (francescopio.scognamiglio at 4science.com) - */ -public class ItemIdentifierProcessor extends ASignPostingProcessor - implements ItemSignPostingProcessor { - - /** - * log4j category - */ - private static Logger log = Logger.getLogger(ItemIdentifierProcessor.class); - - @Autowired - private ItemService itemService; - - public ItemIdentifierProcessor() { - setRelation("cite-as"); - } - - @Override - public void buildRelation(Context context, HttpServletRequest request, - Item item, List linksets, Linkset primaryLinkset) { - try { - List identifiers = itemService.getMetadataByMetadataString(item, getMetadataField()); - for (MetadataValue identifier : identifiers) { - if (identifier != null) { - String identifierValue = identifier.getValue(); - if (StringUtils.isNotBlank(identifierValue)) { - if (StringUtils.isNotBlank(getPattern())) { - identifierValue = MessageFormat.format(getPattern(), identifierValue); - } - primaryLinkset.getCiteAs().add(new Relation(identifierValue, null)); - } - } - } - } catch (Exception e) { - log.error(e.getMessage(), e); - } - } - - @Override - public void buildLset(Context context, HttpServletRequest request, - Item item, List lsets) { - try { - List identifiers = itemService.getMetadataByMetadataString(item, getMetadataField()); - for (MetadataValue identifier : identifiers) { - if (identifier != null) { - String identifierValue = identifier.getValue(); - if (StringUtils.isNotBlank(identifierValue)) { - if (StringUtils.isNotBlank(getPattern())) { - identifierValue = MessageFormat.format(getPattern(), identifierValue); - } - lsets.add(new Lset(identifierValue, getRelation(), buildAnchor(context, item))); - } - } - } - } catch (Exception e) { - log.error(e.getMessage(), e); - } - } - -} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/relation/ItemLandingPageProcessor.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/relation/ItemLandingPageProcessor.java deleted file mode 100644 index 8c048a9f7c40..000000000000 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/relation/ItemLandingPageProcessor.java +++ /dev/null @@ -1,59 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.app.rest.signposting.relation; - -import java.util.List; -import javax.servlet.http.HttpServletRequest; - -import org.apache.log4j.Logger; -import org.dspace.app.rest.signposting.model.Linkset; -import org.dspace.app.rest.signposting.model.Lset; -import org.dspace.app.rest.signposting.model.Relation; -import org.dspace.app.rest.signposting.model.TypedLinkRest; -import org.dspace.app.rest.signposting.processor.ItemSignPostingProcessor; -import org.dspace.content.Item; -import org.dspace.core.Context; -import org.dspace.util.FrontendUrlService; -import org.springframework.beans.factory.annotation.Autowired; - -/** - * An implementation of {@link ItemSignPostingProcessor} for the Landing Page relation. - */ -public class ItemLandingPageProcessor extends ASignPostingProcessor implements ItemSignPostingProcessor { - - private static Logger log = Logger.getLogger(ItemLandingPageProcessor.class); - - @Autowired - private FrontendUrlService frontendUrlService; - - public ItemLandingPageProcessor() { - setRelation(TypedLinkRest.Relation.LANDING_PAGE.getName()); - } - - @Override - public void buildRelation(Context context, HttpServletRequest request, - Item item, List linksets, Linkset primaryLinkset) { - try { - String landingPageUrl = frontendUrlService.generateUrl(item); - primaryLinkset.getLandingPage().add(new Relation(landingPageUrl, "text/html")); - } catch (Exception e) { - log.error(e.getMessage(), e); - } - } - - @Override - public void buildLset(Context context, HttpServletRequest request, Item item, List lsets) { - try { - String landingPageUrl = frontendUrlService.generateUrl(item); - lsets.add(new Lset(landingPageUrl, getRelation(), "text/html", buildAnchor(context, item))); - } catch (Exception e) { - log.error(e.getMessage(), e); - } - } - -} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/relation/ItemLicenseProcessor.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/relation/ItemLicenseProcessor.java deleted file mode 100644 index 90177f8c2a93..000000000000 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/relation/ItemLicenseProcessor.java +++ /dev/null @@ -1,79 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.app.rest.signposting.relation; - -import java.text.MessageFormat; -import java.util.List; -import javax.servlet.http.HttpServletRequest; - -import org.apache.commons.lang3.StringUtils; -import org.apache.log4j.Logger; -import org.dspace.app.rest.signposting.model.Linkset; -import org.dspace.app.rest.signposting.model.Lset; -import org.dspace.app.rest.signposting.model.Relation; -import org.dspace.app.rest.signposting.processor.ItemSignPostingProcessor; -import org.dspace.content.Item; -import org.dspace.content.MetadataFieldName; -import org.dspace.content.service.ItemService; -import org.dspace.core.Context; -import org.springframework.beans.factory.annotation.Autowired; - -/** - * An implementation of {@link ItemSignPostingProcessor} for the license relation. - */ -public class ItemLicenseProcessor extends ASignPostingProcessor - implements ItemSignPostingProcessor { - - private static Logger log = Logger.getLogger(ItemLicenseProcessor.class); - - @Autowired - private ItemService itemService; - - public ItemLicenseProcessor() { - setRelation("license"); - } - - @Override - public void buildRelation(Context context, HttpServletRequest request, - Item item, List linksets, Linkset primaryLinkset) { - try { - if (StringUtils.isNotEmpty(getMetadataField())) { - String license = itemService - .getMetadataFirstValue(item, new MetadataFieldName(getMetadataField()), Item.ANY); - if (StringUtils.isNotBlank(license)) { - if (StringUtils.isNotBlank(getPattern())) { - license = MessageFormat.format(getPattern(), license); - } - primaryLinkset.getLicense().add(new Relation(license, null)); - } - } - } catch (Exception e) { - log.error(e.getMessage(), e); - } - } - - @Override - public void buildLset(Context context, HttpServletRequest request, - Item item, List lsets) { - try { - if (StringUtils.isNotEmpty(getMetadataField())) { - String license = itemService - .getMetadataFirstValue(item, new MetadataFieldName(getMetadataField()), Item.ANY); - if (StringUtils.isNotBlank(license)) { - if (StringUtils.isNotBlank(getPattern())) { - license = MessageFormat.format(getPattern(), license); - } - lsets.add(new Lset(license, getRelation(), buildAnchor(context, item))); - } - } - } catch (Exception e) { - log.error(e.getMessage(), e); - } - } - -} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/relation/ItemPublicationBundaryProcessor.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/relation/ItemPublicationBundaryProcessor.java deleted file mode 100644 index 265b991521ae..000000000000 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/relation/ItemPublicationBundaryProcessor.java +++ /dev/null @@ -1,87 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.app.rest.signposting.relation; - -import java.sql.SQLException; -import java.text.MessageFormat; -import java.util.List; -import javax.servlet.http.HttpServletRequest; - -import org.apache.log4j.Logger; -import org.dspace.app.rest.signposting.model.Linkset; -import org.dspace.app.rest.signposting.model.Lset; -import org.dspace.app.rest.signposting.model.Relation; -import org.dspace.app.rest.signposting.processor.ItemSignPostingProcessor; -import org.dspace.content.Bitstream; -import org.dspace.content.Bundle; -import org.dspace.content.Item; -import org.dspace.core.Constants; -import org.dspace.core.Context; -import org.dspace.services.ConfigurationService; -import org.springframework.beans.factory.annotation.Autowired; - -/** - * An implementation of {@link ItemSignPostingProcessor} for the item relation. - * - * @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.com) - * @author Francesco Pio Scognamiglio (francescopio.scognamiglio at 4science.com) - */ -public class ItemPublicationBundaryProcessor extends ASignPostingProcessor - implements ItemSignPostingProcessor { - - /** - * log4j category - */ - private static Logger log = Logger - .getLogger(ItemPublicationBundaryProcessor.class); - - @Autowired - private ConfigurationService configurationService; - - public ItemPublicationBundaryProcessor() { - setRelation("item"); - } - - @Override - public void buildRelation(Context context, HttpServletRequest request, - Item item, List linksets, Linkset primaryLinkset) { - String url = configurationService.getProperty("dspace.ui.url"); - try { - for (Bundle bundle : item.getBundles(Constants.CONTENT_BUNDLE_NAME)) { - for (Bitstream bitstream : bundle.getBitstreams()) { - String mimeType = bitstream.getFormat(context).getMIMEType(); - primaryLinkset.getItem().add( - new Relation( - MessageFormat.format(getPattern(), - url, "bitstreams", bitstream.getID() + "/download"), mimeType)); - } - } - } catch (SQLException e) { - log.error(e.getMessage(), e); - } - } - - @Override - public void buildLset(Context context, HttpServletRequest request, - Item item, List lsets) { - String url = configurationService.getProperty("dspace.ui.url"); - try { - for (Bundle bundle : item.getBundles(Constants.CONTENT_BUNDLE_NAME)) { - for (Bitstream bitstream : bundle.getBitstreams()) { - String mimeType = bitstream.getFormat(context).getMIMEType(); - lsets.add(new Lset(MessageFormat.format(getPattern(), - url, "bitstreams", bitstream.getID() + "/download"), - getRelation(), mimeType, buildAnchor(context, item))); - } - } - } catch (SQLException e) { - log.error(e.getMessage(), e); - } - } - -} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/relation/ItemTypeProcessor.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/relation/ItemTypeProcessor.java deleted file mode 100644 index 1d5ecb0a8992..000000000000 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/relation/ItemTypeProcessor.java +++ /dev/null @@ -1,79 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.app.rest.signposting.relation; - -import java.text.MessageFormat; -import java.util.List; -import javax.servlet.http.HttpServletRequest; - -import org.apache.commons.lang3.StringUtils; -import org.apache.log4j.Logger; -import org.dspace.app.rest.signposting.model.Linkset; -import org.dspace.app.rest.signposting.model.Lset; -import org.dspace.app.rest.signposting.model.Relation; -import org.dspace.app.rest.signposting.processor.ItemSignPostingProcessor; -import org.dspace.content.Item; -import org.dspace.content.MetadataFieldName; -import org.dspace.content.service.ItemService; -import org.dspace.core.Context; -import org.springframework.beans.factory.annotation.Autowired; - -/** - * An implementation of {@link ItemSignPostingProcessor} for the type relation. - */ -public class ItemTypeProcessor extends ASignPostingProcessor - implements ItemSignPostingProcessor { - - private static Logger log = Logger.getLogger(ItemTypeProcessor.class); - - @Autowired - private ItemService itemService; - - public ItemTypeProcessor() { - setRelation("type"); - } - - @Override - public void buildRelation(Context context, HttpServletRequest request, - Item item, List linksets, Linkset primaryLinkset) { - try { - if (StringUtils.isNotBlank(getMetadataField())) { - String itemType = itemService - .getMetadataFirstValue(item, new MetadataFieldName(getMetadataField()), Item.ANY); - if (StringUtils.isNotBlank(itemType)) { - if (StringUtils.isNotBlank(getPattern())) { - itemType = MessageFormat.format(getPattern(), itemType); - } - primaryLinkset.getType().add(new Relation(itemType, null)); - } - } - } catch (Exception e) { - log.error(e.getMessage(), e); - } - } - - @Override - public void buildLset(Context context, HttpServletRequest request, - Item item, List lsets) { - try { - if (StringUtils.isNotEmpty(getMetadataField())) { - String itemType = itemService - .getMetadataFirstValue(item, new MetadataFieldName(getMetadataField()), Item.ANY); - if (StringUtils.isNotBlank(itemType)) { - if (StringUtils.isNotBlank(getPattern())) { - itemType = MessageFormat.format(getPattern(), itemType); - } - lsets.add(new Lset(itemType, getRelation(), buildAnchor(context, item))); - } - } - } catch (Exception e) { - log.error(e.getMessage(), e); - } - } - -} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/utils/LinksetMapper.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/utils/LinksetMapper.java new file mode 100644 index 000000000000..31860143b997 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/utils/LinksetMapper.java @@ -0,0 +1,54 @@ +/** + * 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.signposting.utils; + +import java.util.List; +import java.util.stream.Collectors; + +import org.dspace.app.rest.signposting.model.Linkset; +import org.dspace.app.rest.signposting.model.LinksetNode; +import org.dspace.app.rest.signposting.model.LinksetRelation; +import org.dspace.app.rest.signposting.model.LinksetRelationType; + +/** + * Class for mapping {@link Linkset} objects. + */ +public class LinksetMapper { + + private LinksetMapper() { + } + + /** + * Converts list of linkset nodes into linkset. + * + * @param linksetNodes + * @return linkset + */ + public static Linkset map(List linksetNodes) { + Linkset linkset = new Linkset(); + linkset.setLinkset(getLinksetRelationsByType(linksetNodes, LinksetRelationType.LINKSET)); + linkset.setAuthor(getLinksetRelationsByType(linksetNodes, LinksetRelationType.AUTHOR)); + linkset.setItem(getLinksetRelationsByType(linksetNodes, LinksetRelationType.ITEM)); + linkset.setType(getLinksetRelationsByType(linksetNodes, LinksetRelationType.TYPE)); + linkset.setCollection(getLinksetRelationsByType(linksetNodes, LinksetRelationType.COLLECTION)); + linkset.setLicense(getLinksetRelationsByType(linksetNodes, LinksetRelationType.LICENSE)); + linkset.setCiteAs(getLinksetRelationsByType(linksetNodes, LinksetRelationType.CITE_AS)); + if (!linksetNodes.isEmpty()) { + linkset.setAnchor(linksetNodes.get(0).getAnchor()); + } + return linkset; + } + + private static List getLinksetRelationsByType(List linkset, + LinksetRelationType type) { + return linkset.stream() + .filter(linksetNode -> type.equals(linksetNode.getRelation())) + .map(linksetNode -> new LinksetRelation(linksetNode.getLink(), linksetNode.getType())) + .collect(Collectors.toList()); + } +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/signposting/controller/LinksetRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/signposting/controller/LinksetRestControllerIT.java index 13f578650516..39a7ef664ba6 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/signposting/controller/LinksetRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/signposting/controller/LinksetRestControllerIT.java @@ -15,6 +15,7 @@ import java.io.InputStream; import java.text.MessageFormat; +import java.util.List; import org.apache.commons.codec.CharEncoding; import org.apache.commons.io.IOUtils; @@ -22,15 +23,30 @@ import org.dspace.builder.BitstreamBuilder; import org.dspace.builder.CollectionBuilder; import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.EntityTypeBuilder; +import org.dspace.builder.GroupBuilder; import org.dspace.builder.ItemBuilder; +import org.dspace.builder.RelationshipBuilder; +import org.dspace.builder.RelationshipTypeBuilder; +import org.dspace.builder.WorkspaceItemBuilder; import org.dspace.content.Bitstream; import org.dspace.content.Collection; +import org.dspace.content.EntityType; import org.dspace.content.Item; +import org.dspace.content.MetadataSchemaEnum; +import org.dspace.content.MetadataValue; +import org.dspace.content.RelationshipType; +import org.dspace.content.WorkspaceItem; import org.dspace.content.authority.Choices; import org.dspace.content.authority.service.ChoiceAuthorityService; import org.dspace.content.authority.service.MetadataAuthorityService; +import org.dspace.content.service.BitstreamService; +import org.dspace.content.service.ItemService; +import org.dspace.content.service.RelationshipTypeService; +import org.dspace.eperson.Group; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; +import org.dspace.util.SimpleMapConverter; import org.hamcrest.Matchers; import org.junit.Before; import org.junit.Test; @@ -39,7 +55,9 @@ public class LinksetRestControllerIT extends AbstractControllerIntegrationTest { private static final String doiPattern = "https://doi.org/{0}"; + private static final String orcidPattern = "http://orcid.org/{0}"; private static final String doi = "10.1007/978-3-642-35233-1_18"; + private static final String AUTHOR = "Author"; private Collection collection; @@ -47,10 +65,22 @@ public class LinksetRestControllerIT extends AbstractControllerIntegrationTest { private ConfigurationService configurationService; @Autowired - MetadataAuthorityService metadataAuthorityService; + private MetadataAuthorityService metadataAuthorityService; @Autowired - ChoiceAuthorityService choiceAuthorityService; + private ChoiceAuthorityService choiceAuthorityService; + + @Autowired + private ItemService itemService; + + @Autowired + private BitstreamService bitstreamService; + + @Autowired + private RelationshipTypeService relationshipTypeService; + + @Autowired + private SimpleMapConverter mapConverterDSpaceToSchemaOrgUri; @Before @Override @@ -103,6 +133,60 @@ public void findOneItemJsonLinksets() throws Exception { Matchers.hasToString(MessageFormat.format(doiPattern, doi)))); } + @Test + public void findOneItemJsonLinksetsWithType() throws Exception { + String articleUri = mapConverterDSpaceToSchemaOrgUri.getValue("Article"); + context.turnOffAuthorisationSystem(); + Item item = ItemBuilder.createItem(context, collection) + .withTitle("Item Test") + .withMetadata("dc", "identifier", "doi", doi) + .withType("Article") + .build(); + context.restoreAuthSystemState(); + + getClient().perform(get("/signposting/linksets/" + item.getID() + "/json")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.linkset", + Matchers.hasSize(1))) + .andExpect(jsonPath("$.linkset[0].cite-as[0].href", + Matchers.hasToString(MessageFormat.format(doiPattern, doi)))) + .andExpect(jsonPath("$.linkset[0].type", + Matchers.hasSize(2))) + .andExpect(jsonPath("$.linkset[0].type[0].href", + Matchers.hasToString("https://schema.org/AboutPage"))) + .andExpect(jsonPath("$.linkset[0].type[0].type", + Matchers.hasToString("text/html"))) + .andExpect(jsonPath("$.linkset[0].type[1].href", + Matchers.hasToString(articleUri))) + .andExpect(jsonPath("$.linkset[0].type[1].type", + Matchers.hasToString("text/html"))); + } + + @Test + public void findOneItemJsonLinksetsWithLicence() throws Exception { + String licenceUrl = "https://exmple.com/licence"; + context.turnOffAuthorisationSystem(); + Item item = ItemBuilder.createItem(context, collection) + .withTitle("Item Test") + .withMetadata(MetadataSchemaEnum.DC.getName(), "rights", "uri", licenceUrl) + .build(); + context.restoreAuthSystemState(); + + getClient().perform(get("/signposting/linksets/" + item.getID() + "/json")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.linkset", + Matchers.hasSize(1))) + .andExpect(jsonPath("$.linkset[0].type[0].href", + Matchers.hasToString("https://schema.org/AboutPage"))) + .andExpect(jsonPath("$.linkset[0].type[0].type", + Matchers.hasToString("text/html"))) + .andExpect(jsonPath("$.linkset[0].license[0].href", + Matchers.hasToString(licenceUrl))) + .andExpect(jsonPath("$.linkset[0].license[0].type", + Matchers.hasToString("text/html"))); + + } + @Test public void findOneItemJsonLinksetsWithBitstreams() throws Exception { String bitstream1Content = "ThisIsSomeDummyText"; @@ -150,11 +234,82 @@ public void findOneItemJsonLinksetsWithBitstreams() throws Exception { .andExpect(jsonPath("$.linkset[0].item[1].type", Matchers.hasToString(bitstream2MimeType))) .andExpect(jsonPath("$.linkset[0].anchor", - Matchers.hasToString(url + "/handle/" + item.getHandle()))) - .andExpect(jsonPath("$.linkset[0].landingPage[0].href", - Matchers.hasToString(url + "/entities/publication/" + item.getID()))) - .andExpect(jsonPath("$.linkset[0].landingPage[0].type", - Matchers.hasToString("text/html"))); + Matchers.hasToString(url + "/entities/publication/" + item.getID()))); + } + + @Test + public void findOneItemThatIsInWorkspaceJsonLinksets() throws Exception { + context.turnOffAuthorisationSystem(); + WorkspaceItem workspaceItem = WorkspaceItemBuilder.createWorkspaceItem(context, collection) + .withTitle("Workspace Item") + .build(); + itemService.addMetadata(context, workspaceItem.getItem(), "dc", "identifier", "doi", Item.ANY, doi); + context.restoreAuthSystemState(); + + getClient().perform(get("/signposting/linksets/" + workspaceItem.getItem().getID() + "/json")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.linkset", + Matchers.hasSize(1))) + .andExpect(jsonPath("$.linkset[0].cite-as[0].href", + Matchers.hasToString(MessageFormat.format(doiPattern, doi)))); + } + + @Test + public void findOneWithdrawnItemJsonLinksets() throws Exception { + context.turnOffAuthorisationSystem(); + Item item = ItemBuilder.createItem(context, collection) + .withTitle("Withdrawn Item") + .withMetadata("dc", "identifier", "doi", doi) + .withdrawn() + .build(); + context.restoreAuthSystemState(); + + getClient().perform(get("/signposting/linksets/" + item.getID() + "/json")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.linkset", + Matchers.hasSize(1))) + .andExpect(jsonPath("$.linkset[0].cite-as[0].href", + Matchers.hasToString(MessageFormat.format(doiPattern, doi)))); + } + + @Test + public void findOneEmbargoItemJsonLinksets() throws Exception { + context.turnOffAuthorisationSystem(); + Item item = ItemBuilder.createItem(context, collection) + .withTitle("Withdrawn Item") + .withMetadata("dc", "identifier", "doi", doi) + .withIssueDate("2017-11-18") + .withEmbargoPeriod("2 week") + .build(); + context.restoreAuthSystemState(); + + getClient().perform(get("/signposting/linksets/" + item.getID() + "/json")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.linkset", + Matchers.hasSize(1))) + .andExpect(jsonPath("$.linkset[0].cite-as[0].href", + Matchers.hasToString(MessageFormat.format(doiPattern, doi)))); + } + + @Test + public void findOneRestrictedItemJsonLinksets() throws Exception { + context.turnOffAuthorisationSystem(); + Group internalGroup = GroupBuilder.createGroup(context) + .withName("Internal Group") + .build(); + Item item = ItemBuilder.createItem(context, collection) + .withTitle("Withdrawn Item") + .withMetadata("dc", "identifier", "doi", doi) + .withReaderGroup(internalGroup) + .build(); + context.restoreAuthSystemState(); + + getClient().perform(get("/signposting/linksets/" + item.getID() + "/json")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.linkset", + Matchers.hasSize(1))) + .andExpect(jsonPath("$.linkset[0].cite-as[0].href", + Matchers.hasToString(MessageFormat.format(doiPattern, doi)))); } @Test @@ -183,6 +338,20 @@ public void findOneBitstreamJsonLinksets() throws Exception { .andExpect(status().isNotFound()); } + @Test + public void findOneCollectionJsonLinksets() throws Exception { + getClient().perform(get("/signposting/linksets/" + collection.getID() + "/json") + .header("Accept", "application/linkset+json")) + .andExpect(status().isNotFound()); + } + + @Test + public void findOneCommunityJsonLinksets() throws Exception { + getClient().perform(get("/signposting/linksets/" + parentCommunity.getID() + "/json") + .header("Accept", "application/linkset+json")) + .andExpect(status().isNotFound()); + } + @Test public void findOneItemLsetLinksets() throws Exception { String bitstream1Content = "ThisIsSomeDummyText"; @@ -204,34 +373,38 @@ public void findOneItemLsetLinksets() throws Exception { context.restoreAuthSystemState(); String url = configurationService.getProperty("dspace.ui.url"); - String expectedResponse = "<" + MessageFormat.format(doiPattern, doi) + "> ; rel=\"cite-as\" ; anchor=\"" + - url + "/handle/" + item.getHandle() + "\" , <" + url + "/entities/publication/" + item.getID() + - "> ; rel=\"landing page\" ; type=\"text/html\" ; anchor=\"" + url + "/handle/" + item.getHandle() + - "\" , <" + url + "/bitstreams/" + bitstream1.getID() + "/download> ; rel=\"item\" ; " + - "type=\"text/plain\" ; anchor=\"" + url + "/handle/" + item.getHandle() + "\" ,"; + String siteAsRelation = "<" + MessageFormat.format(doiPattern, doi) + "> ; rel=\"cite-as\" ; anchor=\"" + + url + "/entities/publication/" + item.getID() + "\" ,"; + String itemRelation = "<" + url + "/bitstreams/" + bitstream1.getID() + + "/download> ; rel=\"item\" ; " + "type=\"text/plain\" ; anchor=\"" + url + "/entities/publication/" + + item.getID() + "\" ,"; + String typeRelation = " ; rel=\"type\" ; type=\"text/html\" ; anchor=\"" + + url + "/entities/publication/" + + item.getID() + "\" ,"; getClient().perform(get("/signposting/linksets/" + item.getID()) .header("Accept", "application/linkset")) - .andExpect(content().string(expectedResponse)); + .andExpect(content().string(Matchers.containsString(siteAsRelation))) + .andExpect(content().string(Matchers.containsString(itemRelation))) + .andExpect(content().string(Matchers.containsString(typeRelation))); } @Test public void findTypedLinkForItem() throws Exception { - configurationService.setProperty("choices.plugin.dc.contributor.author", "SolrAuthorAuthority"); - configurationService.setProperty("authority.controlled.dc.contributor.author", "true"); - metadataAuthorityService.clearCache(); - choiceAuthorityService.clearCache(); - String bitstreamContent = "ThisIsSomeDummyText"; String bitstreamMimeType = "text/plain"; String orcidValue = "orcidValue"; context.turnOffAuthorisationSystem(); + Item author = ItemBuilder.createItem(context, collection) - .withType("John") + .withMetadata("dspace", "entity", "type", AUTHOR) .withMetadata(PERSON.getName(), "identifier", "orcid", orcidValue) .build(); - Item item = ItemBuilder.createItem(context, collection) + List metadata = itemService.getMetadata(author, "dspace", "entity", + "type", Item.ANY, false); + itemService.removeMetadataValues(context, author, List.of(metadata.get(0))); + Item publication = ItemBuilder.createItem(context, collection) .withTitle("Item Test") .withMetadata("dc", "identifier", "doi", doi) .withAuthor("John", author.getID().toString(), Choices.CF_ACCEPTED) @@ -239,42 +412,82 @@ public void findTypedLinkForItem() throws Exception { Bitstream bitstream = null; try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { - bitstream = BitstreamBuilder.createBitstream(context, item, is) + bitstream = BitstreamBuilder.createBitstream(context, publication, is) .withName("Bitstream") .withDescription("description") .withMimeType(bitstreamMimeType) .build(); } + + EntityType publicationEntityType = EntityTypeBuilder.createEntityTypeBuilder(context, "Publication").build(); + EntityType authorEntityType = EntityTypeBuilder.createEntityTypeBuilder(context, AUTHOR).build(); + RelationshipType isAuthorOfPublicationRelationshipType = + RelationshipTypeBuilder.createRelationshipTypeBuilder(context, publicationEntityType, authorEntityType, + "isAuthorOfPublication", "isPublicationOfAuthor", + null, null, null, null).build(); + isAuthorOfPublicationRelationshipType.setTilted(RelationshipType.Tilted.LEFT); + isAuthorOfPublicationRelationshipType = + relationshipTypeService.create(context, isAuthorOfPublicationRelationshipType); + RelationshipBuilder.createRelationshipBuilder(context, publication, author, + isAuthorOfPublicationRelationshipType).build(); + context.restoreAuthSystemState(); String url = configurationService.getProperty("dspace.ui.url"); - getClient().perform(get("/signposting/links/" + item.getID()) + getClient().perform(get("/signposting/links/" + publication.getID()) .header("Accept", "application/json")) .andExpect(status().isOk()) .andExpect(jsonPath("$", Matchers.hasSize(4))) - .andExpect(jsonPath("$[0].href", - Matchers.hasToString(url + "/entities/publication/" + item.getID()))) - .andExpect(jsonPath("$[0].rel", - Matchers.hasToString("landing page"))) - .andExpect(jsonPath("$[0].type", - Matchers.hasToString("text/html"))) - .andExpect(jsonPath("$[1].href", - Matchers.hasToString(url + "/bitstreams/" + bitstream.getID() + "/download"))) - .andExpect(jsonPath("$[1].rel", - Matchers.hasToString("item"))) - .andExpect(jsonPath("$[1].type", - Matchers.hasToString("text/plain"))) - .andExpect(jsonPath("$[2].href", - Matchers.hasToString(MessageFormat.format(doiPattern, doi)))) - .andExpect(jsonPath("$[2].rel", - Matchers.hasToString("cite-as"))) - .andExpect(jsonPath("$[3].href", - Matchers.hasToString(url + "/entities/publication/" + author.getID()))) - .andExpect(jsonPath("$[3].rel", - Matchers.hasToString("author"))) - .andExpect(jsonPath("$[3].type", - Matchers.hasToString(orcidValue))); + .andExpect(jsonPath("$[?(@.href == '" + MessageFormat.format(orcidPattern, orcidValue) + "' " + + "&& @.rel == 'author' " + + "&& @.type == 'text/html')]").exists()) + .andExpect(jsonPath("$[?(@.href == '" + MessageFormat.format(doiPattern, doi) + "' " + + "&& @.rel == 'cite-as')]").exists()) + .andExpect(jsonPath("$[?(@.href == '" + url + "/bitstreams/" + bitstream.getID() + "/download' " + + "&& @.rel == 'item' " + + "&& @.type == 'text/plain')]").exists()) + .andExpect(jsonPath("$[?(@.href == 'https://schema.org/AboutPage' " + + "&& @.rel == 'type' " + + "&& @.type == 'text/html')]").exists()); + } + + @Test + public void findTypedLinkForBitstream() throws Exception { + String bitstreamContent = "ThisIsSomeDummyText"; + String bitstreamMimeType = "text/plain"; + + context.turnOffAuthorisationSystem(); + Item item = ItemBuilder.createItem(context, collection) + .withTitle("Item Test") + .withMetadata("dc", "identifier", "doi", doi) + .build(); + + Bitstream bitstream = null; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + bitstream = BitstreamBuilder.createBitstream(context, item, is) + .withName("Bitstream") + .withDescription("description") + .withMimeType(bitstreamMimeType) + .build(); + } + context.restoreAuthSystemState(); + + String uiUrl = configurationService.getProperty("dspace.ui.url"); + getClient().perform(get("/signposting/links/" + bitstream.getID()) + .header("Accept", "application/json")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", + Matchers.hasSize(3))) + .andExpect(jsonPath("$[?(@.href == '" + uiUrl + "/entities/publication/" + item.getID() + "' " + + "&& @.rel == 'collection' " + + "&& @.type == 'text/html')]").exists()) + .andExpect(jsonPath("$[?(@.href == '" + uiUrl + "/signposting/linksets/" + item.getID() + "' " + + "&& @.rel == 'linkset' " + + "&& @.type == 'application/linkset')]").exists()) + .andExpect(jsonPath("$[?(@.href == '" + uiUrl + "/signposting/linksets/" + item.getID() + "/json" + + "' && @.rel == 'linkset' " + + "&& @.type == 'application/linkset+json')]").exists()); DSpaceServicesFactory.getInstance().getConfigurationService().reloadConfig(); metadataAuthorityService.clearCache(); @@ -282,11 +495,61 @@ public void findTypedLinkForItem() throws Exception { } @Test - public void findTypedLinkForBitstream() throws Exception { + public void findTypedLinkForBitstreamWithType() throws Exception { + String bitstreamContent = "ThisIsSomeDummyText"; + String bitstreamMimeType = "text/plain"; + + context.turnOffAuthorisationSystem(); + Item item = ItemBuilder.createItem(context, collection) + .withTitle("Item Test") + .withMetadata("dc", "identifier", "doi", doi) + .build(); + + Bitstream bitstream = null; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + bitstream = BitstreamBuilder.createBitstream(context, item, is) + .withName("Bitstream") + .withDescription("description") + .withMimeType(bitstreamMimeType) + .build(); + } + bitstreamService.addMetadata(context, bitstream, "dc", "type", null, Item.ANY, "Article"); + + context.restoreAuthSystemState(); + + String uiUrl = configurationService.getProperty("dspace.ui.url"); + getClient().perform(get("/signposting/links/" + bitstream.getID()) + .header("Accept", "application/json")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", + Matchers.hasSize(4))) + .andExpect(jsonPath("$[?(@.href == '" + uiUrl + "/entities/publication/" + item.getID() + "' " + + "&& @.rel == 'collection' " + + "&& @.type == 'text/html')]").exists()) + .andExpect(jsonPath("$[?(@.href == '" + uiUrl + "/signposting/linksets/" + item.getID() + "' " + + "&& @.rel == 'linkset' " + + "&& @.type == 'application/linkset')]").exists()) + .andExpect(jsonPath("$[?(@.href == '" + uiUrl + "/signposting/linksets/" + item.getID() + "/json" + + "' && @.rel == 'linkset' " + + "&& @.type == 'application/linkset+json')]").exists()) + .andExpect(jsonPath("$[?(@.href == 'https://schema.org/ScholarlyArticle' " + + "&& @.rel == 'type' " + + "&& @.type == 'text/html')]").exists()); + + DSpaceServicesFactory.getInstance().getConfigurationService().reloadConfig(); + metadataAuthorityService.clearCache(); + choiceAuthorityService.clearCache(); + } + + @Test + public void findTypedLinkForRestrictedBitstream() throws Exception { String bitstreamContent = "ThisIsSomeDummyText"; String bitstreamMimeType = "text/plain"; context.turnOffAuthorisationSystem(); + Group internalGroup = GroupBuilder.createGroup(context) + .withName("Internal Group") + .build(); Item item = ItemBuilder.createItem(context, collection) .withTitle("Item Test") .withMetadata("dc", "identifier", "doi", doi) @@ -298,35 +561,113 @@ public void findTypedLinkForBitstream() throws Exception { .withName("Bitstream") .withDescription("description") .withMimeType(bitstreamMimeType) + .withReaderGroup(internalGroup) .build(); } context.restoreAuthSystemState(); String uiUrl = configurationService.getProperty("dspace.ui.url"); - String serverUrl = configurationService.getProperty("dspace.server.url"); getClient().perform(get("/signposting/links/" + bitstream.getID()) .header("Accept", "application/json")) .andExpect(status().isOk()) .andExpect(jsonPath("$", Matchers.hasSize(3))) - .andExpect(jsonPath("$[0].href", - Matchers.hasToString(uiUrl + "/entities/publication/" + item.getID()))) - .andExpect(jsonPath("$[0].rel", - Matchers.hasToString("collection"))) - .andExpect(jsonPath("$[0].type", - Matchers.hasToString("text/html"))) - .andExpect(jsonPath("$[1].href", - Matchers.hasToString(serverUrl + "/signposting/linksets/" + item.getID()))) - .andExpect(jsonPath("$[1].rel", - Matchers.hasToString("linkset"))) - .andExpect(jsonPath("$[1].type", - Matchers.hasToString("application/linkset"))) - .andExpect(jsonPath("$[2].href", - Matchers.hasToString(serverUrl + "/signposting/linksets/" + item.getID() + "/json"))) - .andExpect(jsonPath("$[2].rel", - Matchers.hasToString("linkset"))) - .andExpect(jsonPath("$[2].type", - Matchers.hasToString("application/linkset+json"))); + .andExpect(jsonPath("$[?(@.href == '" + uiUrl + "/entities/publication/" + item.getID() + "' " + + "&& @.rel == 'collection' " + + "&& @.type == 'text/html')]").exists()) + .andExpect(jsonPath("$[?(@.href == '" + uiUrl + "/signposting/linksets/" + item.getID() + "' " + + "&& @.rel == 'linkset' " + + "&& @.type == 'application/linkset')]").exists()) + .andExpect(jsonPath("$[?(@.href == '" + uiUrl + "/signposting/linksets/" + item.getID() + "/json" + + "' && @.rel == 'linkset' " + + "&& @.type == 'application/linkset+json')]").exists()); + + DSpaceServicesFactory.getInstance().getConfigurationService().reloadConfig(); + metadataAuthorityService.clearCache(); + choiceAuthorityService.clearCache(); + } + + @Test + public void findTypedLinkForBitstreamUnderEmbargo() throws Exception { + String bitstreamContent = "ThisIsSomeDummyText"; + String bitstreamMimeType = "text/plain"; + + context.turnOffAuthorisationSystem(); + Item item = ItemBuilder.createItem(context, collection) + .withTitle("Item Test") + .withIssueDate("2017-10-17") + .withMetadata("dc", "identifier", "doi", doi) + .build(); + + Bitstream bitstream = null; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + bitstream = BitstreamBuilder.createBitstream(context, item, is) + .withName("Bitstream") + .withDescription("description") + .withMimeType(bitstreamMimeType) + .withEmbargoPeriod("6 months") + .build(); + } + context.restoreAuthSystemState(); + + String uiUrl = configurationService.getProperty("dspace.ui.url"); + getClient().perform(get("/signposting/links/" + bitstream.getID()) + .header("Accept", "application/json")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", + Matchers.hasSize(3))) + .andExpect(jsonPath("$[?(@.href == '" + uiUrl + "/entities/publication/" + item.getID() + "' " + + "&& @.rel == 'collection' " + + "&& @.type == 'text/html')]").exists()) + .andExpect(jsonPath("$[?(@.href == '" + uiUrl + "/signposting/linksets/" + item.getID() + "' " + + "&& @.rel == 'linkset' " + + "&& @.type == 'application/linkset')]").exists()) + .andExpect(jsonPath("$[?(@.href == '" + uiUrl + "/signposting/linksets/" + item.getID() + "/json" + + "' && @.rel == 'linkset' " + + "&& @.type == 'application/linkset+json')]").exists()); + + DSpaceServicesFactory.getInstance().getConfigurationService().reloadConfig(); + metadataAuthorityService.clearCache(); + choiceAuthorityService.clearCache(); + } + + @Test + public void findTypedLinkForBitstreamOfWorkspaceItem() throws Exception { + String bitstreamContent = "ThisIsSomeDummyText"; + String bitstreamMimeType = "text/plain"; + + context.turnOffAuthorisationSystem(); + WorkspaceItem workspaceItem = WorkspaceItemBuilder.createWorkspaceItem(context, collection) + .withTitle("Workspace Item") + .build(); + Item item = workspaceItem.getItem(); + itemService.addMetadata(context, item, "dc", "identifier", "doi", Item.ANY, doi); + + Bitstream bitstream = null; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + bitstream = BitstreamBuilder.createBitstream(context, workspaceItem.getItem(), is) + .withName("Bitstream") + .withDescription("description") + .withMimeType(bitstreamMimeType) + .build(); + } + context.restoreAuthSystemState(); + + String uiUrl = configurationService.getProperty("dspace.ui.url"); + getClient().perform(get("/signposting/links/" + bitstream.getID()) + .header("Accept", "application/json")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", + Matchers.hasSize(3))) + .andExpect(jsonPath("$[?(@.href == '" + uiUrl + "/items/" + item.getID() + "' " + + "&& @.rel == 'collection' " + + "&& @.type == 'text/html')]").exists()) + .andExpect(jsonPath("$[?(@.href == '" + uiUrl + "/signposting/linksets/" + item.getID() + "' " + + "&& @.rel == 'linkset' " + + "&& @.type == 'application/linkset')]").exists()) + .andExpect(jsonPath("$[?(@.href == '" + uiUrl + "/signposting/linksets/" + item.getID() + "/json" + + "' && @.rel == 'linkset' " + + "&& @.type == 'application/linkset+json')]").exists()); DSpaceServicesFactory.getInstance().getConfigurationService().reloadConfig(); metadataAuthorityService.clearCache(); diff --git a/dspace/config/crosswalks/signposting/mapConverter-dspace-to-schema-org-uri.properties b/dspace/config/crosswalks/signposting/mapConverter-dspace-to-schema-org-uri.properties new file mode 100644 index 000000000000..e2fef507b77a --- /dev/null +++ b/dspace/config/crosswalks/signposting/mapConverter-dspace-to-schema-org-uri.properties @@ -0,0 +1,23 @@ +# Mapping between DSpace common publication's types and a schema.org URI +Animation = https://schema.org/3DModel +Article = https://schema.org/ScholarlyArticle +Book = https://schema.org/Book +Book\ chapter = https://schema.org/Chapter +Dataset = https://schema.org/Dataset +Learning\ Object = https://schema.org/LearningResource +Image = https://schema.org/ImageObject +Image,\ 3-D = https://schema.org/3DModel +Map = https://schema.org/Map +Musical\ Score = https://schema.org/MusicComposition +Plan\ or\ blueprint = https://schema.org/Map +Preprint = https://schema.org/VisualArtwork +Presentation = https://schema.org/PresentationDigitalDocument +Recording,\ acoustical = https://schema.org/MusicRecording +Recording,\ musical = https://schema.org/MusicRecording +Recording,\ oral = https://schema.org/MusicRecording +Software = https://schema.org/SoftwareApplication +Technical\ Report = https://schema.org/Report +Thesis = https://schema.org/Thesis +Video = https://schema.org/VideoObject +Working\ Paper = https://schema.org/TechArticle +Other = https://schema.org/CreativeWork \ No newline at end of file diff --git a/dspace/config/spring/rest/signposting.xml b/dspace/config/spring/rest/signposting.xml index 33e73f3b77e1..ed0c8912eb00 100644 --- a/dspace/config/spring/rest/signposting.xml +++ b/dspace/config/spring/rest/signposting.xml @@ -1,46 +1,40 @@ + xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> - + - - - - - - + + + + + - - - - - + + + + - - - + + + + - + - + - - - + - - - + - + - - - - - - - + + From bfbf17f5772659414757565f1a8378dedd682167 Mon Sep 17 00:00:00 2001 From: Mohamed Saber Eskander Date: Wed, 31 May 2023 12:33:56 +0300 Subject: [PATCH 216/510] - refactoring and adding new test cases - update resource polices of derivative bitstreams also - check authorization when run script - added some log information --- .../bulkaccesscontrol/BulkAccessControl.java | 158 +++- .../BulkAccessControlCli.java | 48 ++ ...lkAccessControlCliScriptConfiguration.java | 23 + .../BulkAccessControlScriptConfiguration.java | 2 +- .../model/AccessConditionBitstream.java | 5 + .../model/AccessConditionItem.java | 5 + .../model/AccessControl.java | 40 - .../model/BulkAccessControlInput.java | 72 ++ .../mediafilter/MediaFilterServiceImpl.java | 82 +- .../service/MediaFilterService.java | 18 + .../dspace/content/service/ItemService.java | 7 +- .../BulkAccessControlIT.java | 704 +++++++++++++++++- .../org/dspace/builder/CollectionBuilder.java | 25 + 13 files changed, 1077 insertions(+), 112 deletions(-) delete mode 100644 dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/model/AccessControl.java create mode 100644 dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/model/BulkAccessControlInput.java 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 5c32484ec774..86061559798d 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 @@ -7,6 +7,7 @@ */ package org.dspace.app.bulkaccesscontrol; +import static org.apache.commons.collections4.CollectionUtils.isEmpty; import static org.apache.commons.collections4.CollectionUtils.isNotEmpty; import static org.dspace.authorize.ResourcePolicy.TYPE_CUSTOM; import static org.dspace.authorize.ResourcePolicy.TYPE_INHERITED; @@ -15,12 +16,15 @@ import java.io.IOException; import java.io.InputStream; import java.sql.SQLException; +import java.text.DateFormat; +import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.Date; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Optional; import java.util.TimeZone; import java.util.UUID; import java.util.function.Function; @@ -28,15 +32,16 @@ import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.commons.cli.ParseException; -import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.dspace.app.bulkaccesscontrol.exception.BulkAccessControlException; import org.dspace.app.bulkaccesscontrol.model.AccessCondition; import org.dspace.app.bulkaccesscontrol.model.AccessConditionBitstream; import org.dspace.app.bulkaccesscontrol.model.AccessConditionItem; -import org.dspace.app.bulkaccesscontrol.model.AccessControl; import org.dspace.app.bulkaccesscontrol.model.BulkAccessConditionConfiguration; +import org.dspace.app.bulkaccesscontrol.model.BulkAccessControlInput; import org.dspace.app.bulkaccesscontrol.service.BulkAccessConditionConfigurationService; +import org.dspace.app.mediafilter.factory.MediaFilterServiceFactory; +import org.dspace.app.mediafilter.service.MediaFilterService; import org.dspace.app.util.DSpaceObjectUtilsImpl; import org.dspace.app.util.service.DSpaceObjectUtils; import org.dspace.authorize.AuthorizeException; @@ -56,7 +61,10 @@ import org.dspace.discovery.indexobject.IndexableItem; import org.dspace.eperson.EPerson; import org.dspace.eperson.factory.EPersonServiceFactory; +import org.dspace.eperson.service.EPersonService; import org.dspace.scripts.DSpaceRunnable; +import org.dspace.services.ConfigurationService; +import org.dspace.services.factory.DSpaceServicesFactory; import org.dspace.submit.model.AccessConditionOption; import org.dspace.utils.DSpace; @@ -84,6 +92,12 @@ public class BulkAccessControl extends DSpaceRunnable itemAccessConditions; private Map uploadAccessConditions; @@ -92,6 +106,10 @@ public class BulkAccessControl extends DSpaceRunnable"); throw new BulkAccessControlException("wrong value for item mode<" + mode + ">"); - } else if (ADD_MODE.equals(mode) && CollectionUtils.isEmpty(accessConditions)) { + } else if (ADD_MODE.equals(mode) && isEmpty(accessConditions)) { handler.logError("accessConditions of item must be provided with mode<" + ADD_MODE + ">"); throw new BulkAccessControlException( "accessConditions of item must be provided with mode<" + ADD_MODE + ">"); @@ -208,7 +241,7 @@ private void validateBitstreamNode(AccessConditionBitstream bitstream) throws SQ } else if (!(StringUtils.equalsAny(mode, ADD_MODE, REPLACE_MODE))) { handler.logError("wrong value for bitstream mode<" + mode + ">"); throw new BulkAccessControlException("wrong value for bitstream mode<" + mode + ">"); - } else if (ADD_MODE.equals(mode) && CollectionUtils.isEmpty(accessConditions)) { + } else if (ADD_MODE.equals(mode) && isEmpty(accessConditions)) { handler.logError("accessConditions of bitstream must be provided with mode<" + ADD_MODE + ">"); throw new BulkAccessControlException( "accessConditions of bitstream must be provided with mode<" + ADD_MODE + ">"); @@ -252,7 +285,7 @@ private void validateAccessCondition(AccessCondition accessCondition) { } } - public void updateItemsAndBitstreamsPolices(AccessControl accessControl) + public void updateItemsAndBitstreamsPolices(BulkAccessControlInput accessControl) throws SQLException, SearchServiceException, AuthorizeException { int counter = 0; @@ -328,18 +361,23 @@ private DiscoverQuery buildDiscoveryQuery(String query, int start, int limit) { return discoverQuery; } - private void updateItemPolicies(Item item, AccessControl accessControl) throws SQLException, AuthorizeException { + private void updateItemPolicies(Item item, BulkAccessControlInput accessControl) + throws SQLException, AuthorizeException { + + AccessConditionItem acItem = accessControl.getItem(); - if (REPLACE_MODE.equals(accessControl.getItem().getMode())) { + if (REPLACE_MODE.equals(acItem.getMode())) { removeReadPolicies(item, TYPE_CUSTOM); removeReadPolicies(item, TYPE_INHERITED); } setItemPolicies(item, accessControl); + logInfo(acItem.getAccessConditions(), acItem.getMode(), item); } - private void setItemPolicies(Item item, AccessControl accessControl) throws SQLException, AuthorizeException { - AccessConditionItem itemControl = accessControl.getItem(); + private void setItemPolicies(Item item, BulkAccessControlInput accessControl) + throws SQLException, AuthorizeException { + accessControl .getItem() .getAccessConditions() @@ -349,7 +387,7 @@ private void setItemPolicies(Item item, AccessControl accessControl) throws SQLE itemService.adjustItemPolicies(context, item, item.getOwningCollection()); } - private void updateBitstreamsPolicies(Item item, AccessControl accessControl) { + private void updateBitstreamsPolicies(Item item, BulkAccessControlInput accessControl) { AccessConditionBitstream.Constraint constraints = accessControl.getBitstream().getConstraints(); item.getBundles(CONTENT_BUNDLE_NAME).stream() @@ -367,15 +405,18 @@ private boolean containsConstraints(AccessConditionBitstream bitstream) { isNotEmpty(bitstream.getConstraints().getUuid()); } - private void updateBitstreamPolicies(Bitstream bitstream, Item item, AccessControl accessControl) { + private void updateBitstreamPolicies(Bitstream bitstream, Item item, BulkAccessControlInput accessControl) { + + AccessConditionBitstream acBitstream = accessControl.getBitstream(); - if (REPLACE_MODE.equals(accessControl.getBitstream().getMode())) { + if (REPLACE_MODE.equals(acBitstream.getMode())) { removeReadPolicies(bitstream, TYPE_CUSTOM); removeReadPolicies(bitstream, TYPE_INHERITED); } try { setBitstreamPolicies(bitstream, item, accessControl); + logInfo(acBitstream.getAccessConditions(), acBitstream.getMode(), bitstream); } catch (SQLException | AuthorizeException e) { throw new RuntimeException(e); } @@ -390,14 +431,16 @@ private void removeReadPolicies(DSpaceObject dso, String type) { } } - private void setBitstreamPolicies(Bitstream bitstream, Item item, AccessControl accessControl) + private void setBitstreamPolicies(Bitstream bitstream, Item item, BulkAccessControlInput accessControl) throws SQLException, AuthorizeException { - AccessConditionBitstream bitstreamControl = accessControl.getBitstream(); + accessControl.getBitstream() .getAccessConditions() .forEach(accessCondition -> createResourcePolicy(bitstream, accessCondition, uploadAccessConditions.get(accessCondition.getName()))); + itemService.adjustBitstreamPolicies(context, item, item.getOwningCollection(), bitstream); + mediaFilterService.updatePoliciesOfDerivativeBitstreams(context, item, bitstream); } private void createResourcePolicy(DSpaceObject obj, AccessCondition accessCondition, @@ -415,25 +458,84 @@ private void createResourcePolicy(DSpaceObject obj, AccessCondition accessCondit } } - private void assignCurrentUserInContext() throws SQLException { - UUID uuid = getEpersonIdentifier(); - if (uuid != null) { - EPerson ePerson = EPersonServiceFactory.getInstance().getEPersonService().find(context, uuid); - context.setCurrentUser(ePerson); + /** + * Set the eperson in the context + * + * @param context the context + * @throws SQLException if database error + */ + protected void setEPerson(Context context) throws SQLException { + EPerson myEPerson = epersonService.find(context, this.getEpersonIdentifier()); + + if (myEPerson == null) { + handler.logError("EPerson cannot be found: " + this.getEpersonIdentifier()); + throw new UnsupportedOperationException("EPerson cannot be found: " + this.getEpersonIdentifier()); } + + context.setCurrentUser(myEPerson); } - private void assignSpecialGroupsInContext() { - for (UUID uuid : handler.getSpecialGroups()) { - context.setSpecialGroup(uuid); + private void logInfo(List accessConditions, String mode, DSpaceObject dso) { + String type = dso.getClass().getSimpleName(); + + if (REPLACE_MODE.equals(mode) && isEmpty(accessConditions)) { + handler.logInfo("Cleaning " + type + " {" + dso.getID() + "} policies"); + handler.logInfo("Inheriting policies from owning Collection in " + type + " {" + dso.getID() + "}"); + return; } + + StringBuilder message = new StringBuilder(); + message.append(mode.equals(ADD_MODE) ? "Adding " : "Replacing ") + .append(type) + .append(" {") + .append(dso.getID()) + .append("} policy") + .append(mode.equals(ADD_MODE) ? " with " : " to ") + .append("access conditions:"); + + AppendAccessConditionsInfo(message, accessConditions); + + handler.logInfo(message.toString()); + + if (REPLACE_MODE.equals(mode) && isAppendModeEnabled()) { + handler.logInfo("Inheriting policies from owning Collection in " + type + " {" + dso.getID() + "}"); + } + } + + private void AppendAccessConditionsInfo(StringBuilder message, List accessConditions) { + DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); + message.append("{"); + + for (int i = 0; i < accessConditions.size(); i++) { + message.append(accessConditions.get(i).getName()); + + Optional.ofNullable(accessConditions.get(i).getStartDate()) + .ifPresent(date -> message.append(", start_date=" + dateFormat.format(date))); + + Optional.ofNullable(accessConditions.get(i).getEndDate()) + .ifPresent(date -> message.append(", end_date=" + dateFormat.format(date))); + + if (i != accessConditions.size() - 1) { + message.append(", "); + } + } + + message.append("}"); + } + + private boolean isAppendModeEnabled() { + return configurationService.getBooleanProperty("core.authorization.installitem.inheritance-read.append-mode"); + } + + protected boolean isAuthorized(Context context) { + return true; } @Override @SuppressWarnings("unchecked") public BulkAccessControlScriptConfiguration getScriptConfiguration() { return new DSpace().getServiceManager() - .getServiceByName("bulk-access-control",BulkAccessControlScriptConfiguration.class); + .getServiceByName("bulk-access-control", BulkAccessControlScriptConfiguration.class); } } diff --git a/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/BulkAccessControlCli.java b/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/BulkAccessControlCli.java index 054c642298d4..4e8cfe480eeb 100644 --- a/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/BulkAccessControlCli.java +++ b/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/BulkAccessControlCli.java @@ -7,6 +7,16 @@ */ package org.dspace.app.bulkaccesscontrol; +import java.sql.SQLException; +import java.util.Arrays; +import java.util.UUID; +import java.util.stream.Collectors; + +import org.apache.commons.lang3.StringUtils; +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; +import org.dspace.scripts.DSpaceCommandLineParameter; + /** * Extension of {@link BulkAccessControl} for CLI. * @@ -15,4 +25,42 @@ */ public class BulkAccessControlCli extends BulkAccessControl { + @Override + protected void setEPerson(Context context) throws SQLException { + EPerson myEPerson; + eperson = commandLine.getOptionValue('e'); + + if (eperson == null) { + handler.logError("An eperson to do the the Bulk Access Control must be specified " + + "(run with -h flag for details)"); + throw new UnsupportedOperationException("An eperson to do the Bulk Access Control must be specified"); + } + + if (StringUtils.contains(eperson, '@')) { + myEPerson = epersonService.findByEmail(context, eperson); + } else { + myEPerson = epersonService.find(context, UUID.fromString(eperson)); + } + + if (myEPerson == null) { + handler.logError("EPerson cannot be found: " + eperson + " (run with -h flag for details)"); + throw new UnsupportedOperationException("EPerson cannot be found: " + eperson); + } + + context.setCurrentUser(myEPerson); + } + + @Override + protected boolean isAuthorized(Context context) { + + if (context.getCurrentUser() == null) { + return false; + } + + return getScriptConfiguration().isAllowedToExecute(context, + Arrays.stream(commandLine.getOptions()) + .map(option -> + new DSpaceCommandLineParameter("-" + option.getOpt(), option.getValue())) + .collect(Collectors.toList())); + } } diff --git a/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/BulkAccessControlCliScriptConfiguration.java b/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/BulkAccessControlCliScriptConfiguration.java index 25b631c51f46..e329d8894a37 100644 --- a/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/BulkAccessControlCliScriptConfiguration.java +++ b/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/BulkAccessControlCliScriptConfiguration.java @@ -7,6 +7,10 @@ */ package org.dspace.app.bulkaccesscontrol; +import java.io.InputStream; + +import org.apache.commons.cli.Options; + /** * Extension of {@link BulkAccessControlScriptConfiguration} for CLI. * @@ -16,4 +20,23 @@ public class BulkAccessControlCliScriptConfiguration extends BulkAccessControlScriptConfiguration { + @Override + public Options getOptions() { + Options options = new Options(); + + options.addOption("u", "uuid", true, "target uuids of communities/collections/items"); + options.getOption("u").setType(String.class); + options.getOption("u").setRequired(true); + + options.addOption("f", "file", true, "source json file"); + options.getOption("f").setType(InputStream.class); + options.getOption("f").setRequired(true); + + options.addOption("e", "eperson", true, "email of eperson doing importing"); + options.getOption("e").setRequired(true); + + options.addOption("h", "help", false, "help"); + + return options; + } } diff --git a/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/BulkAccessControlScriptConfiguration.java b/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/BulkAccessControlScriptConfiguration.java index 1fa6e12abd33..590a7eaa7957 100644 --- a/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/BulkAccessControlScriptConfiguration.java +++ b/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/BulkAccessControlScriptConfiguration.java @@ -76,7 +76,7 @@ public Options getOptions() { if (options == null) { Options options = new Options(); - options.addOption("u", "target", true, "target uuids of communities/collections/items"); + options.addOption("u", "uuids", true, "target uuids of communities/collections/items"); options.getOption("u").setType(String.class); options.getOption("u").setRequired(true); diff --git a/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/model/AccessConditionBitstream.java b/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/model/AccessConditionBitstream.java index 46354ddff41c..2176e24d7f9d 100644 --- a/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/model/AccessConditionBitstream.java +++ b/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/model/AccessConditionBitstream.java @@ -10,7 +10,12 @@ import java.util.ArrayList; import java.util.List; +import org.dspace.app.bulkaccesscontrol.BulkAccessControl; + /** + * Class that model the value of bitstream node + * from json file of the {@link BulkAccessControl} + * * @author Mohamed Eskander (mohamed.eskander at 4science.it) */ public class AccessConditionBitstream { diff --git a/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/model/AccessConditionItem.java b/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/model/AccessConditionItem.java index 10f17c1c01bf..c482dfc34d65 100644 --- a/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/model/AccessConditionItem.java +++ b/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/model/AccessConditionItem.java @@ -10,7 +10,12 @@ import java.util.ArrayList; import java.util.List; +import org.dspace.app.bulkaccesscontrol.BulkAccessControl; + /** + * Class that model the value of item node + * from json file of the {@link BulkAccessControl} + * * @author Mohamed Eskander (mohamed.eskander at 4science.it) */ public class AccessConditionItem { diff --git a/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/model/AccessControl.java b/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/model/AccessControl.java deleted file mode 100644 index 8e3c11001fa8..000000000000 --- a/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/model/AccessControl.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.dspace.app.bulkaccesscontrol.model; - -public class AccessControl { - - AccessConditionItem item; - - AccessConditionBitstream bitstream; - - public AccessControl() { - } - - public AccessControl(AccessConditionItem item, - AccessConditionBitstream bitstream) { - this.item = item; - this.bitstream = bitstream; - } - - public AccessConditionItem getItem() { - return item; - } - - public void setItem(AccessConditionItem item) { - this.item = item; - } - - public AccessConditionBitstream getBitstream() { - return bitstream; - } - - public void setBitstream(AccessConditionBitstream bitstream) { - this.bitstream = bitstream; - } -} diff --git a/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/model/BulkAccessControlInput.java b/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/model/BulkAccessControlInput.java new file mode 100644 index 000000000000..0f8852a71f7d --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/model/BulkAccessControlInput.java @@ -0,0 +1,72 @@ +/** + * 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.bulkaccesscontrol.model; + +import org.dspace.app.bulkaccesscontrol.BulkAccessControl; + +/** + * Class that model the content of the JSON file used as input for the {@link BulkAccessControl} + * + *
+ * {
+ * item: {
+ * mode: "replace",
+ * accessConditions: [
+ * {
+ * "name": "openaccess"
+ * }
+ * ]
+ * },
+ * bitstream: {
+ * constraints: {
+ * uuid: [bit-uuid1, bit-uuid2, ..., bit-uuidN],
+ * },
+ * mode: "add",
+ * accessConditions: [
+ * {
+ * "name": "embargo",
+ * "startDate": "2024-06-24T23:59:59.999+0000"
+ * }
+ * ]
+ * }
+ * } + *
+ * + * @author Mohamed Eskander (mohamed.eskander at 4science.it) + */ +public class BulkAccessControlInput { + + AccessConditionItem item; + + AccessConditionBitstream bitstream; + + public BulkAccessControlInput() { + } + + public BulkAccessControlInput(AccessConditionItem item, + AccessConditionBitstream bitstream) { + this.item = item; + this.bitstream = bitstream; + } + + public AccessConditionItem getItem() { + return item; + } + + public void setItem(AccessConditionItem item) { + this.item = item; + } + + public AccessConditionBitstream getBitstream() { + return bitstream; + } + + public void setBitstream(AccessConditionBitstream bitstream) { + this.bitstream = bitstream; + } +} diff --git a/dspace-api/src/main/java/org/dspace/app/mediafilter/MediaFilterServiceImpl.java b/dspace-api/src/main/java/org/dspace/app/mediafilter/MediaFilterServiceImpl.java index 88061d1d4d37..e2c6c9c5db06 100644 --- a/dspace-api/src/main/java/org/dspace/app/mediafilter/MediaFilterServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/app/mediafilter/MediaFilterServiceImpl.java @@ -8,13 +8,17 @@ package org.dspace.app.mediafilter; import java.io.InputStream; +import java.sql.SQLException; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; +import org.apache.commons.lang3.StringUtils; import org.dspace.app.mediafilter.service.MediaFilterService; +import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.service.AuthorizeService; import org.dspace.content.Bitstream; import org.dspace.content.BitstreamFormat; @@ -388,18 +392,7 @@ public boolean processBitstream(Context context, Item item, Bitstream source, Fo bitstreamService.update(context, b); //Set permissions on the derivative bitstream - //- First remove any existing policies - authorizeService.removeAllPolicies(context, b); - - //- Determine if this is a public-derivative format - if (publicFiltersClasses.contains(formatFilter.getClass().getSimpleName())) { - //- Set derivative bitstream to be publicly accessible - Group anonymous = groupService.findByName(context, Group.ANONYMOUS); - authorizeService.addPolicy(context, b, Constants.READ, anonymous); - } else { - //- replace the policies using the same in the source bitstream - authorizeService.replaceAllPolicies(context, source, b); - } + updatePoliciesOfDerivativeBitstream(context, b, formatFilter, source); //do post-processing of the generated bitstream formatFilter.postProcessBitstream(context, item, b); @@ -421,6 +414,71 @@ public boolean processBitstream(Context context, Item item, Bitstream source, Fo return true; } + @Override + public void updatePoliciesOfDerivativeBitstreams(Context context, Item item, Bitstream source) + throws SQLException, AuthorizeException { + + if (filterClasses == null) { + return; + } + + for (FormatFilter formatFilter : filterClasses) { + for (Bitstream bitstream : findDerivativeBitstreams(item, source, formatFilter)) { + updatePoliciesOfDerivativeBitstream(context, bitstream, formatFilter, source); + } + } + } + + /** + * find derivative bitstreams related to source bitstream + * + * @param item item containing bitstreams + * @param source source bitstream + * @param formatFilter formatFilter + * @return list of derivative bitstreams from source bitstream + * @throws SQLException If something goes wrong in the database + */ + private List findDerivativeBitstreams(Item item, Bitstream source, FormatFilter formatFilter) + throws SQLException { + + String bitstreamName = formatFilter.getFilteredName(source.getName()); + List bundles = itemService.getBundles(item, formatFilter.getBundleName()); + + return bundles.stream() + .flatMap(bundle -> + bundle.getBitstreams().stream()) + .filter(bitstream -> + StringUtils.equals(bitstream.getName().trim(), bitstreamName.trim())) + .collect(Collectors.toList()); + } + + /** + * update resource polices of derivative bitstreams. + * by remove all resource policies and + * set derivative bitstreams to be publicly accessible or + * replace derivative bitstreams policies using + * the same in the source bitstream. + * + * @param context the context + * @param bitstream derivative bitstream + * @param formatFilter formatFilter + * @param source the source bitstream + * @throws SQLException If something goes wrong in the database + * @throws AuthorizeException if authorization error + */ + private void updatePoliciesOfDerivativeBitstream(Context context, Bitstream bitstream, FormatFilter formatFilter, + Bitstream source) throws SQLException, AuthorizeException { + + authorizeService.removeAllPolicies(context, bitstream); + + if (publicFiltersClasses.contains(formatFilter.getClass().getSimpleName())) { + Group anonymous = groupService.findByName(context, Group.ANONYMOUS); + authorizeService.addPolicy(context, bitstream, Constants.READ, anonymous); + } else { + authorizeService.replaceAllPolicies(context, source, bitstream); + } + } + @Override public Item getCurrentItem() { return currentItem; diff --git a/dspace-api/src/main/java/org/dspace/app/mediafilter/service/MediaFilterService.java b/dspace-api/src/main/java/org/dspace/app/mediafilter/service/MediaFilterService.java index 50a6bb3a2027..bc92ff521098 100644 --- a/dspace-api/src/main/java/org/dspace/app/mediafilter/service/MediaFilterService.java +++ b/dspace-api/src/main/java/org/dspace/app/mediafilter/service/MediaFilterService.java @@ -7,10 +7,12 @@ */ package org.dspace.app.mediafilter.service; +import java.sql.SQLException; import java.util.List; import java.util.Map; import org.dspace.app.mediafilter.FormatFilter; +import org.dspace.authorize.AuthorizeException; import org.dspace.content.Bitstream; import org.dspace.content.Collection; import org.dspace.content.Community; @@ -91,6 +93,22 @@ public void applyFiltersCollection(Context context, Collection collection) public boolean processBitstream(Context context, Item item, Bitstream source, FormatFilter formatFilter) throws Exception; + /** + * update resource polices of derivative bitstreams + * related to source bitstream. + * set derivative bitstreams to be publicly accessible or + * replace derivative bitstreams policies using + * the same in the source bitstream. + * + * @param context context + * @param item item containing bitstreams + * @param source source bitstream + * @throws SQLException If something goes wrong in the database + * @throws AuthorizeException if authorization error + */ + public void updatePoliciesOfDerivativeBitstreams(Context context, Item item, Bitstream source) + throws SQLException, AuthorizeException; + /** * Return the item that is currently being processed/filtered * by the MediaFilterManager. diff --git a/dspace-api/src/main/java/org/dspace/content/service/ItemService.java b/dspace-api/src/main/java/org/dspace/content/service/ItemService.java index 02d83808c1ff..b6bf7aa5cfa2 100644 --- a/dspace-api/src/main/java/org/dspace/content/service/ItemService.java +++ b/dspace-api/src/main/java/org/dspace/content/service/ItemService.java @@ -508,18 +508,19 @@ public void adjustBundleBitstreamPolicies(Context context, Item item, Collection throws SQLException, AuthorizeException; /** - * Adjust the Bundle and Bitstream policies to reflect what have been defined + * Adjust the Bitstream policies to reflect what have been defined * during the submission/workflow. The temporary SUBMISSION and WORKFLOW * policies are removed and the policies defined at the item and collection * level are copied and inherited as appropriate. Custom selected Item policies - * are copied to the bundle/bitstream only if no explicit custom policies were - * already applied to the bundle/bitstream. Collection's policies are inherited + * are copied to the bitstream only if no explicit custom policies were + * already applied to the bitstream. Collection's policies are inherited * if there are no other policies defined or if the append mode is defined by * the configuration via the core.authorization.installitem.inheritance-read.append-mode property * * @param context DSpace context object * @param item Item to adjust policies on * @param collection Collection + * @param bitstream Bitstream to adjust policies on * @throws SQLException If database error * @throws AuthorizeException If authorization error */ 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 c1d6a0db64ca..6335ecd417f7 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 @@ -9,13 +9,16 @@ import static org.dspace.app.matcher.ResourcePolicyMatcher.matches; import static org.dspace.authorize.ResourcePolicy.TYPE_CUSTOM; +import static org.dspace.authorize.ResourcePolicy.TYPE_INHERITED; import static org.dspace.core.Constants.CONTENT_BUNDLE_NAME; +import static org.dspace.core.Constants.DEFAULT_BUNDLE_NAME; import static org.dspace.core.Constants.READ; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.hasItems; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.is; @@ -26,30 +29,39 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.UUID; import java.util.stream.Collectors; import org.apache.commons.codec.CharEncoding; import org.apache.commons.io.IOUtils; import org.apache.commons.io.file.PathUtils; +import org.apache.commons.lang3.ArrayUtils; import org.dspace.AbstractIntegrationTestWithDatabase; import org.dspace.app.launcher.ScriptLauncher; +import org.dspace.app.mediafilter.FormatFilter; +import org.dspace.app.mediafilter.factory.MediaFilterServiceFactory; +import org.dspace.app.mediafilter.service.MediaFilterService; import org.dspace.app.scripts.handler.impl.TestDSpaceRunnableHandler; import org.dspace.authorize.ResourcePolicy; import org.dspace.builder.BitstreamBuilder; import org.dspace.builder.BundleBuilder; import org.dspace.builder.CollectionBuilder; import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.GroupBuilder; import org.dspace.builder.ItemBuilder; import org.dspace.content.Bitstream; import org.dspace.content.Bundle; import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.Item; -import org.dspace.content.factory.ContentServiceFactory; -import org.dspace.content.service.ItemService; import org.dspace.core.Constants; +import org.dspace.core.SelfNamedPlugin; +import org.dspace.core.factory.CoreServiceFactory; import org.dspace.discovery.DiscoverQuery; import org.dspace.discovery.SearchService; import org.dspace.discovery.SearchServiceException; @@ -58,6 +70,8 @@ import org.dspace.eperson.Group; import org.dspace.eperson.factory.EPersonServiceFactory; import org.dspace.eperson.service.GroupService; +import org.dspace.services.ConfigurationService; +import org.dspace.services.factory.DSpaceServicesFactory; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -68,12 +82,22 @@ * @author Mohamed Eskander (mohamed.eskander at 4science.it) */ public class BulkAccessControlIT extends AbstractIntegrationTestWithDatabase { + + //key (in dspace.cfg) which lists all enabled filters by name + private static final String MEDIA_FILTER_PLUGINS_KEY = "filter.plugins"; + + //prefix (in dspace.cfg) for all filter properties + private static final String FILTER_PREFIX = "filter"; + + //suffix (in dspace.cfg) for input formats supported by each filter + private static final String INPUT_FORMATS_SUFFIX = "inputFormats"; + private Path tempDir; private String tempFilePath; - private ItemService itemService = ContentServiceFactory.getInstance().getItemService(); private GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); private SearchService searchService = SearchUtils.getSearchService(); + private ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); @Before @Override @@ -92,12 +116,300 @@ public void destroy() throws Exception { super.destroy(); } + @Test + public void performBulkAccessWithAnonymousEPersonTest() throws Exception { + context.turnOffAuthorisationSystem(); + + Community community = CommunityBuilder.createCommunity(context) + .withName("community") + .build(); + + Collection collection = CollectionBuilder.createCollection(context, community) + .withName("collection") + .build(); + + Item item = ItemBuilder.createItem(context, collection).build(); + + context.restoreAuthSystemState(); + + String json = "{ \"item\": {\n" + + " \"mode\": \"add\",\n" + + " \"accessConditions\": [\n" + + " {\n" + + " \"name\": \"openaccess\"\n" + + " }\n" + + " ]\n" + + " }}\n"; + + buildJsonFile(json); + + String[] args = new String[] {"bulk-access-control", "-u", item.getID().toString(), "-f", tempFilePath}; + + TestDSpaceRunnableHandler testDSpaceRunnableHandler = new TestDSpaceRunnableHandler(); + ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl), testDSpaceRunnableHandler, kernelImpl); + + assertThat(testDSpaceRunnableHandler.getErrorMessages(), hasSize(1)); + assertThat(testDSpaceRunnableHandler.getWarningMessages(), empty()); + + assertThat(testDSpaceRunnableHandler.getErrorMessages(), hasItem( + containsString("An eperson to do the the Bulk Access Control must be specified") + )); + } + + @Test + public void performBulkAccessWithNotExistingEPersonTest() throws Exception { + context.turnOffAuthorisationSystem(); + + String randomUUID = UUID.randomUUID().toString(); + + Community community = CommunityBuilder.createCommunity(context) + .withName("community") + .build(); + + Collection collection = CollectionBuilder.createCollection(context, community) + .withName("collection") + .build(); + + Item item = ItemBuilder.createItem(context, collection).build(); + + context.restoreAuthSystemState(); + + String json = "{ \"item\": {\n" + + " \"mode\": \"add\",\n" + + " \"accessConditions\": [\n" + + " {\n" + + " \"name\": \"openaccess\"\n" + + " }\n" + + " ]\n" + + " }}\n"; + + buildJsonFile(json); + + String[] args = new String[] {"bulk-access-control", "-u", item.getID().toString(), "-f", tempFilePath, + "-e", randomUUID}; + + TestDSpaceRunnableHandler testDSpaceRunnableHandler = new TestDSpaceRunnableHandler(); + ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl), testDSpaceRunnableHandler, kernelImpl); + + assertThat(testDSpaceRunnableHandler.getErrorMessages(), hasSize(1)); + assertThat(testDSpaceRunnableHandler.getWarningMessages(), empty()); + + assertThat(testDSpaceRunnableHandler.getErrorMessages(), hasItem( + containsString("EPerson cannot be found: " + randomUUID) + )); + } + + @Test + public void performBulkAccessWithNotAdminEPersonTest() throws Exception { + context.turnOffAuthorisationSystem(); + + Community community = CommunityBuilder.createCommunity(context) + .withName("community") + .build(); + + Collection collection = CollectionBuilder.createCollection(context, community) + .withName("collection") + .build(); + + Item item = ItemBuilder.createItem(context, collection).build(); + + context.restoreAuthSystemState(); + + String json = "{ \"item\": {\n" + + " \"mode\": \"add\",\n" + + " \"accessConditions\": [\n" + + " {\n" + + " \"name\": \"openaccess\"\n" + + " }\n" + + " ]\n" + + " }}\n"; + + buildJsonFile(json); + + String[] args = new String[] {"bulk-access-control", "-u", item.getID().toString(), "-f", tempFilePath, + "-e", eperson.getEmail()}; + + TestDSpaceRunnableHandler testDSpaceRunnableHandler = new TestDSpaceRunnableHandler(); + ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl), testDSpaceRunnableHandler, kernelImpl); + + assertThat(testDSpaceRunnableHandler.getErrorMessages(), hasSize(1)); + assertThat(testDSpaceRunnableHandler.getWarningMessages(), empty()); + + assertThat(testDSpaceRunnableHandler.getErrorMessages(), hasItem( + containsString("Current user is not eligible to execute script bulk-access-control") + )); + } + + @Test + public void performBulkAccessWithCommunityAdminEPersonTest() throws Exception { + context.turnOffAuthorisationSystem(); + + Community community = CommunityBuilder.createCommunity(context) + .withName("community") + .withAdminGroup(eperson) + .build(); + + Collection collection = CollectionBuilder.createCollection(context, community) + .withName("collection") + .build(); + + ItemBuilder.createItem(context, collection).build(); + + context.restoreAuthSystemState(); + + String json = "{ \"item\": {\n" + + " \"mode\": \"add\",\n" + + " \"accessConditions\": [\n" + + " {\n" + + " \"name\": \"openaccess\"\n" + + " }\n" + + " ]\n" + + " }}\n"; + + buildJsonFile(json); + + String[] args = new String[] {"bulk-access-control", "-u", community.getID().toString(), "-f", tempFilePath, + "-e", eperson.getEmail()}; + + TestDSpaceRunnableHandler testDSpaceRunnableHandler = new TestDSpaceRunnableHandler(); + ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl), testDSpaceRunnableHandler, kernelImpl); + + assertThat(testDSpaceRunnableHandler.getErrorMessages(), hasSize(0)); + assertThat(testDSpaceRunnableHandler.getWarningMessages(), empty()); + } + + @Test + public void performBulkAccessWithCollectionAdminEPersonTest() throws Exception { + context.turnOffAuthorisationSystem(); + + Community community = CommunityBuilder.createCommunity(context) + .withName("community") + .build(); + + Collection collection = CollectionBuilder.createCollection(context, community) + .withName("collection") + .withAdminGroup(eperson) + .build(); + + ItemBuilder.createItem(context, collection).build(); + + context.restoreAuthSystemState(); + + String json = "{ \"item\": {\n" + + " \"mode\": \"add\",\n" + + " \"accessConditions\": [\n" + + " {\n" + + " \"name\": \"openaccess\"\n" + + " }\n" + + " ]\n" + + " }}\n"; + + buildJsonFile(json); + + String[] args = new String[] {"bulk-access-control", "-u", collection.getID().toString(), "-f", tempFilePath, + "-e", eperson.getEmail()}; + + TestDSpaceRunnableHandler testDSpaceRunnableHandler = new TestDSpaceRunnableHandler(); + ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl), testDSpaceRunnableHandler, kernelImpl); + + assertThat(testDSpaceRunnableHandler.getErrorMessages(), hasSize(0)); + assertThat(testDSpaceRunnableHandler.getWarningMessages(), empty()); + } + + @Test + public void performBulkAccessWithItemAdminEPersonTest() throws Exception { + context.turnOffAuthorisationSystem(); + + Community community = CommunityBuilder.createCommunity(context) + .withName("community") + .build(); + + Collection collection = CollectionBuilder.createCollection(context, community) + .withName("collection") + .build(); + + Item item = ItemBuilder.createItem(context, collection).withAdminUser(eperson).build(); + + context.restoreAuthSystemState(); + + String json = "{ \"item\": {\n" + + " \"mode\": \"add\",\n" + + " \"accessConditions\": [\n" + + " {\n" + + " \"name\": \"openaccess\"\n" + + " }\n" + + " ]\n" + + " }}\n"; + + buildJsonFile(json); + + String[] args = new String[] {"bulk-access-control", "-u", item.getID().toString(), "-f", tempFilePath, + "-e", eperson.getEmail()}; + + TestDSpaceRunnableHandler testDSpaceRunnableHandler = new TestDSpaceRunnableHandler(); + ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl), testDSpaceRunnableHandler, kernelImpl); + + assertThat(testDSpaceRunnableHandler.getErrorMessages(), hasSize(0)); + assertThat(testDSpaceRunnableHandler.getWarningMessages(), empty()); + } + + @Test + public void performBulkAccessWithNotCollectionAdminEPersonTest() throws Exception { + context.turnOffAuthorisationSystem(); + + Community community = CommunityBuilder.createCommunity(context) + .withName("community") + .build(); + + // add eperson to admin group + Collection collectionOne = CollectionBuilder.createCollection(context, community) + .withName("collection") + .withAdminGroup(eperson) + .build(); + + Collection collectionTwo = CollectionBuilder.createCollection(context, community) + .withName("collection") + .build(); + + ItemBuilder.createItem(context, collectionOne).build(); + ItemBuilder.createItem(context, collectionTwo).build(); + + context.restoreAuthSystemState(); + + String json = "{ \"item\": {\n" + + " \"mode\": \"add\",\n" + + " \"accessConditions\": [\n" + + " {\n" + + " \"name\": \"openaccess\"\n" + + " }\n" + + " ]\n" + + " }}\n"; + + buildJsonFile(json); + + String[] args = new String[] {"bulk-access-control", + "-u", collectionOne.getID().toString(), + "-u", collectionTwo.getID().toString(), + "-f", tempFilePath, + "-e", eperson.getEmail()}; + + TestDSpaceRunnableHandler testDSpaceRunnableHandler = new TestDSpaceRunnableHandler(); + ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl), testDSpaceRunnableHandler, kernelImpl); + + assertThat(testDSpaceRunnableHandler.getErrorMessages(), hasSize(1)); + assertThat(testDSpaceRunnableHandler.getWarningMessages(), empty()); + + assertThat(testDSpaceRunnableHandler.getErrorMessages(), hasItem( + containsString("Current user is not eligible to execute script bulk-access-control") + )); + } + @Test public void performBulkAccessWithoutRequiredParamTest() throws Exception { buildJsonFile(""); - String[] args = new String[] {"bulk-access-control", "-f", tempFilePath}; + String[] args = new String[] {"bulk-access-control", "-f", tempFilePath, "-e", admin.getEmail()}; TestDSpaceRunnableHandler testDSpaceRunnableHandler = new TestDSpaceRunnableHandler(); ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl), testDSpaceRunnableHandler, kernelImpl); @@ -128,7 +440,8 @@ public void performBulkAccessWithEmptyJsonTest() throws Exception { buildJsonFile(""); - String[] args = new String[] {"bulk-access-control", "-u", item.getID().toString(), "-f", tempFilePath}; + String[] args = new String[] {"bulk-access-control", "-u", item.getID().toString(), "-f", tempFilePath, + "-e", admin.getEmail()}; TestDSpaceRunnableHandler testDSpaceRunnableHandler = new TestDSpaceRunnableHandler(); ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl), testDSpaceRunnableHandler, kernelImpl); @@ -168,7 +481,8 @@ public void performBulkAccessWithWrongModeOfItemValueTest() throws Exception { buildJsonFile(json); - String[] args = new String[] {"bulk-access-control", "-u", item.getID().toString(), "-f", tempFilePath}; + String[] args = new String[] {"bulk-access-control", "-u", item.getID().toString(), "-f", tempFilePath, + "-e", admin.getEmail()}; TestDSpaceRunnableHandler testDSpaceRunnableHandler = new TestDSpaceRunnableHandler(); ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl), testDSpaceRunnableHandler, kernelImpl); @@ -179,8 +493,25 @@ public void performBulkAccessWithWrongModeOfItemValueTest() throws Exception { assertThat(testDSpaceRunnableHandler.getErrorMessages(), hasItem( containsString("wrong value for item mode") )); + } + + @Test + public void performBulkAccessWithMissingModeOfItemValueTest() throws Exception { + context.turnOffAuthorisationSystem(); + + Community community = CommunityBuilder.createCommunity(context) + .withName("community") + .build(); + + Collection collection = CollectionBuilder.createCollection(context, community) + .withName("collection") + .build(); + + Item item = ItemBuilder.createItem(context, collection).build(); + + context.restoreAuthSystemState(); - json = "{ \"item\": {\n" + + String json = "{ \"item\": {\n" + " \"accessConditions\": [\n" + " {\n" + " \"name\": \"openaccess\"\n" + @@ -190,9 +521,10 @@ public void performBulkAccessWithWrongModeOfItemValueTest() throws Exception { buildJsonFile(json); - args = new String[] {"bulk-access-control", "-u", item.getID().toString(), "-f", tempFilePath}; + String[] args = new String[] {"bulk-access-control", "-u", item.getID().toString(), "-f", tempFilePath, + "-e", admin.getEmail()}; - testDSpaceRunnableHandler = new TestDSpaceRunnableHandler(); + TestDSpaceRunnableHandler testDSpaceRunnableHandler = new TestDSpaceRunnableHandler(); ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl), testDSpaceRunnableHandler, kernelImpl); assertThat(testDSpaceRunnableHandler.getErrorMessages(), hasSize(1)); @@ -230,7 +562,8 @@ public void performBulkAccessWithWrongModeOfBitstreamValueTest() throws Exceptio buildJsonFile(json); - String[] args = new String[] {"bulk-access-control", "-u", item.getID().toString(), "-f", tempFilePath}; + String[] args = new String[] {"bulk-access-control", "-u", item.getID().toString(), "-f", tempFilePath, + "-e", admin.getEmail()}; TestDSpaceRunnableHandler testDSpaceRunnableHandler = new TestDSpaceRunnableHandler(); ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl), testDSpaceRunnableHandler, kernelImpl); @@ -241,8 +574,25 @@ public void performBulkAccessWithWrongModeOfBitstreamValueTest() throws Exceptio assertThat(testDSpaceRunnableHandler.getErrorMessages(), hasItem( containsString("wrong value for bitstream mode") )); + } - json = "{ \"bitstream\": {\n" + + @Test + public void performBulkAccessWithMissingModeOfBitstreamValueTest() throws Exception { + context.turnOffAuthorisationSystem(); + + Community community = CommunityBuilder.createCommunity(context) + .withName("community") + .build(); + + Collection collection = CollectionBuilder.createCollection(context, community) + .withName("collection") + .build(); + + Item item = ItemBuilder.createItem(context, collection).build(); + + context.restoreAuthSystemState(); + + String json = "{ \"bitstream\": {\n" + " \"accessConditions\": [\n" + " {\n" + " \"name\": \"openaccess\"\n" + @@ -252,9 +602,10 @@ public void performBulkAccessWithWrongModeOfBitstreamValueTest() throws Exceptio buildJsonFile(json); - args = new String[] {"bulk-access-control", "-u", item.getID().toString(), "-f", tempFilePath}; + String[] args = new String[] {"bulk-access-control", "-u", item.getID().toString(), "-f", tempFilePath, + "-e", admin.getEmail()}; - testDSpaceRunnableHandler = new TestDSpaceRunnableHandler(); + TestDSpaceRunnableHandler testDSpaceRunnableHandler = new TestDSpaceRunnableHandler(); ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl), testDSpaceRunnableHandler, kernelImpl); assertThat(testDSpaceRunnableHandler.getErrorMessages(), hasSize(1)); @@ -291,7 +642,8 @@ public void performBulkAccessWithNotFoundAccessConditionNameTest() throws Except buildJsonFile(json); - String[] args = new String[] {"bulk-access-control", "-u", item.getID().toString(), "-f", tempFilePath}; + String[] args = new String[] {"bulk-access-control", "-u", item.getID().toString(), "-f", tempFilePath, + "-e", admin.getEmail()}; TestDSpaceRunnableHandler testDSpaceRunnableHandler = new TestDSpaceRunnableHandler(); ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl), testDSpaceRunnableHandler, kernelImpl); @@ -305,7 +657,7 @@ public void performBulkAccessWithNotFoundAccessConditionNameTest() throws Except } @Test - public void performBulkAccessWithInvalidAccessConditionDateTest() throws Exception { + public void performBulkAccessWithInvalidEmbargoAccessConditionDateTest() throws Exception { context.turnOffAuthorisationSystem(); Community community = CommunityBuilder.createCommunity(context) @@ -332,7 +684,8 @@ public void performBulkAccessWithInvalidAccessConditionDateTest() throws Excepti buildJsonFile(jsonOne); - String[] args = new String[] {"bulk-access-control", "-u", item.getID().toString(), "-f", tempFilePath}; + String[] args = new String[] {"bulk-access-control", "-u", item.getID().toString(), "-f", tempFilePath, + "-e", admin.getEmail()}; TestDSpaceRunnableHandler testDSpaceRunnableHandler = new TestDSpaceRunnableHandler(); ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl), testDSpaceRunnableHandler, kernelImpl); @@ -343,8 +696,25 @@ public void performBulkAccessWithInvalidAccessConditionDateTest() throws Excepti assertThat(testDSpaceRunnableHandler.getErrorMessages(), hasItem( containsString("invalid access condition, The access condition embargo requires a start date.") )); + } + + @Test + public void performBulkAccessWithInvalidLeaseAccessConditionDateTest() throws Exception { + context.turnOffAuthorisationSystem(); + + Community community = CommunityBuilder.createCommunity(context) + .withName("community") + .build(); + + Collection collection = CollectionBuilder.createCollection(context, community) + .withName("collection") + .build(); + + Item item = ItemBuilder.createItem(context, collection).build(); - String jsonTwo = "{ \"item\": {\n" + + context.restoreAuthSystemState(); + + String json = "{ \"item\": {\n" + " \"mode\": \"add\",\n" + " \"accessConditions\": [\n" + " {\n" + @@ -354,11 +724,12 @@ public void performBulkAccessWithInvalidAccessConditionDateTest() throws Excepti " ]\n" + " }}\n"; - buildJsonFile(jsonTwo); + buildJsonFile(json); - args = new String[] {"bulk-access-control", "-u", item.getID().toString(), "-f", tempFilePath}; + String[] args = new String[] {"bulk-access-control", "-u", item.getID().toString(), "-f", tempFilePath, + "-e", admin.getEmail()}; - testDSpaceRunnableHandler = new TestDSpaceRunnableHandler(); + TestDSpaceRunnableHandler testDSpaceRunnableHandler = new TestDSpaceRunnableHandler(); ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl), testDSpaceRunnableHandler, kernelImpl); assertThat(testDSpaceRunnableHandler.getErrorMessages(), hasSize(1)); @@ -398,7 +769,8 @@ public void performBulkAccessForCommunityItemsWithBitstreamConstraintsTest() thr String[] args = new String[] {"bulk-access-control", "-u", communityOne.getID().toString(), - "-f", tempFilePath}; + "-f", tempFilePath, + "-e", admin.getEmail()}; TestDSpaceRunnableHandler testDSpaceRunnableHandler = new TestDSpaceRunnableHandler(); ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl), testDSpaceRunnableHandler, kernelImpl); @@ -444,7 +816,8 @@ public void performBulkAccessForMultipleItemsWithBitstreamConstraintsTest() thro new String[] {"bulk-access-control", "-u", communityOne.getID().toString(), "-u", communityTwo.getID().toString(), - "-f", tempFilePath}; + "-f", tempFilePath, + "-e", admin.getEmail()}; TestDSpaceRunnableHandler testDSpaceRunnableHandler = new TestDSpaceRunnableHandler(); ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl), testDSpaceRunnableHandler, kernelImpl); @@ -511,13 +884,19 @@ public void performBulkAccessForSingleItemWithBitstreamConstraintsTest() throws String[] args = new String[] {"bulk-access-control", "-u", item.getID().toString(), - "-f", tempFilePath}; + "-f", tempFilePath, + "-e", admin.getEmail()}; TestDSpaceRunnableHandler testDSpaceRunnableHandler = new TestDSpaceRunnableHandler(); ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl), testDSpaceRunnableHandler, kernelImpl); assertThat(testDSpaceRunnableHandler.getWarningMessages(), empty()); assertThat(testDSpaceRunnableHandler.getErrorMessages(), empty()); + assertThat(testDSpaceRunnableHandler.getInfoMessages(), hasSize(1)); + + assertThat(testDSpaceRunnableHandler.getInfoMessages(), hasItem( + containsString("Replacing Bitstream {" + bitstreamOne.getID() + + "} policy to access conditions:{embargo, start_date=2024-06-24}"))); bitstreamOne = context.reloadEntity(bitstreamOne); bitstreamTwo = context.reloadEntity(bitstreamTwo); @@ -553,7 +932,8 @@ public void performBulkAccessWithAddModeAndEmptyAccessConditionsTest() throws Ex buildJsonFile(jsonOne); String[] args = - new String[] {"bulk-access-control", "-u", parentCommunity.getID().toString(), "-f", tempFilePath}; + new String[] {"bulk-access-control", "-u", parentCommunity.getID().toString(), "-f", tempFilePath, + "-e", admin.getEmail()}; TestDSpaceRunnableHandler testDSpaceRunnableHandler = new TestDSpaceRunnableHandler(); ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl), testDSpaceRunnableHandler, kernelImpl); @@ -624,7 +1004,8 @@ public void performBulkAccessWithValidJsonTest() throws Exception { "-u", subCommunityOne.getID().toString(), "-u", collectionTwo.getID().toString(), "-u", itemThree.getID().toString(), - "-f", tempFilePath + "-f", tempFilePath, + "-e", admin.getEmail() }; TestDSpaceRunnableHandler testDSpaceRunnableHandler = new TestDSpaceRunnableHandler(); @@ -632,6 +1013,16 @@ public void performBulkAccessWithValidJsonTest() throws Exception { assertThat(testDSpaceRunnableHandler.getErrorMessages(), empty()); assertThat(testDSpaceRunnableHandler.getWarningMessages(), empty()); + assertThat(testDSpaceRunnableHandler.getInfoMessages(), hasSize(3)); + + assertThat(testDSpaceRunnableHandler.getInfoMessages(), containsInAnyOrder( + containsString("Replacing Item {" + itemOne.getID() + + "} policy to access conditions:{embargo, start_date=2024-06-24}"), + containsString("Replacing Item {" + itemTwo.getID() + + "} policy to access conditions:{embargo, start_date=2024-06-24}"), + containsString("Replacing Item {" + itemThree.getID() + + "} policy to access conditions:{embargo, start_date=2024-06-24}") + )); itemOne = context.reloadEntity(itemOne); itemTwo = context.reloadEntity(itemTwo); @@ -726,7 +1117,8 @@ public void performBulkAccessWithReplaceModeAndEmptyAccessConditionsTest() throw "bulk-access-control", "-u", subCommunityOne.getID().toString(), "-u", collectionTwo.getID().toString(), - "-f", tempFilePath + "-f", tempFilePath, + "-e", admin.getEmail() }; TestDSpaceRunnableHandler testDSpaceRunnableHandler = new TestDSpaceRunnableHandler(); @@ -734,6 +1126,7 @@ public void performBulkAccessWithReplaceModeAndEmptyAccessConditionsTest() throw assertThat(testDSpaceRunnableHandler.getErrorMessages(), empty()); assertThat(testDSpaceRunnableHandler.getWarningMessages(), empty()); + assertThat(testDSpaceRunnableHandler.getInfoMessages(), hasSize(60)); List itemsOfSubCommOne = findItems("location.comm:" + subCommunityOne.getID()); List itemsOfSubCommTwo = findItems("location.comm:" + subCommunityTwo.getID()); @@ -762,6 +1155,11 @@ public void performBulkAccessWithReplaceModeAndEmptyAccessConditionsTest() throw matches(Constants.READ, anonymousGroup, ResourcePolicy.TYPE_INHERITED) )); + assertThat(testDSpaceRunnableHandler.getInfoMessages(), hasItems( + containsString("Cleaning Item {" + item.getID() + "} policies"), + containsString("Inheriting policies from owning Collection in Item {" + item.getID() + "") + )); + List bitstreams = findAllBitstreams(item); for (Bitstream bitstream : bitstreams) { @@ -769,6 +1167,11 @@ public void performBulkAccessWithReplaceModeAndEmptyAccessConditionsTest() throw assertThat(bitstream.getResourcePolicies(), hasItem( matches(Constants.READ, anonymousGroup, ResourcePolicy.TYPE_INHERITED) )); + + assertThat(testDSpaceRunnableHandler.getInfoMessages(), hasItems( + containsString("Cleaning Bitstream {" + bitstream.getID() + "} policies"), + containsString("Inheriting policies from owning Collection in Bitstream {" + bitstream.getID() + "") + )); } } } @@ -840,7 +1243,8 @@ public void performBulkAccessWithAddModeTest() throws Exception { String[] args = new String[] { "bulk-access-control", "-u", subCommunityOne.getID().toString(), - "-f", tempFilePath + "-f", tempFilePath, + "-e", admin.getEmail() }; TestDSpaceRunnableHandler testDSpaceRunnableHandler = new TestDSpaceRunnableHandler(); @@ -848,6 +1252,7 @@ public void performBulkAccessWithAddModeTest() throws Exception { assertThat(testDSpaceRunnableHandler.getErrorMessages(), empty()); assertThat(testDSpaceRunnableHandler.getWarningMessages(), empty()); + assertThat(testDSpaceRunnableHandler.getInfoMessages(), hasSize(10)); List itemsOfSubCommOne = findItems("location.comm:" + subCommunityOne.getID()); @@ -945,7 +1350,8 @@ public void performBulkAccessWithReplaceModeTest() throws Exception { String[] args = new String[] { "bulk-access-control", "-u", subCommunityOne.getID().toString(), - "-f", tempFilePath + "-f", tempFilePath, + "-e", admin.getEmail() }; TestDSpaceRunnableHandler testDSpaceRunnableHandler = new TestDSpaceRunnableHandler(); @@ -953,6 +1359,7 @@ public void performBulkAccessWithReplaceModeTest() throws Exception { assertThat(testDSpaceRunnableHandler.getErrorMessages(), empty()); assertThat(testDSpaceRunnableHandler.getWarningMessages(), empty()); + assertThat(testDSpaceRunnableHandler.getInfoMessages(), hasSize(6)); List itemsOfSubCommOne = findItems("location.comm:" + subCommunityOne.getID()); @@ -969,6 +1376,11 @@ public void performBulkAccessWithReplaceModeTest() throws Exception { matches(Constants.READ, anonymousGroup, "embargo", TYPE_CUSTOM, "2024-06-24", null, null) )); + assertThat(testDSpaceRunnableHandler.getInfoMessages(), hasItem( + containsString("Replacing Item {" + item.getID() + + "} policy to access conditions:{openaccess, embargo, start_date=2024-06-24}") + )); + List bitstreams = findAllBitstreams(item); for (Bitstream bitstream : bitstreams) { @@ -977,10 +1389,246 @@ public void performBulkAccessWithReplaceModeTest() throws Exception { matches(READ, anonymousGroup, "openaccess", TYPE_CUSTOM), matches(Constants.READ, anonymousGroup, "lease", TYPE_CUSTOM, null, "2023-06-24", null) )); + + assertThat(testDSpaceRunnableHandler.getInfoMessages(), hasItem( + containsString("Replacing Bitstream {" + bitstream.getID() + + "} policy to access conditions:{openaccess, lease, end_date=2023-06-24}") + )); + } + } + } + + @Test + public void performBulkAccessAndCheckDerivativeBitstreamsPoliciesTest() throws Exception { + context.turnOffAuthorisationSystem(); + + Group anonymousGroup = groupService.findByName(context, Group.ANONYMOUS); + + Community parentCommunity = CommunityBuilder.createCommunity(context) + .withName("parent community") + .build(); + + Community subCommunityOne = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("sub community one") + .build(); + + Collection collectionOne = CollectionBuilder.createCollection(context, subCommunityOne) + .withName("collection one") + .build(); + + Item item = ItemBuilder.createItem(context, collectionOne).build(); + + Bundle bundle = BundleBuilder.createBundle(context, item) + .withName("ORIGINAL") + .build(); + + String bitstreamContent = "Dummy content"; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + BitstreamBuilder.createBitstream(context, bundle, is) + .withName("bitstream") + .withFormat("TEXT") + .withMimeType("text/plain") + .build(); + } + + List formatFilters = new ArrayList<>(); + Map> filterFormats = new HashMap<>(); + MediaFilterService mediaFilterService = MediaFilterServiceFactory.getInstance().getMediaFilterService(); + + String[] filterNames = + DSpaceServicesFactory.getInstance() + .getConfigurationService() + .getArrayProperty(MEDIA_FILTER_PLUGINS_KEY); + + + for (int i = 0; i < filterNames.length; i++) { + + //get filter of this name & add to list of filters + FormatFilter filter = + (FormatFilter) CoreServiceFactory.getInstance() + .getPluginService() + .getNamedPlugin(FormatFilter.class, filterNames[i]); + formatFilters.add(filter); + + String filterClassName = filter.getClass().getName(); + + String pluginName = null; + + if (SelfNamedPlugin.class.isAssignableFrom(filter.getClass())) { + //Get the plugin instance name for this class + pluginName = ((SelfNamedPlugin) filter).getPluginInstanceName(); } + + String[] formats = + DSpaceServicesFactory.getInstance().getConfigurationService().getArrayProperty( + FILTER_PREFIX + "." + filterClassName + + (pluginName != null ? "." + pluginName : "") + + "." + INPUT_FORMATS_SUFFIX); + + //add to internal map of filters to supported formats + if (ArrayUtils.isNotEmpty(formats)) { + filterFormats.put(filterClassName + + (pluginName != null ? MediaFilterService.FILTER_PLUGIN_SEPARATOR + + pluginName : ""), + Arrays.asList(formats)); + } + } + + mediaFilterService.setFilterClasses(formatFilters); + mediaFilterService.setFilterFormats(filterFormats); + + // here will create derivative bitstreams + mediaFilterService.applyFiltersItem(context, item); + + context.restoreAuthSystemState(); + + String jsonOne = "{ \"item\": {\n" + + " \"mode\": \"replace\",\n" + + " \"accessConditions\": [\n" + + " {\n" + + " \"name\": \"openaccess\"\n" + + " },\n" + + " {\n" + + " \"name\": \"embargo\",\n" + + " \"startDate\": \"2024-06-24\"\n" + + " }\n" + + " ]\n" + + " },\n" + + " \"bitstream\": {\n" + + " \"mode\": \"replace\",\n" + + " \"accessConditions\": [\n" + + " {\n" + + " \"name\": \"openaccess\"\n" + + " },\n" + + " {\n" + + " \"name\": \"lease\",\n" + + " \"endDate\": \"2023-06-24\"\n" + + " }\n" + + " ]\n" + + " }\n" + + "}\n"; + + buildJsonFile(jsonOne); + + String[] args = new String[] { + "bulk-access-control", + "-u", subCommunityOne.getID().toString(), + "-f", tempFilePath, + "-e", admin.getEmail() + }; + + TestDSpaceRunnableHandler testDSpaceRunnableHandler = new TestDSpaceRunnableHandler(); + ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl), testDSpaceRunnableHandler, kernelImpl); + + assertThat(testDSpaceRunnableHandler.getErrorMessages(), empty()); + assertThat(testDSpaceRunnableHandler.getWarningMessages(), empty()); + assertThat(testDSpaceRunnableHandler.getInfoMessages(), hasSize(2)); + + item = context.reloadEntity(item); + + Bundle originalBundle = item.getBundles(DEFAULT_BUNDLE_NAME).get(0); + Bundle textBundle = item.getBundles("TEXT").get(0); + + assertThat(item.getResourcePolicies(), hasSize(2)); + assertThat(item.getResourcePolicies(), containsInAnyOrder( + matches(READ, anonymousGroup, "openaccess", TYPE_CUSTOM), + matches(Constants.READ, anonymousGroup, "embargo", TYPE_CUSTOM, "2024-06-24", null, null) + )); + + assertThat(originalBundle.getBitstreams().get(0).getResourcePolicies(), hasSize(2)); + assertThat(originalBundle.getBitstreams().get(0).getResourcePolicies(), containsInAnyOrder( + matches(READ, anonymousGroup, "openaccess", TYPE_CUSTOM), + matches(Constants.READ, anonymousGroup, "lease", TYPE_CUSTOM, null, "2023-06-24", null) + )); + + assertThat(textBundle.getBitstreams().get(0).getResourcePolicies(), hasSize(2)); + assertThat(textBundle.getBitstreams().get(0).getResourcePolicies(), containsInAnyOrder( + matches(READ, anonymousGroup, "openaccess", TYPE_CUSTOM), + matches(Constants.READ, anonymousGroup, "lease", TYPE_CUSTOM, null, "2023-06-24", null) + )); + } + + @Test + public void performBulkAccessWithReplaceModeAndAppendModeIsEnabledTest() throws Exception { + context.turnOffAuthorisationSystem(); + + Group group = GroupBuilder.createGroup(context).withName("special network").build(); + + Community community = CommunityBuilder.createCommunity(context) + .withName("parent community") + .build(); + + Collection collection = CollectionBuilder.createCollection(context, community) + .withName("collection one") + .withDefaultItemRead(group) + .build(); + + Item item = ItemBuilder.createItem(context, collection).build(); + + context.restoreAuthSystemState(); + + String jsonOne = "{ \"item\": {\n" + + " \"mode\": \"replace\",\n" + + " \"accessConditions\": [\n" + + " {\n" + + " \"name\": \"embargo\",\n" + + " \"startDate\": \"2024-06-24\"\n" + + " }\n" + + " ]\n" + + " }}\n"; + + buildJsonFile(jsonOne); + + String[] args = new String[] { + "bulk-access-control", + "-u", item.getID().toString(), + "-f", tempFilePath, + "-e", admin.getEmail() + }; + + try { + configurationService.setProperty("core.authorization.installitem.inheritance-read.append-mode", true); + + TestDSpaceRunnableHandler testDSpaceRunnableHandler = new TestDSpaceRunnableHandler(); + ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl), + testDSpaceRunnableHandler, kernelImpl); + + assertThat(testDSpaceRunnableHandler.getErrorMessages(), empty()); + assertThat(testDSpaceRunnableHandler.getWarningMessages(), empty()); + assertThat(testDSpaceRunnableHandler.getInfoMessages(), hasSize(2)); + + assertThat(testDSpaceRunnableHandler.getInfoMessages(), containsInAnyOrder( + containsString("Replacing Item {" + item.getID() + "} policy to access conditions:" + + "{embargo, start_date=2024-06-24}"), + containsString("Inheriting policies from owning Collection in Item {" + item.getID() + "}") + )); + + item = context.reloadEntity(item); + + Group anonymousGroup = groupService.findByName(context, Group.ANONYMOUS); + + assertThat(item.getResourcePolicies(), hasSize(2)); + assertThat(item.getResourcePolicies(), containsInAnyOrder( + matches(Constants.READ, anonymousGroup, "embargo", TYPE_CUSTOM, "2024-06-24", null, null), + matches(Constants.READ, group, TYPE_INHERITED) + )); + } finally { + configurationService.setProperty("core.authorization.installitem.inheritance-read.append-mode", false); } } + @Test + public void performBulkAccessWithHelpParamTest() throws Exception { + + String[] args = new String[] {"bulk-access-control", "-h"}; + + TestDSpaceRunnableHandler testDSpaceRunnableHandler = new TestDSpaceRunnableHandler(); + ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl), testDSpaceRunnableHandler, kernelImpl); + + assertThat(testDSpaceRunnableHandler.getErrorMessages(), empty()); + assertThat(testDSpaceRunnableHandler.getWarningMessages(), empty()); + } + private List findItems(String query) throws SearchServiceException { DiscoverQuery discoverQuery = new DiscoverQuery(); diff --git a/dspace-api/src/test/java/org/dspace/builder/CollectionBuilder.java b/dspace-api/src/test/java/org/dspace/builder/CollectionBuilder.java index 3e8a7dc9f0f5..f287c7aa8d32 100644 --- a/dspace-api/src/test/java/org/dspace/builder/CollectionBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/CollectionBuilder.java @@ -7,6 +7,8 @@ */ package org.dspace.builder; +import static org.dspace.core.Constants.DEFAULT_ITEM_READ; + import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; @@ -15,6 +17,7 @@ import org.apache.commons.io.IOUtils; import org.dspace.authorize.AuthorizeException; +import org.dspace.authorize.ResourcePolicy; import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.MetadataSchemaEnum; @@ -237,6 +240,28 @@ public CollectionBuilder withAdminGroup(EPerson... members) throws SQLException, return this; } + /** + * remove the resource policies with type DEFAULT_ITEM_READ and + * add new policy with type DEFAULT_ITEM_READ of + * the new group to current collection. + * + * @param group the group + * @return this builder + * @throws SQLException passed through. + * @throws AuthorizeException passed through. + */ + public CollectionBuilder withDefaultItemRead(Group group) throws SQLException, AuthorizeException { + resourcePolicyService.removePolicies(context, collection, DEFAULT_ITEM_READ); + + ResourcePolicy resourcePolicy = resourcePolicyService.create(context); + resourcePolicy.setGroup(group); + resourcePolicy.setAction(DEFAULT_ITEM_READ); + resourcePolicy.setdSpaceObject(collection); + resourcePolicyService.update(context, resourcePolicy); + return this; + } + + @Override public Collection build() { try { From e6daf49f25ece0a46b9acf5c32e703d778e2e7f1 Mon Sep 17 00:00:00 2001 From: Mohamed Saber Eskander Date: Wed, 31 May 2023 13:26:28 +0300 Subject: [PATCH 217/510] added missed configuration --- .../test/data/dspaceFolder/config/spring/rest/scripts.xml | 5 +++++ dspace/config/spring/rest/scripts.xml | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/dspace-server-webapp/src/test/data/dspaceFolder/config/spring/rest/scripts.xml b/dspace-server-webapp/src/test/data/dspaceFolder/config/spring/rest/scripts.xml index fd218aa77a8d..79927833d26e 100644 --- a/dspace-server-webapp/src/test/data/dspaceFolder/config/spring/rest/scripts.xml +++ b/dspace-server-webapp/src/test/data/dspaceFolder/config/spring/rest/scripts.xml @@ -43,4 +43,9 @@
+ + + + + diff --git a/dspace/config/spring/rest/scripts.xml b/dspace/config/spring/rest/scripts.xml index 0fae31d552c1..eda8c579a89c 100644 --- a/dspace/config/spring/rest/scripts.xml +++ b/dspace/config/spring/rest/scripts.xml @@ -64,4 +64,9 @@
+ + + + + From e31a2238824e79a2b60bf2cb1902352f5e3b9845 Mon Sep 17 00:00:00 2001 From: damian Date: Wed, 31 May 2023 13:53:09 +0200 Subject: [PATCH 218/510] Item counts are now managable. --- .../java/org/dspace/browse/ItemCounter.java | 20 +++++++++++++------ dspace/config/dspace.cfg | 4 ++++ 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/browse/ItemCounter.java b/dspace-api/src/main/java/org/dspace/browse/ItemCounter.java index 27e29ad9fa1c..da88b0657ff8 100644 --- a/dspace-api/src/main/java/org/dspace/browse/ItemCounter.java +++ b/dspace-api/src/main/java/org/dspace/browse/ItemCounter.java @@ -55,6 +55,9 @@ public class ItemCounter { protected ItemService itemService; protected ConfigurationService configurationService; + private boolean showStrengths; + private boolean useCache; + /** * Construct a new item counter which will use the given DSpace Context * @@ -66,6 +69,8 @@ public ItemCounter(Context context) throws ItemCountException { this.dao = ItemCountDAOFactory.getInstance(this.context); this.itemService = ContentServiceFactory.getInstance().getItemService(); this.configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); + this.showStrengths = configurationService.getBooleanProperty("webui.strengths.show", true); + this.useCache = configurationService.getBooleanProperty("webui.strengths.cache", true); } public static ItemCounter getInstance() throws ItemCountException { @@ -76,18 +81,21 @@ public static ItemCounter getInstance() throws ItemCountException { } /** - * Get the count of the items in the given container. If the configuration - * value webui.strengths.cache is equal to 'true' this will return the - * cached value if it exists. If it is equal to 'false' it will count - * the number of items in the container in real time. + * Get the count of the items in the given container. If the configuration + * value webui.strengths.show is equal to 'true' this method will return all + * archived items. If the configuration value webui.strengths.cache + * is equal to 'true' this will return the cached value if it exists. + * If it is equal to 'false' it will count the number of items + * in the container in real time. * * @param dso DSpaceObject * @return count * @throws ItemCountException when error occurs */ public int getCount(DSpaceObject dso) throws ItemCountException { - boolean useCache = configurationService.getBooleanProperty( - "webui.strengths.cache", true); + if (!showStrengths) { + return 0; + } if (useCache) { return dao.getCount(dso); diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 65b1f951fad3..2e7ad1636bce 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -1066,6 +1066,10 @@ webui.preview.brand.fontpoint = 12 ##### Settings for item count (strength) information #### +# Whether to display collection and community strengths (i.e. item counts) +#The default behaviour is to show item counts. +#webui.strengths.show = true + # Counts fetched in real time will perform an actual count of the # index contents every time a page with this feature is requested, # which may not scale as well as a cached count. From 99d4a8447ebaa3d6d641b628bc041a3fb07cb17e Mon Sep 17 00:00:00 2001 From: Nona Luypaert Date: Wed, 31 May 2023 16:52:04 +0200 Subject: [PATCH 219/510] 102052: Add remaining CRUD methods to BundlePrimaryBitstreamLinkRepository --- .../BundlePrimaryBitstreamLinkRepository.java | 100 ++++++++++++++++++ 1 file changed, 100 insertions(+) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BundlePrimaryBitstreamLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BundlePrimaryBitstreamLinkRepository.java index ed580a21b746..c2f6dac5f228 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BundlePrimaryBitstreamLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BundlePrimaryBitstreamLinkRepository.java @@ -12,9 +12,12 @@ import javax.annotation.Nullable; import javax.servlet.http.HttpServletRequest; +import org.dspace.app.rest.exception.DSpaceBadRequestException; +import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.BitstreamRest; import org.dspace.app.rest.model.BundleRest; import org.dspace.app.rest.projection.Projection; +import org.dspace.content.Bitstream; import org.dspace.content.Bundle; import org.dspace.content.service.BundleService; import org.dspace.core.Context; @@ -34,6 +37,15 @@ public class BundlePrimaryBitstreamLinkRepository extends AbstractDSpaceRestRepo @Autowired BundleService bundleService; + /** + * Retrieves the primaryBitstream of a Bundle. + * + * @param request The HttpServletRequest if relevant + * @param bundleId The UUID of the Bundle + * @param optionalPageable The pageable if relevant + * @param projection The projection to use + * @return The primaryBitstream, or null if not found + */ @PreAuthorize("hasPermission(#bundleId, 'BUNDLE', 'READ')") public BitstreamRest getPrimaryBitstream(@Nullable HttpServletRequest request, UUID bundleId, @@ -53,4 +65,92 @@ public BitstreamRest getPrimaryBitstream(@Nullable HttpServletRequest request, throw new RuntimeException(e); } } + + /** + * Sets a primaryBitstream on a Bundle. + * + * @param context The current DSpace context + * @param bundleId The UUID of the Bundle + * @param bitstream The Bitstream to use as primaryBitstream + * @param projection The projection to use + * @return The Bundle + */ + @PreAuthorize("hasPermission(#bundleId, 'BUNDLE', 'WRITE')") + public BundleRest createPrimaryBitstream(Context context, UUID bundleId, + Bitstream bitstream, Projection projection) { + try { + Bundle bundle = setPrimaryBitstream(context, bundleId, bitstream, true); + return converter.toRest(context.reloadEntity(bundle), projection); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + + /** + * Updates a primaryBitstream on a Bundle. + * + * @param context The current DSpace context + * @param bundleId The UUID of the Bundle + * @param bitstream The Bitstream to use as primaryBitstream + * @param projection The projection to use + * @return The Bundle + */ + @PreAuthorize("hasPermission(#bundleId, 'BUNDLE', 'WRITE')") + public BundleRest updatePrimaryBitstream(Context context, UUID bundleId, + Bitstream bitstream, Projection projection) { + try { + Bundle bundle = setPrimaryBitstream(context, bundleId, bitstream, false); + return converter.toRest(context.reloadEntity(bundle), projection); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + + /** + * Deletes the primaryBitstream on a Bundle. + * + * @param context The current DSpace context + * @param bundleId The UUID of the Bundle + */ + @PreAuthorize("hasPermission(#bundleId, 'BUNDLE', 'DELETE')") + public void deletePrimaryBitstream(Context context, UUID bundleId) { + try { + Bundle bundle = setPrimaryBitstream(context, bundleId, null, false); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + + /** + * Internal method to set the primaryBitstream on a Bundle. + * + * @param context The current DSpace context + * @param bundleId The UUID of the Bundle + * @param bitstream The Bitstream to use as primaryBitstream + * @param shouldBeSet Whether a primaryBitstream is should already be set + * @return The Bundle + */ + private Bundle setPrimaryBitstream(Context context, UUID bundleId, Bitstream bitstream, boolean shouldBeSet) + throws SQLException { + Bundle bundle = bundleService.find(context, bundleId); + if (bundle == null) { + throw new ResourceNotFoundException("No such bundle: " + bundleId); + } + if (!shouldBeSet && bundle.getPrimaryBitstream() == null) { + throw new DSpaceBadRequestException("Bundle '" + bundle.getName() + + "' does not have a primary bitstream."); + } + if (shouldBeSet && bundle.getPrimaryBitstream() != null) { + throw new DSpaceBadRequestException("Bundle '" + bundle.getName() + + "' already has a primary bitstream."); + } + if (bitstream != null && !bundle.getBitstreams().contains(bitstream)) { + throw new UnprocessableEntityException("Bundle '" + bundle.getName() + "' does not contain " + + "bitstream with id: " + bitstream.getID()); + } + + bundle.setPrimaryBitstreamID(bitstream); + context.commit(); + return bundle; + } } From 2c43270152825a775388ff2c3a9f309ce609d942 Mon Sep 17 00:00:00 2001 From: Nona Luypaert Date: Wed, 31 May 2023 16:53:45 +0200 Subject: [PATCH 220/510] 102052: Create PrimaryBitstreamController for primaryBitstream endpoint + ITs --- .../app/rest/PrimaryBitstreamController.java | 157 ++++++++++ .../rest/PrimaryBitstreamControllerIT.java | 296 ++++++++++++++++++ 2 files changed, 453 insertions(+) create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/PrimaryBitstreamController.java create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/PrimaryBitstreamControllerIT.java diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/PrimaryBitstreamController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/PrimaryBitstreamController.java new file mode 100644 index 000000000000..cdcbbce7476e --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/PrimaryBitstreamController.java @@ -0,0 +1,157 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest; + +import static org.dspace.app.rest.utils.RegexUtils.REGEX_REQUESTMAPPING_IDENTIFIER_AS_UUID; +import static org.dspace.core.Constants.BITSTREAM; + +import java.util.List; +import java.util.UUID; +import javax.servlet.http.HttpServletRequest; + +import org.dspace.app.rest.converter.ConverterService; +import org.dspace.app.rest.exception.UnprocessableEntityException; +import org.dspace.app.rest.model.BitstreamRest; +import org.dspace.app.rest.model.BundleRest; +import org.dspace.app.rest.model.hateoas.BitstreamResource; +import org.dspace.app.rest.model.hateoas.BundleResource; +import org.dspace.app.rest.repository.BundlePrimaryBitstreamLinkRepository; +import org.dspace.app.rest.utils.ContextUtil; +import org.dspace.app.rest.utils.Utils; +import org.dspace.content.Bitstream; +import org.dspace.content.DSpaceObject; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.rest.webmvc.ControllerUtils; +import org.springframework.hateoas.RepresentationModel; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +/** + * This RestController is responsible for managing primaryBitstreams on bundles. + * The endpoint can be found at /api/core/bundles/{bundle-uuid}/primaryBitstream + */ +@RestController +@RequestMapping("/api/" + BundleRest.CATEGORY + "/" + BundleRest.PLURAL_NAME + + REGEX_REQUESTMAPPING_IDENTIFIER_AS_UUID + "/" + BundleRest.PRIMARY_BITSTREAM) +public class PrimaryBitstreamController { + + @Autowired + private BundlePrimaryBitstreamLinkRepository repository; + @Autowired + private ConverterService converter; + @Autowired + private Utils utils; + + /** + * This method retrieves a primaryBitstream on the given Bundle. + * Returns null if Bundle doesn't have a primaryBitstream. + *
+ * curl -X GET "http://{dspace.server.url}/api/core/bundles/{bundle-uuid}/primaryBitstream" + * + * + * @param uuid The UUID of the Bundle of which the primaryBitstream will be retrieved + * @param request The HttpServletRequest + * @return The primaryBitstream, or null if not found + */ + @PreAuthorize("hasPermission(#uuid, 'BUNDLE', 'READ')") + @RequestMapping(method = RequestMethod.GET) + public BitstreamResource getPrimaryBitstream(@PathVariable UUID uuid, + HttpServletRequest request) { + BitstreamRest bitstreamRest = repository.getPrimaryBitstream(null, uuid, null, utils.obtainProjection()); + return converter.toResource(bitstreamRest); + } + + /** + * This method creates a primaryBitstream on the given Bundle. + *
+ * curl -i -X POST "http://{dspace.server.url}/api/core/bundles/{bundle-uuid}/primaryBitstream" + * -H "Content-type:text/uri-list" + * -d "https://{dspace.server.url}/api/core/bitstreams/{bitstream-uuid}" + * + * + * @param uuid The UUID of the Bundle on which the primaryBitstream will be set + * @param request The HttpServletRequest + * @return The Bundle on which the primaryBitstream was set + */ + @PreAuthorize("hasPermission(#uuid, 'BUNDLE', 'WRITE')") + @RequestMapping(method = RequestMethod.POST, consumes = {"text/uri-list"}) + public ResponseEntity> createPrimaryBitstream(@PathVariable UUID uuid, + HttpServletRequest request) { + Context context = ContextUtil.obtainContext(request); + BundleRest bundleRest = repository.createPrimaryBitstream(context, uuid, + getBitstreamFromRequest(context, request), + utils.obtainProjection()); + return ControllerUtils.toResponseEntity(HttpStatus.CREATED, new HttpHeaders(), + (RepresentationModel) converter.toResource(bundleRest)); + } + + /** + * This method updates the primaryBitstream on the given Bundle. + *
+ * curl -i -X PUT "http://{dspace.server.url}/api/core/bundles/{bundle-uuid}/primaryBitstream" + * -H "Content-type:text/uri-list" + * -d "https://{dspace.server.url}/api/core/bitstreams/{bitstream-uuid}" + * + * + * @param uuid The UUID of the Bundle of which the primaryBitstream will be updated + * @param request The HttpServletRequest + * @return The Bundle of which the primaryBitstream was updated + */ + @PreAuthorize("hasPermission(#uuid, 'BUNDLE', 'WRITE')") + @RequestMapping(method = RequestMethod.PUT, consumes = {"text/uri-list"}) + public BundleResource updatePrimaryBitstream(@PathVariable UUID uuid, + HttpServletRequest request) { + Context context = ContextUtil.obtainContext(request); + BundleRest bundleRest = repository.updatePrimaryBitstream(context, uuid, + getBitstreamFromRequest(context, request), + utils.obtainProjection()); + return converter.toResource(bundleRest); + } + + /** + * This method deletes the primaryBitstream on the given Bundle. + *
+ * curl -i -X DELETE "http://{dspace.server.url}/api/core/bundles/{bundle-uuid}/primaryBitstream" + * + * + * @param uuid The UUID of the Bundle of which the primaryBitstream will be deleted + * @param request The HttpServletRequest + * @return The Bundle of which the primaryBitstream was deleted + */ + @PreAuthorize("hasPermission(#uuid, 'BUNDLE', 'DELETE')") + @RequestMapping(method = RequestMethod.DELETE) + public ResponseEntity> deletePrimaryBitstream(@PathVariable UUID uuid, + HttpServletRequest request) { + Context context = ContextUtil.obtainContext(request); + repository.deletePrimaryBitstream(context, uuid); + return ControllerUtils.toEmptyResponse(HttpStatus.NO_CONTENT); + } + + /** + * This method parses a URI from the request body and resolves it to a Bitstream. + * + * @param context The current DSpace context + * @param request The HttpServletRequest + * @return The resolved Bitstream + */ + private Bitstream getBitstreamFromRequest(Context context, HttpServletRequest request) { + List dsoList = utils.constructDSpaceObjectList(context, utils.getStringListFromRequest(request)); + if (dsoList.size() != 1 || dsoList.get(0).getType() != BITSTREAM) { + throw new UnprocessableEntityException("No bitstream has been specified " + + "or the data cannot be resolved to a bitstream."); + } + return (Bitstream) dsoList.get(0); + } +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/PrimaryBitstreamControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/PrimaryBitstreamControllerIT.java new file mode 100644 index 000000000000..630ef257deaa --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/PrimaryBitstreamControllerIT.java @@ -0,0 +1,296 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.io.InputStream; +import java.util.UUID; + +import org.apache.commons.codec.CharEncoding; +import org.apache.commons.io.IOUtils; +import org.dspace.app.rest.matcher.BitstreamMatcher; +import org.dspace.app.rest.matcher.BundleMatcher; +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.builder.BitstreamBuilder; +import org.dspace.builder.BundleBuilder; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.ItemBuilder; +import org.dspace.content.Bitstream; +import org.dspace.content.Bundle; +import org.dspace.content.Collection; +import org.dspace.content.Community; +import org.dspace.content.Item; +import org.dspace.content.service.BitstreamService; +import org.dspace.content.service.BundleService; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Tests for the PrimaryBitstreamController + */ +public class PrimaryBitstreamControllerIT extends AbstractControllerIntegrationTest { + + @Autowired + BundleService bundleService; + @Autowired + BitstreamService bitstreamService; + + Item item; + Bitstream bitstream; + Bundle bundle; + + @Before + @Override + public void setUp() throws Exception { + super.setUp(); + + context.turnOffAuthorisationSystem(); + Community community = CommunityBuilder.createCommunity(context).build(); + Collection collection = CollectionBuilder.createCollection(context, community).build(); + item = ItemBuilder.createItem(context, collection).build(); + + // create bitstream in ORIGINAL bundle of item + String bitstreamContent = "TEST CONTENT"; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + bitstream = BitstreamBuilder.createBitstream(context, item, is) + .withName("Bitstream") + .withMimeType("text/plain") + .build(); + } + bundle = item.getBundles("ORIGINAL").get(0); + context.restoreAuthSystemState(); + } + + @Test + public void testGetPrimaryBitstream() throws Exception { + bundle.setPrimaryBitstreamID(bitstream); + + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(get(getBundleUrl(bundle.getID()))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", BitstreamMatcher.matchProperties(bitstream))); + } + + @Test + public void testGetPrimaryBitstreamBundleNotFound() throws Exception { + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(get(getBundleUrl(UUID.randomUUID()))) + .andExpect(status().isNotFound()); + } + + @Test + public void testGetPrimaryBitstreamNonExisting() throws Exception { + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(get(getBundleUrl(bundle.getID()))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$").doesNotExist()); + } + + @Test + public void testPostPrimaryBitstream() throws Exception { + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(post(getBundleUrl(bundle.getID())) + .contentType(textUriContentType) + .content(getBitstreamUrl(bitstream.getID()))) + .andExpect(status().isCreated()) + .andExpect(jsonPath("$", BundleMatcher.matchProperties(bundle.getName(), bundle.getID(), + bundle.getHandle(), bundle.getType()))); + // verify primaryBitstream was actually added + bundle = context.reloadEntity(bundle); + Assert.assertEquals(bitstream, bundle.getPrimaryBitstream()); + } + + @Test + public void testPostPrimaryBitstreamBundleNotFound() throws Exception { + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(post(getBundleUrl(UUID.randomUUID())) + .contentType(textUriContentType) + .content(getBitstreamUrl(bitstream.getID()))) + .andExpect(status().isNotFound()); + // verify primaryBitstream is still null + bundle = context.reloadEntity(bundle); + Assert.assertNull(bundle.getPrimaryBitstream()); + } + + @Test + public void testPostPrimaryBitstreamInvalidBitstream() throws Exception { + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(post(getBundleUrl(bundle.getID())) + .contentType(textUriContentType) + .content(getBitstreamUrl(UUID.randomUUID()))) + .andExpect(status().isUnprocessableEntity()); + // verify primaryBitstream is still null + bundle = context.reloadEntity(bundle); + Assert.assertNull(bundle.getPrimaryBitstream()); + } + + @Test + public void testPostPrimaryBitstreamAlreadyExists() throws Exception { + context.turnOffAuthorisationSystem(); + bundle.setPrimaryBitstreamID(bitstream); + Bitstream bitstream2 = createSecondBitstream(bundle); + context.restoreAuthSystemState(); + + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(post(getBundleUrl(bundle.getID())) + .contentType(textUriContentType) + .content(getBitstreamUrl(bitstream2.getID()))) + .andExpect(status().isBadRequest()); + // verify primaryBitstream is still the original one + bundle = context.reloadEntity(bundle); + Assert.assertEquals(bitstream, bundle.getPrimaryBitstream()); + } + + @Test + public void testPostPrimaryBitstreamNotInBundle() throws Exception { + context.turnOffAuthorisationSystem(); + Bundle bundle2 = BundleBuilder.createBundle(context, item).withName("Bundle2").build(); + Bitstream bitstream2 = createSecondBitstream(bundle2); + context.restoreAuthSystemState(); + + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(post(getBundleUrl(bundle.getID())) + .contentType(textUriContentType) + .content(getBitstreamUrl(bitstream2.getID()))) + .andExpect(status().isUnprocessableEntity()); + // verify primaryBitstream is still null + bundle = context.reloadEntity(bundle); + Assert.assertNull(bundle.getPrimaryBitstream()); + } + + @Test + public void testUpdatePrimaryBitstream() throws Exception { + context.turnOffAuthorisationSystem(); + bundle.setPrimaryBitstreamID(bitstream); + Bitstream bitstream2 = createSecondBitstream(bundle); + context.restoreAuthSystemState(); + + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(put(getBundleUrl(bundle.getID())) + .contentType(textUriContentType) + .content(getBitstreamUrl(bitstream2.getID()))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", BundleMatcher.matchProperties(bundle.getName(), bundle.getID(), + bundle.getHandle(), bundle.getType()))); + // verify primaryBitstream was actually updated + bundle = context.reloadEntity(bundle); + Assert.assertEquals(bitstream2, bundle.getPrimaryBitstream()); + } + + @Test + public void testUpdatePrimaryBitstreamBundleNotFound() throws Exception { + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(put(getBundleUrl(UUID.randomUUID())) + .contentType(textUriContentType) + .content(getBitstreamUrl(bitstream.getID()))) + .andExpect(status().isNotFound()); + } + + @Test + public void testUpdatePrimaryBitstreamInvalidBitstream() throws Exception { + bundle.setPrimaryBitstreamID(bitstream); + + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(put(getBundleUrl(bundle.getID())) + .contentType(textUriContentType) + .content(getBitstreamUrl(UUID.randomUUID()))) + .andExpect(status().isUnprocessableEntity()); + // verify primaryBitstream is still the original one + bundle = context.reloadEntity(bundle); + Assert.assertEquals(bitstream, bundle.getPrimaryBitstream()); + } + + @Test + public void testUpdatePrimaryBitstreamNonExisting() throws Exception { + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(put(getBundleUrl(bundle.getID())) + .contentType(textUriContentType) + .content(getBitstreamUrl(bitstream.getID()))) + .andExpect(status().isBadRequest()); + // verify primaryBitstream is still null + bundle = context.reloadEntity(bundle); + Assert.assertNull(bundle.getPrimaryBitstream()); + } + + @Test + public void testUpdatePrimaryBitstreamNotInBundle() throws Exception { + context.turnOffAuthorisationSystem(); + bundle.setPrimaryBitstreamID(bitstream); + Bundle bundle2 = BundleBuilder.createBundle(context, item).withName("Bundle2").build(); + Bitstream bitstream2 = createSecondBitstream(bundle2); + context.restoreAuthSystemState(); + + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(put(getBundleUrl(bundle.getID())) + .contentType(textUriContentType) + .content(getBitstreamUrl(bitstream2.getID()))) + .andExpect(status().isUnprocessableEntity()); + // verify primaryBitstream is still the original one + bundle = context.reloadEntity(bundle); + Assert.assertEquals(bitstream, bundle.getPrimaryBitstream()); + } + + @Test + public void testDeletePrimaryBitstream() throws Exception { + bundle.setPrimaryBitstreamID(bitstream); + + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(delete(getBundleUrl(bundle.getID()))) + .andExpect(status().isNoContent()); + // verify primaryBitstream was actually deleted + bundle = context.reloadEntity(bundle); + Assert.assertNull(bundle.getPrimaryBitstream()); + // verify bitstream itself still exists + Assert.assertEquals(1, bundle.getBitstreams().size()); + Assert.assertEquals(bitstream, bundle.getBitstreams().get(0)); + } + + @Test + public void testDeletePrimaryBitstreamBundleNotFound() throws Exception { + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(delete(getBundleUrl(UUID.randomUUID()))) + .andExpect(status().isNotFound()); + } + + @Test + public void testDeletePrimaryBitstreamBundleNonExisting() throws Exception { + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(delete(getBundleUrl(bundle.getID()))) + .andExpect(status().isBadRequest()); + // verify primaryBitstream is still null + bundle = context.reloadEntity(bundle); + Assert.assertNull(bundle.getPrimaryBitstream()); + } + + private String getBundleUrl(UUID uuid) { + return "/api/core/bundles/" + uuid + "/primaryBitstream"; + } + + private String getBitstreamUrl(UUID uuid) { + return "/api/core/bitstreams/" + uuid; + } + + private Bitstream createSecondBitstream(Bundle bundle) throws Exception { + String bitstreamContent = "Second Bitstream"; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + return BitstreamBuilder.createBitstream(context, bundle, is) + .withName("Bitstream2") + .withMimeType("text/plain") + .build(); + } + } +} From b272b1fcab1f5340c35563c5840dadd01d4a7d33 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Wed, 31 May 2023 17:37:34 -0400 Subject: [PATCH 221/510] Make service lookup retry log at DEBUG; radically shorten the trace. --- .../servicemanager/DSpaceServiceManager.java | 19 +++++--- .../java/org/dspace/utils/CallStackUtils.java | 44 +++++++++++++++++++ 2 files changed, 56 insertions(+), 7 deletions(-) create mode 100644 dspace-services/src/main/java/org/dspace/utils/CallStackUtils.java diff --git a/dspace-services/src/main/java/org/dspace/servicemanager/DSpaceServiceManager.java b/dspace-services/src/main/java/org/dspace/servicemanager/DSpaceServiceManager.java index afd1627f5ee3..6cffa7ee66d5 100644 --- a/dspace-services/src/main/java/org/dspace/servicemanager/DSpaceServiceManager.java +++ b/dspace-services/src/main/java/org/dspace/servicemanager/DSpaceServiceManager.java @@ -7,6 +7,8 @@ */ package org.dspace.servicemanager; +import static org.apache.logging.log4j.Level.DEBUG; + import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; @@ -21,6 +23,8 @@ import javax.annotation.PreDestroy; import org.apache.commons.lang3.ArrayUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.kernel.Activator; import org.dspace.kernel.config.SpringLoader; import org.dspace.kernel.mixins.ConfigChangeListener; @@ -28,8 +32,7 @@ import org.dspace.kernel.mixins.ServiceManagerReadyAware; import org.dspace.servicemanager.config.DSpaceConfigurationService; import org.dspace.servicemanager.spring.DSpaceBeanFactoryPostProcessor; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import org.dspace.utils.CallStackUtils; import org.springframework.beans.BeansException; import org.springframework.beans.factory.ListableBeanFactory; import org.springframework.beans.factory.NoSuchBeanDefinitionException; @@ -44,7 +47,7 @@ */ public final class DSpaceServiceManager implements ServiceManagerSystem { - private static Logger log = LoggerFactory.getLogger(DSpaceServiceManager.class); + private static Logger log = LogManager.getLogger(); public static final String CONFIG_PATH = "spring/spring-dspace-applicationContext.xml"; public static final String CORE_RESOURCE_PATH = "classpath*:spring/spring-dspace-core-services.xml"; @@ -426,9 +429,10 @@ public T getServiceByName(String name, Class type) { service = (T) applicationContext.getBean(name, type); } catch (BeansException e) { // no luck, try the fall back option - log.warn( + log.debug( "Unable to locate bean by name or id={}." - + " Will try to look up bean by type next.", name, e); + + " Will try to look up bean by type next.", name); + CallStackUtils.logCaller(log, DEBUG); service = null; } } else { @@ -437,8 +441,9 @@ public T getServiceByName(String name, Class type) { service = (T) applicationContext.getBean(type.getName(), type); } catch (BeansException e) { // no luck, try the fall back option - log.warn("Unable to locate bean by name or id={}." - + " Will try to look up bean by type next.", type.getName(), e); + log.debug("Unable to locate bean by name or id={}." + + " Will try to look up bean by type next.", type::getName); + CallStackUtils.logCaller(log, DEBUG); service = null; } } diff --git a/dspace-services/src/main/java/org/dspace/utils/CallStackUtils.java b/dspace-services/src/main/java/org/dspace/utils/CallStackUtils.java new file mode 100644 index 000000000000..cb60a223a184 --- /dev/null +++ b/dspace-services/src/main/java/org/dspace/utils/CallStackUtils.java @@ -0,0 +1,44 @@ +/** + * 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.utils; + +import static java.lang.StackWalker.Option.RETAIN_CLASS_REFERENCE; + +import java.lang.StackWalker.StackFrame; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.Logger; + +/** + * Utility methods for manipulating call stacks. + * + * @author mwood + */ +public class CallStackUtils { + private CallStackUtils() {} + + /** + * Log the class, method and line of the caller's caller. + * + * @param log logger to use. + * @param level log at this level, if enabled. + */ + static public void logCaller(Logger log, Level level) { + if (log.isEnabled(level)) { + StackWalker stack = StackWalker.getInstance(RETAIN_CLASS_REFERENCE); + StackFrame caller = stack.walk(stream -> stream.skip(2) + .findFirst() + .get()); + String callerClassName = caller.getDeclaringClass().getCanonicalName(); + String callerMethodName = caller.getMethodName(); + int callerLine = caller.getLineNumber(); + log.log(level, "Called from {}.{} line {}.", + callerClassName, callerMethodName, callerLine); + } + } +} From f587989726aeb6ca96c668f931cd2c8b09a33845 Mon Sep 17 00:00:00 2001 From: Mohamed Saber Eskander Date: Thu, 1 Jun 2023 11:48:45 +0300 Subject: [PATCH 222/510] edited param name --- .../bulkaccesscontrol/BulkAccessControlScriptConfiguration.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/BulkAccessControlScriptConfiguration.java b/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/BulkAccessControlScriptConfiguration.java index 590a7eaa7957..5196247f94cb 100644 --- a/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/BulkAccessControlScriptConfiguration.java +++ b/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/BulkAccessControlScriptConfiguration.java @@ -76,7 +76,7 @@ public Options getOptions() { if (options == null) { Options options = new Options(); - options.addOption("u", "uuids", true, "target uuids of communities/collections/items"); + options.addOption("u", "uuid", true, "target uuids of communities/collections/items"); options.getOption("u").setType(String.class); options.getOption("u").setRequired(true); From 56c8820d31439ce613585fadf1813ee62d45d0b1 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Thu, 1 Jun 2023 14:41:58 +0200 Subject: [PATCH 223/510] [DURACOM-126] Improve test in order to check item can be submitted --- .../java/org/dspace/builder/WorkspaceItemBuilder.java | 4 ++++ .../dspace/app/rest/WorkspaceItemRestRepositoryIT.java | 10 +++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/dspace-api/src/test/java/org/dspace/builder/WorkspaceItemBuilder.java b/dspace-api/src/test/java/org/dspace/builder/WorkspaceItemBuilder.java index 8b7bc2978ba1..9d786d4761f0 100644 --- a/dspace-api/src/test/java/org/dspace/builder/WorkspaceItemBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/WorkspaceItemBuilder.java @@ -189,6 +189,10 @@ public WorkspaceItemBuilder withAbstract(final String subject) { return addMetadataValue(MetadataSchemaEnum.DC.getName(),"description", "abstract", subject); } + public WorkspaceItemBuilder withType(final String type) { + return addMetadataValue(MetadataSchemaEnum.DC.getName(),"type", null, type); + } + public WorkspaceItemBuilder grantLicense() { Item item = workspaceItem.getItem(); String license; 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 ad9b8046f25a..8b2f3f093a37 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 @@ -8582,11 +8582,13 @@ public void testSubmissionWithHiddenSections() throws Exception { WorkspaceItem workspaceItem = WorkspaceItemBuilder.createWorkspaceItem(context, collection) .withTitle("Workspace Item") .withIssueDate("2023-01-01") + .withType("book") .build(); context.restoreAuthSystemState(); + String adminToken = getAuthToken(admin.getEmail(), password); - getClient(getAuthToken(admin.getEmail(), password)) + getClient(adminToken) .perform(get("/api/submission/workspaceitems/" + workspaceItem.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$.sections.test-outside-workflow-hidden").doesNotExist()) @@ -8594,5 +8596,11 @@ public void testSubmissionWithHiddenSections() throws Exception { .andExpect(jsonPath("$.sections.test-never-hidden").exists()) .andExpect(jsonPath("$.sections.test-always-hidden").doesNotExist()); + // Deposit the item + getClient(adminToken).perform(post("/api/workflow/workflowitems") + .content("/api/submission/workspaceitems/" + workspaceItem.getID()) + .contentType(textUriContentType)) + .andExpect(status().isCreated()); + } } From dae8f166e1673208402c0be5799180ebea76c212 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Thu, 1 Jun 2023 14:48:46 +0200 Subject: [PATCH 224/510] [DURACOM-126] add java doc --- .../main/java/org/dspace/app/util/SubmissionStepConfig.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/dspace-api/src/main/java/org/dspace/app/util/SubmissionStepConfig.java b/dspace-api/src/main/java/org/dspace/app/util/SubmissionStepConfig.java index 1a8f2744b818..28d39d911b95 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/SubmissionStepConfig.java +++ b/dspace-api/src/main/java/org/dspace/app/util/SubmissionStepConfig.java @@ -176,6 +176,12 @@ public String getVisibilityOutside() { return visibilityOutside; } + /** + * Check if given submission section object is hidden for the current submission scope + * + * @param obj the InProgressSubmission to check + * @return true if the submission section is hidden, false otherwise + */ public boolean isHiddenForInProgressSubmission(InProgressSubmission obj) { String scopeToCheck = getScope(obj); From 0ec27875bcb3bab66a38ddc3987ed401c67a2f0d Mon Sep 17 00:00:00 2001 From: Francesco Pio Scognamiglio Date: Wed, 31 May 2023 16:47:02 +0200 Subject: [PATCH 225/510] [DURACOM-149] use right formatter for mapping of dc.date.issued in pubmed live import; added integration tests --- .../PubmedDateMetadatumContributor.java | 18 +- .../PubmedImportMetadataSourceServiceIT.java | 213 ++++++++++++++++++ .../app/rest/pubmedimport-fetch-test.xml | 14 ++ .../app/rest/pubmedimport-fetch-test2.xml | 14 ++ .../app/rest/pubmedimport-search-test.xml | 194 ++++++++++++++++ .../app/rest/pubmedimport-search-test2.xml | 132 +++++++++++ 6 files changed, 578 insertions(+), 7 deletions(-) create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/PubmedImportMetadataSourceServiceIT.java create mode 100644 dspace-server-webapp/src/test/resources/org/dspace/app/rest/pubmedimport-fetch-test.xml create mode 100644 dspace-server-webapp/src/test/resources/org/dspace/app/rest/pubmedimport-fetch-test2.xml create mode 100644 dspace-server-webapp/src/test/resources/org/dspace/app/rest/pubmedimport-search-test.xml create mode 100644 dspace-server-webapp/src/test/resources/org/dspace/app/rest/pubmedimport-search-test2.xml diff --git a/dspace-api/src/main/java/org/dspace/importer/external/pubmed/metadatamapping/contributor/PubmedDateMetadatumContributor.java b/dspace-api/src/main/java/org/dspace/importer/external/pubmed/metadatamapping/contributor/PubmedDateMetadatumContributor.java index 6536026058ec..add9caef1b74 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/pubmed/metadatamapping/contributor/PubmedDateMetadatumContributor.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/pubmed/metadatamapping/contributor/PubmedDateMetadatumContributor.java @@ -15,8 +15,8 @@ import java.util.LinkedList; import java.util.List; +import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; -import org.dspace.content.DCDate; import org.dspace.importer.external.metadatamapping.MetadataFieldConfig; import org.dspace.importer.external.metadatamapping.MetadataFieldMapping; import org.dspace.importer.external.metadatamapping.MetadatumDTO; @@ -107,28 +107,30 @@ public Collection contributeMetadata(T t) { LinkedList dayList = (LinkedList) day.contributeMetadata(t); for (int i = 0; i < yearList.size(); i++) { - DCDate dcDate = null; + String resultDateString = ""; String dateString = ""; + SimpleDateFormat resultFormatter = null; if (monthList.size() > i && dayList.size() > i) { dateString = yearList.get(i).getValue() + "-" + monthList.get(i).getValue() + "-" + dayList.get(i).getValue(); + resultFormatter = new SimpleDateFormat("yyyy-MM-dd"); } else if (monthList.size() > i) { dateString = yearList.get(i).getValue() + "-" + monthList.get(i).getValue(); + resultFormatter = new SimpleDateFormat("yyyy-MM"); } else { dateString = yearList.get(i).getValue(); + resultFormatter = new SimpleDateFormat("yyyy"); } int j = 0; // Use the first dcDate that has been formatted (Config should go from most specific to most lenient) - while (j < dateFormatsToAttempt.size()) { + while (j < dateFormatsToAttempt.size() && StringUtils.isBlank(resultDateString)) { String dateFormat = dateFormatsToAttempt.get(j); try { SimpleDateFormat formatter = new SimpleDateFormat(dateFormat); Date date = formatter.parse(dateString); - dcDate = new DCDate(date); - values.add(metadataFieldMapping.toDCValue(field, formatter.format(date))); - break; + resultDateString = resultFormatter.format(date); } catch (ParseException e) { // Multiple dateformats can be configured, we don't want to print the entire stacktrace every // time one of those formats fails. @@ -138,7 +140,9 @@ public Collection contributeMetadata(T t) { } j++; } - if (dcDate == null) { + if (StringUtils.isNotBlank(resultDateString)) { + values.add(metadataFieldMapping.toDCValue(field, resultDateString)); + } else { log.info( "Failed parsing " + dateString + ", check " + "the configured dataformats in config/spring/api/pubmed-integration.xml"); 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 new file mode 100644 index 000000000000..79b8ec3f7297 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/PubmedImportMetadataSourceServiceIT.java @@ -0,0 +1,213 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.when; + +import java.io.InputStream; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.apache.commons.io.IOUtils; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.impl.client.CloseableHttpClient; +import org.dspace.importer.external.datamodel.ImportRecord; +import org.dspace.importer.external.liveimportclient.service.LiveImportClientImpl; +import org.dspace.importer.external.metadatamapping.MetadatumDTO; +import org.dspace.importer.external.pubmed.service.PubmedImportMetadataSourceServiceImpl; +import org.junit.Test; +import org.mockito.ArgumentMatchers; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Integration tests for {@link PubmedImportMetadataSourceServiceImpl} + * + * @author Francesco Pio Scognamiglio (francescopio.scognamiglio at 4science.com) + */ +public class PubmedImportMetadataSourceServiceIT extends AbstractLiveImportIntegrationTest { + + @Autowired + private PubmedImportMetadataSourceServiceImpl pubmedImportMetadataServiceImpl; + + @Autowired + private LiveImportClientImpl liveImportClientImpl; + + @Test + public void pubmedImportMetadataGetRecordsTest() throws Exception { + context.turnOffAuthorisationSystem(); + + CloseableHttpClient originalHttpClient = liveImportClientImpl.getHttpClient(); + CloseableHttpClient httpClient = Mockito.mock(CloseableHttpClient.class); + try (InputStream fetchFile = getClass().getResourceAsStream("pubmedimport-fetch-test.xml"); + InputStream searchFile = getClass().getResourceAsStream("pubmedimport-search-test.xml")) { + liveImportClientImpl.setHttpClient(httpClient); + + CloseableHttpResponse fetchResponse = mockResponse( + IOUtils.toString(fetchFile, Charset.defaultCharset()), 200, "OK"); + CloseableHttpResponse searchResponse = mockResponse( + IOUtils.toString(searchFile, Charset.defaultCharset()), 200, "OK"); + + when(httpClient.execute(ArgumentMatchers.any())).thenReturn(fetchResponse).thenReturn(searchResponse); + + context.restoreAuthSystemState(); + ArrayList collection2match = getRecords(); + Collection recordsImported = pubmedImportMetadataServiceImpl.getRecords("test query", 0, 1); + assertEquals(1, recordsImported.size()); + matchRecords(new ArrayList(recordsImported), collection2match); + } finally { + liveImportClientImpl.setHttpClient(originalHttpClient); + } + } + + @Test + public void pubmedImportMetadataGetRecords2Test() throws Exception { + context.turnOffAuthorisationSystem(); + + CloseableHttpClient originalHttpClient = liveImportClientImpl.getHttpClient(); + CloseableHttpClient httpClient = Mockito.mock(CloseableHttpClient.class); + try (InputStream fetchFile = getClass().getResourceAsStream("pubmedimport-fetch-test2.xml"); + InputStream searchFile = getClass().getResourceAsStream("pubmedimport-search-test2.xml")) { + liveImportClientImpl.setHttpClient(httpClient); + + CloseableHttpResponse fetchResponse = mockResponse( + IOUtils.toString(fetchFile, Charset.defaultCharset()), 200, "OK"); + CloseableHttpResponse searchResponse = mockResponse( + IOUtils.toString(searchFile, Charset.defaultCharset()), 200, "OK"); + + when(httpClient.execute(ArgumentMatchers.any())).thenReturn(fetchResponse).thenReturn(searchResponse); + + context.restoreAuthSystemState(); + ArrayList collection2match = getRecords2(); + Collection recordsImported = pubmedImportMetadataServiceImpl.getRecords("test query", 0, 1); + assertEquals(1, recordsImported.size()); + matchRecords(new ArrayList(recordsImported), collection2match); + } finally { + liveImportClientImpl.setHttpClient(originalHttpClient); + } + } + + private ArrayList getRecords() { + ArrayList records = new ArrayList<>(); + List metadatums = new ArrayList(); + //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 identifierOther = createMetadatumDTO("dc", "identifier", "other", "36708638"); + MetadatumDTO author1 = createMetadatumDTO("dc", "contributor", "author", "Giuffrida, Silvia"); + MetadatumDTO author2 = createMetadatumDTO("dc", "contributor", "author", "Silano, Verdiana"); + MetadatumDTO author3 = createMetadatumDTO("dc", "contributor", "author", "Ramacciati, Nicola"); + MetadatumDTO author4 = createMetadatumDTO("dc", "contributor", "author", "Prandi, Cesarina"); + MetadatumDTO author5 = createMetadatumDTO("dc", "contributor", "author", "Baldon, Alessia"); + MetadatumDTO author6 = createMetadatumDTO("dc", "contributor", "author", "Bianchi, Monica"); + MetadatumDTO date = createMetadatumDTO("dc", "date", "issued", "2023-02"); + MetadatumDTO language = createMetadatumDTO("dc", "language", "iso", "en"); + MetadatumDTO subject1 = createMetadatumDTO("dc", "subject", null, "Advanced practice nursing"); + MetadatumDTO subject2 = createMetadatumDTO("dc", "subject", null, "Clinical reasoning"); + MetadatumDTO subject3 = createMetadatumDTO("dc", "subject", null, "Critical thinking"); + MetadatumDTO subject4 = createMetadatumDTO("dc", "subject", null, "Educational strategies"); + MetadatumDTO subject5 = createMetadatumDTO("dc", "subject", null, "Nursing education"); + MetadatumDTO subject6 = createMetadatumDTO("dc", "subject", null, "Teaching methodology"); + + 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); + metadatums.add(author3); + metadatums.add(author4); + metadatums.add(author5); + metadatums.add(author6); + metadatums.add(date); + metadatums.add(language); + metadatums.add(subject1); + metadatums.add(subject2); + metadatums.add(subject3); + metadatums.add(subject4); + metadatums.add(subject5); + metadatums.add(subject6); + ImportRecord record = new ImportRecord(metadatums); + + records.add(record); + return records; + } + + private ArrayList getRecords2() { + ArrayList records = new ArrayList<>(); + List metadatums = new ArrayList(); + //define first record + MetadatumDTO title = createMetadatumDTO("dc","title", null, "Searching NCBI Databases Using Entrez."); + MetadatumDTO description = createMetadatumDTO("dc", "description", "abstract", "One of the most widely" + + " used interfaces for the retrieval of information from biological databases is the NCBI Entrez" + + " system. Entrez capitalizes on the fact that there are pre-existing, logical relationships between" + + " the individual entries found in numerous public databases. The existence of such natural" + + " connections, mostly biological in nature, argued for the development of a method through which" + + " all the information about a particular biological entity could be found without having to" + + " sequentially visit and query disparate databases. Two basic protocols describe simple, text-based" + + " searches, illustrating the types of information that can be retrieved through the Entrez system." + + " An alternate protocol builds upon the first basic protocol, using additional," + + " built-in features of the Entrez system, and providing alternative ways to issue the initial query." + + " The support protocol reviews how to save frequently issued queries. Finally, Cn3D, a structure" + + " visualization tool, is also discussed."); + MetadatumDTO identifierOther = createMetadatumDTO("dc", "identifier", "other", "21975942"); + MetadatumDTO author1 = createMetadatumDTO("dc", "contributor", "author", "Gibney, Gretchen"); + MetadatumDTO author2 = createMetadatumDTO("dc", "contributor", "author", "Baxevanis, Andreas D"); + MetadatumDTO date = createMetadatumDTO("dc", "date", "issued", "2011-10"); + MetadatumDTO language = createMetadatumDTO("dc", "language", "iso", "en"); + + metadatums.add(title); + metadatums.add(description); + metadatums.add(identifierOther); + metadatums.add(author1); + metadatums.add(author2); + metadatums.add(date); + metadatums.add(language); + ImportRecord record = new ImportRecord(metadatums); + + records.add(record); + return records; + } + +} \ No newline at end of file diff --git a/dspace-server-webapp/src/test/resources/org/dspace/app/rest/pubmedimport-fetch-test.xml b/dspace-server-webapp/src/test/resources/org/dspace/app/rest/pubmedimport-fetch-test.xml new file mode 100644 index 000000000000..4f921658e32b --- /dev/null +++ b/dspace-server-webapp/src/test/resources/org/dspace/app/rest/pubmedimport-fetch-test.xml @@ -0,0 +1,14 @@ + + + + 1 + 1 + 0 + 1 + MCID_64784b5ab65e3b2b2253cd3a + + 36708638 + + + "10 1016 j nepr 2023 103548"[All Fields] + \ No newline at end of file diff --git a/dspace-server-webapp/src/test/resources/org/dspace/app/rest/pubmedimport-fetch-test2.xml b/dspace-server-webapp/src/test/resources/org/dspace/app/rest/pubmedimport-fetch-test2.xml new file mode 100644 index 000000000000..1ff9570777a7 --- /dev/null +++ b/dspace-server-webapp/src/test/resources/org/dspace/app/rest/pubmedimport-fetch-test2.xml @@ -0,0 +1,14 @@ + + + + 1 + 1 + 0 + 1 + MCID_64784b12ccf058150336d6a8 + + 21975942 + + + "10 1002 0471142905 hg0610s71"[All Fields] + \ 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 new file mode 100644 index 000000000000..666fb1e7d550 --- /dev/null +++ b/dspace-server-webapp/src/test/resources/org/dspace/app/rest/pubmedimport-search-test.xml @@ -0,0 +1,194 @@ + + + + + + 36708638 + + 2023 + 02 + 23 + + + 2023 + 02 + 23 + +
+ + 1873-5223 + + 67 + + 2023 + Feb + + + Nurse education in practice + Nurse Educ Pract + + Teaching strategies of clinical reasoning in advanced nursing clinical practice: A scoping review. + + 103548 + 103548 + + 10.1016/j.nepr.2023.103548 + S1471-5953(23)00010-0 + + 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. + 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. + 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. + 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. + Copyright © 2023 Elsevier Ltd. All rights reserved. + + + + Giuffrida + Silvia + S + + Department of Cardiology and Cardiac Surgery, Cardio Centro Ticino Institute, Ente Ospedaliero Cantonale, Lugano, Switzerland. Electronic address: silvia.giuffrida@eoc.ch. + + + + Silano + Verdiana + V + + Nursing Direction of Settore Anziani Città di Bellinzona, Bellinzona, Switzerland. Electronic address: verdiana.silano@hotmail.it. + + + + Ramacciati + Nicola + N + + Department of Pharmacy, Health and Nutritional Sciences (DFSSN), University of Calabria, Rende, Italy. Electronic address: nicola.ramacciati@unical.it. + + + + Prandi + Cesarina + C + + Department of Business Economics, Health and Social Care (DEASS), University of Applied Sciences and Arts of Southern Switzerland, Manno, Switzerland. Electronic address: cesarina.prandi@supsi.ch. + + + + Baldon + Alessia + A + + Department of Business Economics, Health and Social Care (DEASS), University of Applied Sciences and Arts of Southern Switzerland, Manno, Switzerland. Electronic address: alessia.baldon@supsi.ch. + + + + Bianchi + Monica + M + + Department of Business Economics, Health and Social Care (DEASS), University of Applied Sciences and Arts of Southern Switzerland, Manno, Switzerland. Electronic address: monica.bianchi@supsi.ch. + + + + eng + + Journal Article + Review + + + 2023 + 01 + 17 + +
+ + Scotland + Nurse Educ Pract + 101090848 + 1471-5953 + + IM + + + Humans + + + Advanced Practice Nursing + + + Learning + + + Curriculum + + + Thinking + + + Clinical Reasoning + + + Students, Nursing + + + + Advanced practice nursing + Clinical reasoning + Critical thinking + Educational strategies + Nursing education + Teaching methodology + + Declaration of Competing Interest The authors declare that they have no known competing financial interests or personal relationships that could have appeared to influence the work reported in this paper. +
+ + + + 2022 + 11 + 9 + + + 2022 + 12 + 17 + + + 2023 + 1 + 10 + + + 2023 + 1 + 29 + 6 + 0 + + + 2023 + 2 + 25 + 6 + 0 + + + 2023 + 1 + 28 + 18 + 7 + + + ppublish + + 36708638 + 10.1016/j.nepr.2023.103548 + S1471-5953(23)00010-0 + + +
+
\ No newline at end of file diff --git a/dspace-server-webapp/src/test/resources/org/dspace/app/rest/pubmedimport-search-test2.xml b/dspace-server-webapp/src/test/resources/org/dspace/app/rest/pubmedimport-search-test2.xml new file mode 100644 index 000000000000..949d3b1250b2 --- /dev/null +++ b/dspace-server-webapp/src/test/resources/org/dspace/app/rest/pubmedimport-search-test2.xml @@ -0,0 +1,132 @@ + + + + + + 21975942 + + 2012 + 01 + 13 + + + 2016 + 10 + 21 + +
+ + 1934-8258 + + Chapter 6 + + 2011 + Oct + + + Current protocols in human genetics + Curr Protoc Hum Genet + + Searching NCBI Databases Using Entrez. + + Unit6.10 + Unit6.10 + + 10.1002/0471142905.hg0610s71 + + One of the most widely used interfaces for the retrieval of information from biological databases is the NCBI Entrez system. Entrez capitalizes on the fact that there are pre-existing, logical relationships between the individual entries found in numerous public databases. The existence of such natural connections, mostly biological in nature, argued for the development of a method through which all the information about a particular biological entity could be found without having to sequentially visit and query disparate databases. Two basic protocols describe simple, text-based searches, illustrating the types of information that can be retrieved through the Entrez system. An alternate protocol builds upon the first basic protocol, using additional, built-in features of the Entrez system, and providing alternative ways to issue the initial query. The support protocol reviews how to save frequently issued queries. Finally, Cn3D, a structure visualization tool, is also discussed. + © 2011 by John Wiley & Sons, Inc. + + + + Gibney + Gretchen + G + + + Baxevanis + Andreas D + AD + + + eng + + Journal Article + +
+ + United States + Curr Protoc Hum Genet + 101287858 + 1934-8258 + + IM + + + Animals + + + Database Management Systems + + + Databases, Factual + + + Humans + + + Information Storage and Retrieval + methods + + + Internet + + + Molecular Conformation + + + National Library of Medicine (U.S.) + + + PubMed + + + United States + + + User-Computer Interface + + +
+ + + + 2011 + 10 + 7 + 6 + 0 + + + 2011 + 10 + 7 + 6 + 0 + + + 2012 + 1 + 14 + 6 + 0 + + + ppublish + + 21975942 + 10.1002/0471142905.hg0610s71 + + +
+
\ No newline at end of file From eb224eb8096fa9d7173cace2a2b8625d40b807eb Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Sun, 14 May 2023 21:52:31 +0200 Subject: [PATCH 226/510] 100553: Added stricter metadata field & schema validation --- .../MetadataFieldRestRepository.java | 12 ++-- .../MetadataSchemaRestRepository.java | 6 +- .../rest/MetadataSchemaRestRepositoryIT.java | 16 ++++- .../rest/MetadatafieldRestRepositoryIT.java | 60 +++++++++++++++++-- 4 files changed, 81 insertions(+), 13 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/MetadataFieldRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/MetadataFieldRestRepository.java index eefd6331d116..5152f11902f5 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/MetadataFieldRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/MetadataFieldRestRepository.java @@ -260,14 +260,18 @@ protected MetadataFieldRest createAndReturn(Context context) if (isBlank(metadataFieldRest.getElement())) { throw new UnprocessableEntityException("metadata element (in request body) cannot be blank"); - } else if (metadataFieldRest.getElement().contains(".")) { - throw new UnprocessableEntityException("metadata element (in request body) cannot contain dots"); + } else if (!metadataFieldRest.getElement().matches("^[^. ,]{1,64}$")) { + throw new UnprocessableEntityException( + "metadata element (in request body) cannot contain dots, commas or spaces and should be smaller than" + + " 64 characters"); } if (isBlank(metadataFieldRest.getQualifier())) { metadataFieldRest.setQualifier(null); - } else if (metadataFieldRest.getQualifier().contains(".")) { - throw new UnprocessableEntityException("metadata qualifier (in request body) cannot contain dots"); + } else if (!metadataFieldRest.getQualifier().matches("^[^. ,]{1,64}$")) { + throw new UnprocessableEntityException( + "metadata qualifier (in request body) cannot contain dots, commas or spaces and should be smaller" + + " than 64 characters"); } // create diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/MetadataSchemaRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/MetadataSchemaRestRepository.java index ba49a839bbe5..d9c148b71c0d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/MetadataSchemaRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/MetadataSchemaRestRepository.java @@ -93,8 +93,10 @@ protected MetadataSchemaRest createAndReturn(Context context) // validate fields if (isBlank(metadataSchemaRest.getPrefix())) { throw new UnprocessableEntityException("metadata schema name cannot be blank"); - } else if (metadataSchemaRest.getPrefix().contains(".")) { - throw new UnprocessableEntityException("metadata schema namespace cannot contain dots"); + } else if (!metadataSchemaRest.getPrefix().matches("^[^. ,]{1,32}$")) { + throw new UnprocessableEntityException( + "metadata schema namespace cannot contain dots, commas or spaces and should be smaller than" + + " 32 characters"); } if (isBlank(metadataSchemaRest.getNamespace())) { throw new UnprocessableEntityException("metadata schema namespace cannot be blank"); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/MetadataSchemaRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/MetadataSchemaRestRepositoryIT.java index 4017d5f2da63..72508a0dad58 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/MetadataSchemaRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/MetadataSchemaRestRepositoryIT.java @@ -117,7 +117,7 @@ public void createSuccess() throws Exception { } @Test - public void createUnprocessableEntity_prefixContainingDots() throws Exception { + public void createUnprocessableEntity_prefixContainingInvalidCharacters() throws Exception { context.turnOffAuthorisationSystem(); MetadataSchema metadataSchema = MetadataSchemaBuilder.createMetadataSchema(context, "ATest", "ANamespace") @@ -135,6 +135,20 @@ public void createUnprocessableEntity_prefixContainingDots() throws Exception { .content(new ObjectMapper().writeValueAsBytes(metadataSchemaRest)) .contentType(contentType)) .andExpect(status().isUnprocessableEntity()); + + metadataSchemaRest.setPrefix("test,SchemaName"); + getClient(authToken) + .perform(post("/api/core/metadataschemas") + .content(new ObjectMapper().writeValueAsBytes(metadataSchemaRest)) + .contentType(contentType)) + .andExpect(status().isUnprocessableEntity()); + + metadataSchemaRest.setPrefix("test SchemaName"); + getClient(authToken) + .perform(post("/api/core/metadataschemas") + .content(new ObjectMapper().writeValueAsBytes(metadataSchemaRest)) + .contentType(contentType)) + .andExpect(status().isUnprocessableEntity()); } @Test diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/MetadatafieldRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/MetadatafieldRestRepositoryIT.java index da8dacb46935..6c3872c7eca6 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/MetadatafieldRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/MetadatafieldRestRepositoryIT.java @@ -50,12 +50,12 @@ */ public class MetadatafieldRestRepositoryIT extends AbstractControllerIntegrationTest { - private static final String ELEMENT = "test element"; - private static final String QUALIFIER = "test qualifier"; + private static final String ELEMENT = "test_element"; + private static final String QUALIFIER = "test_qualifier"; private static final String SCOPE_NOTE = "test scope_note"; - private static final String ELEMENT_UPDATED = "test element updated"; - private static final String QUALIFIER_UPDATED = "test qualifier updated"; + private static final String ELEMENT_UPDATED = "test_element_updated"; + private static final String QUALIFIER_UPDATED = "test_qualifier_updated"; private static final String SCOPE_NOTE_UPDATED = "test scope_note updated"; private MetadataSchema metadataSchema; @@ -758,7 +758,7 @@ public void createUnauthorized() throws Exception { } @Test - public void createUnprocessableEntity_elementContainingDots() throws Exception { + public void createUnprocessableEntity_elementContainingInvalidCharacters() throws Exception { MetadataFieldRest metadataFieldRest = new MetadataFieldRest(); metadataFieldRest.setElement("testElement.ForCreate"); metadataFieldRest.setQualifier(QUALIFIER); @@ -775,10 +775,34 @@ public void createUnprocessableEntity_elementContainingDots() throws Exception { .content(new ObjectMapper().writeValueAsBytes(metadataFieldRest)) .contentType(contentType)) .andExpect(status().isUnprocessableEntity()); + + metadataFieldRest.setElement("testElement,ForCreate"); + assertThat(metadataFieldService.findByElement(context, metadataSchema, metadataFieldRest.getElement(), + metadataFieldRest.getQualifier()), nullValue()); + + getClient(authToken) + .perform(post("/api/core/metadatafields") + .param("schemaId", String.valueOf(metadataSchema.getID())) + .param("projection", "full") + .content(new ObjectMapper().writeValueAsBytes(metadataFieldRest)) + .contentType(contentType)) + .andExpect(status().isUnprocessableEntity()); + + metadataFieldRest.setElement("testElement ForCreate"); + assertThat(metadataFieldService.findByElement(context, metadataSchema, metadataFieldRest.getElement(), + metadataFieldRest.getQualifier()), nullValue()); + + getClient(authToken) + .perform(post("/api/core/metadatafields") + .param("schemaId", String.valueOf(metadataSchema.getID())) + .param("projection", "full") + .content(new ObjectMapper().writeValueAsBytes(metadataFieldRest)) + .contentType(contentType)) + .andExpect(status().isUnprocessableEntity()); } @Test - public void createUnprocessableEntity_qualifierContainingDots() throws Exception { + public void createUnprocessableEntity_qualifierContainingInvalidCharacters() throws Exception { MetadataFieldRest metadataFieldRest = new MetadataFieldRest(); metadataFieldRest.setElement(ELEMENT); metadataFieldRest.setQualifier("testQualifier.ForCreate"); @@ -795,6 +819,30 @@ public void createUnprocessableEntity_qualifierContainingDots() throws Exception .content(new ObjectMapper().writeValueAsBytes(metadataFieldRest)) .contentType(contentType)) .andExpect(status().isUnprocessableEntity()); + + metadataFieldRest.setQualifier("testQualifier,ForCreate"); + assertThat(metadataFieldService.findByElement(context, metadataSchema, metadataFieldRest.getElement(), + metadataFieldRest.getQualifier()), nullValue()); + + getClient(authToken) + .perform(post("/api/core/metadatafields") + .param("schemaId", String.valueOf(metadataSchema.getID())) + .param("projection", "full") + .content(new ObjectMapper().writeValueAsBytes(metadataFieldRest)) + .contentType(contentType)) + .andExpect(status().isUnprocessableEntity()); + + metadataFieldRest.setQualifier("testQualifier ForCreate"); + assertThat(metadataFieldService.findByElement(context, metadataSchema, metadataFieldRest.getElement(), + metadataFieldRest.getQualifier()), nullValue()); + + getClient(authToken) + .perform(post("/api/core/metadatafields") + .param("schemaId", String.valueOf(metadataSchema.getID())) + .param("projection", "full") + .content(new ObjectMapper().writeValueAsBytes(metadataFieldRest)) + .contentType(contentType)) + .andExpect(status().isUnprocessableEntity()); } @Test From d76bdc6d476fa07ce518fa920dd0b1559228171f Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Thu, 1 Jun 2023 18:47:52 +0200 Subject: [PATCH 227/510] CST-9634 Assure that REST API assumes all dates to be UTC if not specified --- .../ResourcePolicyEndDateAddOperation.java | 11 +++---- ...ResourcePolicyEndDateReplaceOperation.java | 11 +++---- .../ResourcePolicyStartDateAddOperation.java | 11 +++---- ...sourcePolicyStartDateReplaceOperation.java | 11 +++---- .../resourcePolicy/ResourcePolicyUtils.java | 31 ++++++++----------- .../AccessConditionReplacePatchOperation.java | 21 ++++--------- 6 files changed, 39 insertions(+), 57 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/resourcePolicy/ResourcePolicyEndDateAddOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/resourcePolicy/ResourcePolicyEndDateAddOperation.java index b06637bad240..0d426c96d06a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/resourcePolicy/ResourcePolicyEndDateAddOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/resourcePolicy/ResourcePolicyEndDateAddOperation.java @@ -7,7 +7,6 @@ */ package org.dspace.app.rest.repository.patch.operation.resourcePolicy; -import java.text.ParseException; import java.util.Date; import org.dspace.app.rest.exception.DSpaceBadRequestException; @@ -15,6 +14,7 @@ import org.dspace.app.rest.repository.patch.operation.PatchOperation; import org.dspace.authorize.ResourcePolicy; import org.dspace.core.Context; +import org.dspace.util.MultiFormatDateParser; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -58,11 +58,10 @@ public R perform(Context context, R resource, Operation operation) { */ private void add(ResourcePolicy resourcePolicy, Operation operation) { String dateS = (String) operation.getValue(); - try { - Date date = resourcePolicyUtils.simpleDateFormat.parse(dateS); - resourcePolicy.setEndDate(date); - } catch (ParseException e) { - throw new DSpaceBadRequestException("Invalid endDate value", e); + Date date = MultiFormatDateParser.parse(dateS); + resourcePolicy.setEndDate(date); + if (date == null) { + throw new DSpaceBadRequestException("Invalid endDate value " + dateS); } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/resourcePolicy/ResourcePolicyEndDateReplaceOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/resourcePolicy/ResourcePolicyEndDateReplaceOperation.java index a71224ea294d..fc4e7a63ca87 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/resourcePolicy/ResourcePolicyEndDateReplaceOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/resourcePolicy/ResourcePolicyEndDateReplaceOperation.java @@ -7,7 +7,6 @@ */ package org.dspace.app.rest.repository.patch.operation.resourcePolicy; -import java.text.ParseException; import java.util.Date; import org.dspace.app.rest.exception.DSpaceBadRequestException; @@ -15,6 +14,7 @@ import org.dspace.app.rest.repository.patch.operation.PatchOperation; import org.dspace.authorize.ResourcePolicy; import org.dspace.core.Context; +import org.dspace.util.MultiFormatDateParser; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -58,12 +58,11 @@ public R perform(Context context, R resource, Operation operation) { */ private void replace(ResourcePolicy resourcePolicy, Operation operation) { String dateS = (String) operation.getValue(); - try { - Date date = resourcePolicyUtils.simpleDateFormat.parse(dateS); - resourcePolicy.setEndDate(date); - } catch (ParseException e) { - throw new DSpaceBadRequestException("Invalid endDate value", e); + Date date = MultiFormatDateParser.parse(dateS); + if (date == null) { + throw new DSpaceBadRequestException("Invalid endDate value " + dateS); } + resourcePolicy.setEndDate(date); } @Override diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/resourcePolicy/ResourcePolicyStartDateAddOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/resourcePolicy/ResourcePolicyStartDateAddOperation.java index f8f74b65868d..f19d7043cf7d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/resourcePolicy/ResourcePolicyStartDateAddOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/resourcePolicy/ResourcePolicyStartDateAddOperation.java @@ -7,7 +7,6 @@ */ package org.dspace.app.rest.repository.patch.operation.resourcePolicy; -import java.text.ParseException; import java.util.Date; import org.dspace.app.rest.exception.DSpaceBadRequestException; @@ -15,6 +14,7 @@ import org.dspace.app.rest.repository.patch.operation.PatchOperation; import org.dspace.authorize.ResourcePolicy; import org.dspace.core.Context; +import org.dspace.util.MultiFormatDateParser; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -59,12 +59,11 @@ public R perform(Context context, R resource, Operation operation) { */ private void add(ResourcePolicy resourcePolicy, Operation operation) { String dateS = (String) operation.getValue(); - try { - Date date = resourcePolicyUtils.simpleDateFormat.parse(dateS); - resourcePolicy.setStartDate(date); - } catch (ParseException e) { - throw new DSpaceBadRequestException("Invalid startDate value", e); + Date date = MultiFormatDateParser.parse(dateS); + if (date == null) { + throw new DSpaceBadRequestException("Invalid startDate value " + dateS); } + resourcePolicy.setStartDate(date); } @Override diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/resourcePolicy/ResourcePolicyStartDateReplaceOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/resourcePolicy/ResourcePolicyStartDateReplaceOperation.java index a6812f658132..2d1425341071 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/resourcePolicy/ResourcePolicyStartDateReplaceOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/resourcePolicy/ResourcePolicyStartDateReplaceOperation.java @@ -7,7 +7,6 @@ */ package org.dspace.app.rest.repository.patch.operation.resourcePolicy; -import java.text.ParseException; import java.util.Date; import org.dspace.app.rest.exception.DSpaceBadRequestException; @@ -15,6 +14,7 @@ import org.dspace.app.rest.repository.patch.operation.PatchOperation; import org.dspace.authorize.ResourcePolicy; import org.dspace.core.Context; +import org.dspace.util.MultiFormatDateParser; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -58,12 +58,11 @@ public R perform(Context context, R resource, Operation operation) { */ private void replace(ResourcePolicy resourcePolicy, Operation operation) { String dateS = (String) operation.getValue(); - try { - Date date = resourcePolicyUtils.simpleDateFormat.parse(dateS); - resourcePolicy.setStartDate(date); - } catch (ParseException e) { - throw new DSpaceBadRequestException("Invalid startDate value", e); + Date date = MultiFormatDateParser.parse(dateS); + if (date == null) { + throw new DSpaceBadRequestException("Invalid startDate value " + dateS); } + resourcePolicy.setStartDate(date); } @Override diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/resourcePolicy/ResourcePolicyUtils.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/resourcePolicy/ResourcePolicyUtils.java index 435480e318ef..7718260be7c4 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/resourcePolicy/ResourcePolicyUtils.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/resourcePolicy/ResourcePolicyUtils.java @@ -7,13 +7,12 @@ */ package org.dspace.app.rest.repository.patch.operation.resourcePolicy; -import java.text.ParseException; -import java.text.SimpleDateFormat; import java.util.Date; import org.dspace.app.rest.exception.DSpaceBadRequestException; import org.dspace.app.rest.model.patch.Operation; import org.dspace.authorize.ResourcePolicy; +import org.dspace.util.MultiFormatDateParser; import org.springframework.stereotype.Component; /** @@ -25,8 +24,6 @@ @Component public class ResourcePolicyUtils { - public static final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd"); - /** * Paths in json body of patched that use these resourcePolicy operations */ @@ -112,13 +109,12 @@ public void checkResourcePolicyForExistingDescriptionValue(ResourcePolicy resour */ public void checkResourcePolicyForConsistentStartDateValue(ResourcePolicy resource, Operation operation) { String dateS = (String) operation.getValue(); - try { - Date date = simpleDateFormat.parse(dateS); - if (resource.getEndDate() != null && resource.getEndDate().before(date)) { - throw new DSpaceBadRequestException("Attempting to set an invalid startDate greater than the endDate."); - } - } catch (ParseException e) { - throw new DSpaceBadRequestException("Invalid startDate value", e); + Date date = MultiFormatDateParser.parse(dateS); + if (date == null) { + throw new DSpaceBadRequestException("Invalid startDate value " + dateS); + } + if (resource.getEndDate() != null && resource.getEndDate().before(date)) { + throw new DSpaceBadRequestException("Attempting to set an invalid startDate greater than the endDate."); } } @@ -134,13 +130,12 @@ public void checkResourcePolicyForConsistentStartDateValue(ResourcePolicy resour */ public void checkResourcePolicyForConsistentEndDateValue(ResourcePolicy resource, Operation operation) { String dateS = (String) operation.getValue(); - try { - Date date = simpleDateFormat.parse(dateS); - if (resource.getStartDate() != null && resource.getStartDate().after(date)) { - throw new DSpaceBadRequestException("Attempting to set an invalid endDate smaller than the startDate."); - } - } catch (ParseException e) { - throw new DSpaceBadRequestException("Invalid endDate value", e); + Date date = MultiFormatDateParser.parse(dateS); + if (date == null) { + throw new DSpaceBadRequestException("Invalid endDate value " + dateS); + } + if (resource.getStartDate() != null && resource.getStartDate().after(date)) { + throw new DSpaceBadRequestException("Attempting to set an invalid endDate smaller than the startDate."); } } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionReplacePatchOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionReplacePatchOperation.java index 0216628a6b87..1e45293093dd 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionReplacePatchOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionReplacePatchOperation.java @@ -8,8 +8,6 @@ package org.dspace.app.rest.submit.factory.impl; import java.sql.SQLException; import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.Arrays; import java.util.Date; import java.util.List; import java.util.Objects; @@ -29,6 +27,7 @@ import org.dspace.submit.model.AccessConditionConfiguration; import org.dspace.submit.model.AccessConditionConfigurationService; import org.dspace.submit.model.AccessConditionOption; +import org.dspace.util.MultiFormatDateParser; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -145,20 +144,12 @@ private void updatePolicy(Context context, String valueToReplare, String attribu } } - private Date parseDate(String date) { - List knownPatterns = Arrays.asList( - new SimpleDateFormat("yyyy-MM-dd"), - new SimpleDateFormat("dd-MM-yyyy"), - new SimpleDateFormat("yyyy/MM/dd"), - new SimpleDateFormat("dd/MM/yyyy")); - for (SimpleDateFormat pattern : knownPatterns) { - try { - return pattern.parse(date); - } catch (ParseException e) { - log.error(e.getMessage(), e); - } + private Date parseDate(String dateString) { + Date date = MultiFormatDateParser.parse(dateString); + if (date == null) { + throw new UnprocessableEntityException("Provided format of date:" + dateString + " is not supported!"); } - throw new UnprocessableEntityException("Provided format of date:" + date + " is not supported!"); + return date; } private String getValue(Object value) { From b78ddf249847ebfe3013a164c938316b8782b6b3 Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Thu, 1 Jun 2023 19:38:01 +0200 Subject: [PATCH 228/510] CST-9634 make public the validateResourcePolicy method --- .../java/org/dspace/submit/model/AccessConditionOption.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/submit/model/AccessConditionOption.java b/dspace-api/src/main/java/org/dspace/submit/model/AccessConditionOption.java index b29af4e7733c..e5cd86f50458 100644 --- a/dspace-api/src/main/java/org/dspace/submit/model/AccessConditionOption.java +++ b/dspace-api/src/main/java/org/dspace/submit/model/AccessConditionOption.java @@ -195,7 +195,7 @@ public void updateResourcePolicy(Context context, ResourcePolicy resourcePolicy) * configured maximum. * @throws ParseException passed through. */ - private void validateResourcePolicy(Context context, String name, Date startDate, Date endDate) + public void validateResourcePolicy(Context context, String name, Date startDate, Date endDate) throws IllegalStateException, ParseException { LOG.debug("Validate policy dates: name '{}', startDate {}, endDate {}", name, startDate, endDate); From 1b7af28dd3420000049c75c80666e9e777aa6bdf Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Thu, 1 Jun 2023 21:07:00 +0200 Subject: [PATCH 229/510] CST-9634 remove unnecessary turn off/on auth system --- .../org/dspace/app/bulkaccesscontrol/BulkAccessControl.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/BulkAccessControl.java b/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/BulkAccessControl.java index 86061559798d..bef5a15e03f1 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 @@ -163,8 +163,6 @@ public void internalRun() throws Exception { throw new AuthorizeException("Current user is not eligible to execute script bulk-access-control"); } - context.turnOffAuthorisationSystem(); - if (uuids == null || uuids.size() == 0) { handler.logError("A target uuid must be provided with at least on uuid (run with -h flag for details)"); throw new IllegalArgumentException("At least one target uuid must be provided"); @@ -184,7 +182,6 @@ public void internalRun() throws Exception { validate(accessControl); updateItemsAndBitstreamsPolices(accessControl); context.complete(); - context.restoreAuthSystemState(); } catch (Exception e) { handler.handleException(e); context.abort(); From 70a7930f63d2a98a024aa1d002f799d110e5e8ab Mon Sep 17 00:00:00 2001 From: Bui Thai Hai Date: Fri, 2 Jun 2023 09:45:52 +0700 Subject: [PATCH 230/510] Tweaks fo test cases. --- .../org/dspace/discovery/DiscoveryIT.java | 36 +++++++++---------- .../utils/RestDiscoverQueryBuilderTest.java | 7 ++-- 2 files changed, 18 insertions(+), 25 deletions(-) diff --git a/dspace-api/src/test/java/org/dspace/discovery/DiscoveryIT.java b/dspace-api/src/test/java/org/dspace/discovery/DiscoveryIT.java index 0c3a52ec79f5..164525afb11f 100644 --- a/dspace-api/src/test/java/org/dspace/discovery/DiscoveryIT.java +++ b/dspace-api/src/test/java/org/dspace/discovery/DiscoveryIT.java @@ -768,6 +768,7 @@ public void searchWithDefaultSortServiceTest() throws SearchServiceException { .withTitle("item " + i) .build(); } + context.restoreAuthSystemState(); // Build query with default parameters (except for workspaceConf) DiscoverQuery discoverQuery = SearchUtils.getQueryBuilder() @@ -776,26 +777,21 @@ public void searchWithDefaultSortServiceTest() throws SearchServiceException { DiscoverResult result = searchService.search(context, discoverQuery); - if (defaultSortField.getMetadataField().equals("dc_date_accessioned")) { - // Verify that search results are sort by dc_date_accessioned - LinkedList dc_date_accesioneds = result.getIndexableObjects().stream() - .map(o -> ((Item) o.getIndexedObject()).getMetadata()) - .map(l -> l.stream().filter(m -> m.getMetadataField().toString().equals("dc_date_accessioned")) - .map(m -> m.getValue()).findFirst().orElse("") - ) - .collect(Collectors.toCollection(LinkedList::new)); - assertFalse(dc_date_accesioneds.isEmpty()); - for (int i = 1; i < dc_date_accesioneds.size() - 1; i++) { - assertTrue(dc_date_accesioneds.get(i).compareTo(dc_date_accesioneds.get(i + 1)) >= 0); - } - } else if (defaultSortField.getMetadataField().equals("lastModified")) { - LinkedList lastModifieds = result.getIndexableObjects().stream() - .map(o -> ((Item) o.getIndexedObject()).getLastModified().toString()) - .collect(Collectors.toCollection(LinkedList::new)); - assertFalse(lastModifieds.isEmpty()); - for (int i = 1; i < lastModifieds.size() - 1; i++) { - assertTrue(lastModifieds.get(i).compareTo(lastModifieds.get(i + 1)) >= 0); - } + /* + // code example for testing against sort by dc_date_accessioned + LinkedList dc_date_accesioneds = result.getIndexableObjects().stream() + .map(o -> ((Item) o.getIndexedObject()).getMetadata()) + .map(l -> l.stream().filter(m -> m.getMetadataField().toString().equals("dc_date_accessioned")) + .map(m -> m.getValue()).findFirst().orElse("") + ) + .collect(Collectors.toCollection(LinkedList::new)); + }*/ + LinkedList lastModifieds = result.getIndexableObjects().stream() + .map(o -> ((Item) o.getIndexedObject()).getLastModified().toString()) + .collect(Collectors.toCollection(LinkedList::new)); + assertFalse(lastModifieds.isEmpty()); + for (int i = 1; i < lastModifieds.size() - 1; i++) { + assertTrue(lastModifieds.get(i).compareTo(lastModifieds.get(i + 1)) >= 0); } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/utils/RestDiscoverQueryBuilderTest.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/utils/RestDiscoverQueryBuilderTest.java index 511bb8f98b7b..e21f395f0907 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/utils/RestDiscoverQueryBuilderTest.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/utils/RestDiscoverQueryBuilderTest.java @@ -171,15 +171,12 @@ public void testSortByScore() throws Exception { @Test public void testSortByDefaultSortField() throws Exception { - page = PageRequest.of(2, 10, Sort.Direction.DESC, "dc.date.accessioned"); + page = PageRequest.of(2, 10); restQueryBuilder.buildQuery(context, null, discoveryConfiguration, null, null, emptyList(), page); verify(discoverQueryBuilder, times(1)) .buildQuery(context, null, discoveryConfiguration, null, emptyList(), emptyList(), - page.getPageSize(), page.getOffset(), - discoveryConfiguration.getSearchSortConfiguration().getDefaultSortField().getMetadataField(), - discoveryConfiguration.getSearchSortConfiguration().getDefaultSortField() - .getDefaultSortOrder().name().toUpperCase()); + page.getPageSize(), page.getOffset(), null, null); } @Test(expected = DSpaceBadRequestException.class) From d565cd3e6b343c8928b5a9096d19a48ed1c89ee1 Mon Sep 17 00:00:00 2001 From: Nona Luypaert Date: Fri, 2 Jun 2023 08:21:32 +0200 Subject: [PATCH 231/510] 102052: Return 204 status when primaryBitstream is null --- .../dspace/app/rest/PrimaryBitstreamController.java | 10 +++++++--- .../dspace/app/rest/PrimaryBitstreamControllerIT.java | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/PrimaryBitstreamController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/PrimaryBitstreamController.java index cdcbbce7476e..9afb367b1689 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/PrimaryBitstreamController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/PrimaryBitstreamController.java @@ -18,7 +18,6 @@ import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.BitstreamRest; import org.dspace.app.rest.model.BundleRest; -import org.dspace.app.rest.model.hateoas.BitstreamResource; import org.dspace.app.rest.model.hateoas.BundleResource; import org.dspace.app.rest.repository.BundlePrimaryBitstreamLinkRepository; import org.dspace.app.rest.utils.ContextUtil; @@ -67,10 +66,15 @@ public class PrimaryBitstreamController { */ @PreAuthorize("hasPermission(#uuid, 'BUNDLE', 'READ')") @RequestMapping(method = RequestMethod.GET) - public BitstreamResource getPrimaryBitstream(@PathVariable UUID uuid, + public ResponseEntity> getPrimaryBitstream(@PathVariable UUID uuid, HttpServletRequest request) { BitstreamRest bitstreamRest = repository.getPrimaryBitstream(null, uuid, null, utils.obtainProjection()); - return converter.toResource(bitstreamRest); + if (bitstreamRest == null) { + return ControllerUtils.toEmptyResponse(HttpStatus.NO_CONTENT); + } else { + return ControllerUtils.toResponseEntity(HttpStatus.OK, new HttpHeaders(), + (RepresentationModel) converter.toResource(bitstreamRest)); + } } /** diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/PrimaryBitstreamControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/PrimaryBitstreamControllerIT.java index 630ef257deaa..f42166f25ab8 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/PrimaryBitstreamControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/PrimaryBitstreamControllerIT.java @@ -96,7 +96,7 @@ public void testGetPrimaryBitstreamBundleNotFound() throws Exception { public void testGetPrimaryBitstreamNonExisting() throws Exception { String token = getAuthToken(admin.getEmail(), password); getClient(token).perform(get(getBundleUrl(bundle.getID()))) - .andExpect(status().isOk()) + .andExpect(status().isNoContent()) .andExpect(jsonPath("$").doesNotExist()); } From 7c85b007c027c959217b3b9bc5d730006223d448 Mon Sep 17 00:00:00 2001 From: Francesco Pio Scognamiglio Date: Fri, 2 Jun 2023 10:10:45 +0200 Subject: [PATCH 232/510] [DURACOM-149] remove trailing whitespace --- .../dspace/app/rest/PubmedImportMetadataSourceServiceIT.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 79b8ec3f7297..3b39d251216c 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 @@ -30,7 +30,7 @@ /** * Integration tests for {@link PubmedImportMetadataSourceServiceImpl} - * + * * @author Francesco Pio Scognamiglio (francescopio.scognamiglio at 4science.com) */ public class PubmedImportMetadataSourceServiceIT extends AbstractLiveImportIntegrationTest { From d6d6a3e0740839271c5906f9c2ea46f833d01326 Mon Sep 17 00:00:00 2001 From: Nona Luypaert Date: Mon, 5 Jun 2023 09:45:40 +0200 Subject: [PATCH 233/510] 102052: Replace 'DELETE' with 'WRITE' on deletePrimaryBitstream + More auth ITs --- .../app/rest/PrimaryBitstreamController.java | 2 +- .../BundlePrimaryBitstreamLinkRepository.java | 2 +- .../rest/PrimaryBitstreamControllerIT.java | 286 +++++++++++++++++- 3 files changed, 279 insertions(+), 11 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/PrimaryBitstreamController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/PrimaryBitstreamController.java index 9afb367b1689..59ab1bba763a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/PrimaryBitstreamController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/PrimaryBitstreamController.java @@ -134,7 +134,7 @@ public BundleResource updatePrimaryBitstream(@PathVariable UUID uuid, * @param request The HttpServletRequest * @return The Bundle of which the primaryBitstream was deleted */ - @PreAuthorize("hasPermission(#uuid, 'BUNDLE', 'DELETE')") + @PreAuthorize("hasPermission(#uuid, 'BUNDLE', 'WRITE')") @RequestMapping(method = RequestMethod.DELETE) public ResponseEntity> deletePrimaryBitstream(@PathVariable UUID uuid, HttpServletRequest request) { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BundlePrimaryBitstreamLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BundlePrimaryBitstreamLinkRepository.java index c2f6dac5f228..a013aa0dd002 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BundlePrimaryBitstreamLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BundlePrimaryBitstreamLinkRepository.java @@ -112,7 +112,7 @@ public BundleRest updatePrimaryBitstream(Context context, UUID bundleId, * @param context The current DSpace context * @param bundleId The UUID of the Bundle */ - @PreAuthorize("hasPermission(#bundleId, 'BUNDLE', 'DELETE')") + @PreAuthorize("hasPermission(#bundleId, 'BUNDLE', 'WRITE')") public void deletePrimaryBitstream(Context context, UUID bundleId) { try { Bundle bundle = setPrimaryBitstream(context, bundleId, null, false); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/PrimaryBitstreamControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/PrimaryBitstreamControllerIT.java index f42166f25ab8..d8bc6c8cf98a 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/PrimaryBitstreamControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/PrimaryBitstreamControllerIT.java @@ -52,6 +52,8 @@ public class PrimaryBitstreamControllerIT extends AbstractControllerIntegrationT Item item; Bitstream bitstream; Bundle bundle; + Community community; + Collection collection; @Before @Override @@ -59,8 +61,8 @@ public void setUp() throws Exception { super.setUp(); context.turnOffAuthorisationSystem(); - Community community = CommunityBuilder.createCommunity(context).build(); - Collection collection = CollectionBuilder.createCollection(context, community).build(); + community = CommunityBuilder.createCommunity(context).build(); + collection = CollectionBuilder.createCollection(context, community).build(); item = ItemBuilder.createItem(context, collection).build(); // create bitstream in ORIGINAL bundle of item @@ -142,7 +144,7 @@ public void testPostPrimaryBitstreamInvalidBitstream() throws Exception { public void testPostPrimaryBitstreamAlreadyExists() throws Exception { context.turnOffAuthorisationSystem(); bundle.setPrimaryBitstreamID(bitstream); - Bitstream bitstream2 = createSecondBitstream(bundle); + Bitstream bitstream2 = createBitstream(bundle); context.restoreAuthSystemState(); String token = getAuthToken(admin.getEmail(), password); @@ -159,7 +161,7 @@ public void testPostPrimaryBitstreamAlreadyExists() throws Exception { public void testPostPrimaryBitstreamNotInBundle() throws Exception { context.turnOffAuthorisationSystem(); Bundle bundle2 = BundleBuilder.createBundle(context, item).withName("Bundle2").build(); - Bitstream bitstream2 = createSecondBitstream(bundle2); + Bitstream bitstream2 = createBitstream(bundle2); context.restoreAuthSystemState(); String token = getAuthToken(admin.getEmail(), password); @@ -172,11 +174,95 @@ public void testPostPrimaryBitstreamNotInBundle() throws Exception { Assert.assertNull(bundle.getPrimaryBitstream()); } + @Test + public void testPostPrimaryBitstreamCommunityAdmin() throws Exception { + // create new structure with Admin permissions on Community + context.turnOffAuthorisationSystem(); + Community com2 = CommunityBuilder.createCommunity(context).withAdminGroup(eperson).build(); + Collection col2 = CollectionBuilder.createCollection(context, com2).build(); + Item item2 = ItemBuilder.createItem(context, col2).build(); + Bundle bundle2 = BundleBuilder.createBundle(context, item2).withName("ORIGINAL").build(); + Bitstream bitstream2 = createBitstream(bundle2); + context.restoreAuthSystemState(); + String token = getAuthToken(eperson.getEmail(), password); + + getClient(token).perform(post(getBundleUrl(bundle2.getID())) + .contentType(textUriContentType) + .content(getBitstreamUrl(bitstream2.getID()))) + .andExpect(status().isCreated()) + .andExpect(jsonPath("$", BundleMatcher.matchProperties(bundle2.getName(), bundle2.getID(), + bundle2.getHandle(), bundle2.getType()))); + // verify primaryBitstream was actually added + bundle2 = context.reloadEntity(bundle2); + Assert.assertEquals(bitstream2, bundle2.getPrimaryBitstream()); + } + + @Test + public void testPostPrimaryBitstreamCollectionAdmin() throws Exception { + // create new structure with Admin permissions on Collection + context.turnOffAuthorisationSystem(); + Collection col2 = CollectionBuilder.createCollection(context, community).withAdminGroup(eperson).build(); + Item item2 = ItemBuilder.createItem(context, col2).build(); + Bundle bundle2 = BundleBuilder.createBundle(context, item2).withName("ORIGINAL").build(); + Bitstream bitstream2 = createBitstream(bundle2); + context.restoreAuthSystemState(); + String token = getAuthToken(eperson.getEmail(), password); + + getClient(token).perform(post(getBundleUrl(bundle2.getID())) + .contentType(textUriContentType) + .content(getBitstreamUrl(bitstream2.getID()))) + .andExpect(status().isCreated()) + .andExpect(jsonPath("$", BundleMatcher.matchProperties(bundle2.getName(), bundle2.getID(), + bundle2.getHandle(), bundle2.getType()))); + // verify primaryBitstream was actually added + bundle2 = context.reloadEntity(bundle2); + Assert.assertEquals(bitstream2, bundle2.getPrimaryBitstream()); + } + + @Test + public void testPostPrimaryBitstreamItemAdmin() throws Exception { + // create new structure with Admin permissions on Item + context.turnOffAuthorisationSystem(); + Item item2 = ItemBuilder.createItem(context, collection).withAdminUser(eperson).build(); + Bundle bundle2 = BundleBuilder.createBundle(context, item2).withName("ORIGINAL").build(); + Bitstream bitstream2 = createBitstream(bundle2); + context.restoreAuthSystemState(); + String token = getAuthToken(eperson.getEmail(), password); + + getClient(token).perform(post(getBundleUrl(bundle2.getID())) + .contentType(textUriContentType) + .content(getBitstreamUrl(bitstream2.getID()))) + .andExpect(status().isCreated()) + .andExpect(jsonPath("$", BundleMatcher.matchProperties(bundle2.getName(), bundle2.getID(), + bundle2.getHandle(), bundle2.getType()))); + // verify primaryBitstream was actually added + bundle2 = context.reloadEntity(bundle2); + Assert.assertEquals(bitstream2, bundle2.getPrimaryBitstream()); + } + + @Test + public void testPostPrimaryBitstreamForbidden() throws Exception { + String token = getAuthToken(eperson.getEmail(), password); + + getClient(token).perform(post(getBundleUrl(bundle.getID())) + .contentType(textUriContentType) + .content(getBitstreamUrl(bitstream.getID()))) + .andExpect(status().isForbidden()); + } + + @Test + public void testPostPrimaryBitstreamUnauthenticated() throws Exception { + getClient().perform(post(getBundleUrl(bundle.getID())) + .contentType(textUriContentType) + .content(getBitstreamUrl(bitstream.getID()))) + .andExpect(status().isUnauthorized()); + } + @Test public void testUpdatePrimaryBitstream() throws Exception { context.turnOffAuthorisationSystem(); bundle.setPrimaryBitstreamID(bitstream); - Bitstream bitstream2 = createSecondBitstream(bundle); + Bitstream bitstream2 = createBitstream(bundle); context.restoreAuthSystemState(); String token = getAuthToken(admin.getEmail(), password); @@ -231,7 +317,7 @@ public void testUpdatePrimaryBitstreamNotInBundle() throws Exception { context.turnOffAuthorisationSystem(); bundle.setPrimaryBitstreamID(bitstream); Bundle bundle2 = BundleBuilder.createBundle(context, item).withName("Bundle2").build(); - Bitstream bitstream2 = createSecondBitstream(bundle2); + Bitstream bitstream2 = createBitstream(bundle2); context.restoreAuthSystemState(); String token = getAuthToken(admin.getEmail(), password); @@ -244,6 +330,105 @@ public void testUpdatePrimaryBitstreamNotInBundle() throws Exception { Assert.assertEquals(bitstream, bundle.getPrimaryBitstream()); } + @Test + public void testUpdatePrimaryBitstreamCommunityAdmin() throws Exception { + // create new structure with Admin permissions on Community + context.turnOffAuthorisationSystem(); + Community com2 = CommunityBuilder.createCommunity(context).withAdminGroup(eperson).build(); + Collection col2 = CollectionBuilder.createCollection(context, com2).build(); + Item item2 = ItemBuilder.createItem(context, col2).build(); + Bundle bundle2 = BundleBuilder.createBundle(context, item2).withName("ORIGINAL").build(); + Bitstream bitstream2 = createBitstream(bundle2); + bundle2.setPrimaryBitstreamID(bitstream2); + Bitstream bitstream3 = createBitstream(bundle2); + context.restoreAuthSystemState(); + String token = getAuthToken(eperson.getEmail(), password); + + getClient(token).perform(put(getBundleUrl(bundle2.getID())) + .contentType(textUriContentType) + .content(getBitstreamUrl(bitstream3.getID()))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", BundleMatcher.matchProperties(bundle2.getName(), bundle2.getID(), + bundle2.getHandle(), bundle2.getType()))); + // verify primaryBitstream was actually updated + bundle2 = context.reloadEntity(bundle2); + Assert.assertEquals(bitstream3, bundle2.getPrimaryBitstream()); + } + + @Test + public void testUpdatePrimaryBitstreamCollectionAdmin() throws Exception { + // create new structure with Admin permissions on Collection + context.turnOffAuthorisationSystem(); + Collection col2 = CollectionBuilder.createCollection(context, community).withAdminGroup(eperson).build(); + Item item2 = ItemBuilder.createItem(context, col2).build(); + Bundle bundle2 = BundleBuilder.createBundle(context, item2).withName("ORIGINAL").build(); + Bitstream bitstream2 = createBitstream(bundle2); + bundle2.setPrimaryBitstreamID(bitstream2); + Bitstream bitstream3 = createBitstream(bundle2); + context.restoreAuthSystemState(); + String token = getAuthToken(eperson.getEmail(), password); + + getClient(token).perform(put(getBundleUrl(bundle2.getID())) + .contentType(textUriContentType) + .content(getBitstreamUrl(bitstream3.getID()))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", BundleMatcher.matchProperties(bundle2.getName(), bundle2.getID(), + bundle2.getHandle(), bundle2.getType()))); + // verify primaryBitstream was actually updated + bundle2 = context.reloadEntity(bundle2); + Assert.assertEquals(bitstream3, bundle2.getPrimaryBitstream()); + } + + @Test + public void testUpdatePrimaryBitstreamItemAdmin() throws Exception { + // create new structure with Admin permissions on Item + context.turnOffAuthorisationSystem(); + Item item2 = ItemBuilder.createItem(context, collection).withAdminUser(eperson).build(); + Bundle bundle2 = BundleBuilder.createBundle(context, item2).withName("ORIGINAL").build(); + Bitstream bitstream2 = createBitstream(bundle2); + bundle2.setPrimaryBitstreamID(bitstream2); + Bitstream bitstream3 = createBitstream(bundle2); + context.restoreAuthSystemState(); + String token = getAuthToken(eperson.getEmail(), password); + + getClient(token).perform(put(getBundleUrl(bundle2.getID())) + .contentType(textUriContentType) + .content(getBitstreamUrl(bitstream3.getID()))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", BundleMatcher.matchProperties(bundle2.getName(), bundle2.getID(), + bundle2.getHandle(), bundle2.getType()))); + // verify primaryBitstream was actually updated + bundle2 = context.reloadEntity(bundle2); + Assert.assertEquals(bitstream3, bundle2.getPrimaryBitstream()); + } + + @Test + public void testUpdatePrimaryBitstreamForbidden() throws Exception { + context.turnOffAuthorisationSystem(); + bundle.setPrimaryBitstreamID(bitstream); + Bitstream bitstream2 = createBitstream(bundle); + context.restoreAuthSystemState(); + + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform(put(getBundleUrl(bundle.getID())) + .contentType(textUriContentType) + .content(getBitstreamUrl(bitstream2.getID()))) + .andExpect(status().isForbidden()); + } + + @Test + public void testUpdatePrimaryBitstreamUnauthenticated() throws Exception { + context.turnOffAuthorisationSystem(); + bundle.setPrimaryBitstreamID(bitstream); + Bitstream bitstream2 = createBitstream(bundle); + context.restoreAuthSystemState(); + + getClient().perform(put(getBundleUrl(bundle.getID())) + .contentType(textUriContentType) + .content(getBitstreamUrl(bitstream2.getID()))) + .andExpect(status().isUnauthorized()); + } + @Test public void testDeletePrimaryBitstream() throws Exception { bundle.setPrimaryBitstreamID(bitstream); @@ -276,6 +461,89 @@ public void testDeletePrimaryBitstreamBundleNonExisting() throws Exception { Assert.assertNull(bundle.getPrimaryBitstream()); } + @Test + public void testDeletePrimaryBitstreamCommunityAdmin() throws Exception { + // create new structure with Admin permissions on Community + context.turnOffAuthorisationSystem(); + Community com2 = CommunityBuilder.createCommunity(context).withAdminGroup(eperson).build(); + Collection col2 = CollectionBuilder.createCollection(context, com2).build(); + Item item2 = ItemBuilder.createItem(context, col2).build(); + Bundle bundle2 = BundleBuilder.createBundle(context, item2).withName("ORIGINAL").build(); + Bitstream bitstream2 = createBitstream(bundle2); + bundle2.setPrimaryBitstreamID(bitstream2); + context.restoreAuthSystemState(); + + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform(delete(getBundleUrl(bundle2.getID()))) + .andExpect(status().isNoContent()); + // verify primaryBitstream was actually deleted + bundle2 = context.reloadEntity(bundle2); + Assert.assertNull(bundle2.getPrimaryBitstream()); + // verify bitstream itself still exists + Assert.assertEquals(1, bundle2.getBitstreams().size()); + Assert.assertEquals(bitstream2, bundle2.getBitstreams().get(0)); + } + + @Test + public void testDeletePrimaryBitstreamCollectionAdmin() throws Exception { + // create new structure with Admin permissions on Collection + context.turnOffAuthorisationSystem(); + Collection col2 = CollectionBuilder.createCollection(context, community).withAdminGroup(eperson).build(); + Item item2 = ItemBuilder.createItem(context, col2).build(); + Bundle bundle2 = BundleBuilder.createBundle(context, item2).withName("ORIGINAL").build(); + Bitstream bitstream2 = createBitstream(bundle2); + bundle2.setPrimaryBitstreamID(bitstream2); + context.restoreAuthSystemState(); + + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform(delete(getBundleUrl(bundle2.getID()))) + .andExpect(status().isNoContent()); + // verify primaryBitstream was actually deleted + bundle2 = context.reloadEntity(bundle2); + Assert.assertNull(bundle2.getPrimaryBitstream()); + // verify bitstream itself still exists + Assert.assertEquals(1, bundle2.getBitstreams().size()); + Assert.assertEquals(bitstream2, bundle2.getBitstreams().get(0)); + } + + @Test + public void testDeletePrimaryBitstreamItemAdmin() throws Exception { + // create new structure with Admin permissions on Item + context.turnOffAuthorisationSystem(); + Item item2 = ItemBuilder.createItem(context, collection).withAdminUser(eperson).build(); + Bundle bundle2 = BundleBuilder.createBundle(context, item2).withName("ORIGINAL").build(); + Bitstream bitstream2 = createBitstream(bundle2); + bundle2.setPrimaryBitstreamID(bitstream2); + context.restoreAuthSystemState(); + + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform(delete(getBundleUrl(bundle2.getID()))) + .andExpect(status().isNoContent()); + // verify primaryBitstream was actually deleted + bundle2 = context.reloadEntity(bundle2); + Assert.assertNull(bundle2.getPrimaryBitstream()); + // verify bitstream itself still exists + Assert.assertEquals(1, bundle2.getBitstreams().size()); + Assert.assertEquals(bitstream2, bundle2.getBitstreams().get(0)); + } + + @Test + public void testDeletePrimaryBitstreamForbidden() throws Exception { + bundle.setPrimaryBitstreamID(bitstream); + + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform(delete(getBundleUrl(bundle.getID()))) + .andExpect(status().isForbidden()); + } + + @Test + public void testDeletePrimaryBitstreamUnauthenticated() throws Exception { + bundle.setPrimaryBitstreamID(bitstream); + + getClient().perform(delete(getBundleUrl(bundle.getID()))) + .andExpect(status().isUnauthorized()); + } + private String getBundleUrl(UUID uuid) { return "/api/core/bundles/" + uuid + "/primaryBitstream"; } @@ -284,11 +552,11 @@ private String getBitstreamUrl(UUID uuid) { return "/api/core/bitstreams/" + uuid; } - private Bitstream createSecondBitstream(Bundle bundle) throws Exception { - String bitstreamContent = "Second Bitstream"; + private Bitstream createBitstream(Bundle bundle) throws Exception { + String bitstreamContent = "Bitstream Content"; try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { return BitstreamBuilder.createBitstream(context, bundle, is) - .withName("Bitstream2") + .withName("Bitstream") .withMimeType("text/plain") .build(); } From a38ff421694a5be590a41928985e3f8cd54c1b37 Mon Sep 17 00:00:00 2001 From: Alan Orth Date: Thu, 25 May 2023 14:52:11 +0300 Subject: [PATCH 234/510] dspace: capture publisher from CrossRef live import Publisher is a required field on CrossRef so we can always rely on capturing this information when doing a live import. See: https://github.com/CrossRef/rest-api-doc/blob/master/api_format.md --- .../app/rest/CrossRefImportMetadataSourceServiceIT.java | 8 +++++++- dspace/config/spring/api/crossref-integration.xml | 9 +++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) 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 11fe58ac1d2e..72524709ec65 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 @@ -153,6 +153,8 @@ private ArrayList getRecords() { MetadatumDTO issn = createMetadatumDTO("dc", "identifier", "issn", "2415-3060"); MetadatumDTO volume = createMetadatumDTO("oaire", "citation", "volume", "1"); MetadatumDTO issue = createMetadatumDTO("oaire", "citation", "issue", "2"); + MetadatumDTO publisher = createMetadatumDTO("dc", "publisher", null, + "Petro Mohyla Black Sea National University"); metadatums.add(title); metadatums.add(author); @@ -163,6 +165,7 @@ private ArrayList getRecords() { metadatums.add(issn); metadatums.add(volume); metadatums.add(issue); + metadatums.add(publisher); ImportRecord firstrRecord = new ImportRecord(metadatums); @@ -179,6 +182,8 @@ private ArrayList getRecords() { MetadatumDTO issn2 = createMetadatumDTO("dc", "identifier", "issn", "2415-3060"); MetadatumDTO volume2 = createMetadatumDTO("oaire", "citation", "volume", "1"); MetadatumDTO issue2 = createMetadatumDTO("oaire", "citation", "issue", "2"); + MetadatumDTO publisher2 = createMetadatumDTO("dc", "publisher", null, + "Petro Mohyla Black Sea National University"); metadatums2.add(title2); metadatums2.add(author2); @@ -189,6 +194,7 @@ private ArrayList getRecords() { metadatums2.add(issn2); metadatums2.add(volume2); metadatums2.add(issue2); + metadatums2.add(publisher2); ImportRecord secondRecord = new ImportRecord(metadatums2); records.add(firstrRecord); @@ -196,4 +202,4 @@ private ArrayList getRecords() { return records; } -} \ 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 5d67c17626e2..d1e416d2b0c6 100644 --- a/dspace/config/spring/api/crossref-integration.xml +++ b/dspace/config/spring/api/crossref-integration.xml @@ -30,6 +30,7 @@ + @@ -137,6 +138,14 @@ + + + + + + + + From 9a49998ea0bf8955ebb3063ce11e2f22a2bc06eb Mon Sep 17 00:00:00 2001 From: Nona Luypaert Date: Mon, 5 Jun 2023 16:02:42 +0200 Subject: [PATCH 235/510] 102124: Add config for opening formats as attachment + IT --- .../test/data/dspaceFolder/config/local.cfg | 6 +++ .../app/rest/BitstreamRestController.java | 17 ++++++- .../app/rest/BitstreamRestControllerIT.java | 48 +++++++++++++++++++ dspace/config/dspace.cfg | 5 ++ 4 files changed, 75 insertions(+), 1 deletion(-) diff --git a/dspace-api/src/test/data/dspaceFolder/config/local.cfg b/dspace-api/src/test/data/dspaceFolder/config/local.cfg index 3c19a68e9fd1..144116f212eb 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/local.cfg +++ b/dspace-api/src/test/data/dspaceFolder/config/local.cfg @@ -144,3 +144,9 @@ authentication-ip.Student = 6.6.6.6 useProxies = true proxies.trusted.ipranges = 7.7.7.7 proxies.trusted.include_ui_ip = true + +########################### +# BITSTREAM CONFIGURATION # +########################### +# Files with these mimetypes, extensions will never be opened inline, and should be downloaded +webui.content_disposition_format = text/richtext, text/xml, txt 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 75d3c9886cf1..4e78d5dae706 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 @@ -156,8 +156,11 @@ public ResponseEntity retrieve(@PathVariable UUID uuid, HttpServletResponse resp } //Determine if we need to send the file as a download or if the browser can open it inline + //The file will be downloaded if its size is larger than the configured threshold, + //or if its mimetype/extension appears in the "webui.content_disposition_format" config long dispositionThreshold = configurationService.getLongProperty("webui.content_disposition_threshold"); - if (dispositionThreshold >= 0 && filesize > dispositionThreshold) { + if ((dispositionThreshold >= 0 && filesize > dispositionThreshold) + || checkFormatForContentDisposition(format)) { httpHeadersInitializer.withDisposition(HttpHeadersInitializer.CONTENT_DISPOSITION_ATTACHMENT); } @@ -204,6 +207,18 @@ private boolean isNotAnErrorResponse(HttpServletResponse response) { || responseCode.equals(Response.Status.Family.REDIRECTION); } + private boolean checkFormatForContentDisposition(BitstreamFormat format) { + List formats = List.of((configurationService.getArrayProperty("webui.content_disposition_format"))); + boolean download = formats.contains(format.getMIMEType()); + for (String ext : format.getExtensions()) { + if (formats.contains(ext)) { + download = true; + break; + } + } + return download; + } + /** * This method will update the bitstream format of the bitstream that corresponds to the provided bitstream uuid. * 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 f07aae876f32..9b4aeed1e208 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 @@ -22,6 +22,7 @@ import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.Matchers.equalTo; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.springframework.data.rest.webmvc.RestMediaTypes.TEXT_URI_LIST_VALUE; @@ -968,4 +969,51 @@ public void updateBitstreamFormatAdmin() throws Exception { bitstreamService.getMetadataByMetadataString(bitstream, "dc.format") )); } + + @Test + public void checkContentDispositionOfFormats() throws Exception { + context.turnOffAuthorisationSystem(); + Community community = CommunityBuilder.createCommunity(context).build(); + Collection collection = CollectionBuilder.createCollection(context, community).build(); + Item item = ItemBuilder.createItem(context, collection).build(); + String content = "Test Content"; + Bitstream rtf; + Bitstream xml; + Bitstream txt; + Bitstream html; + try (InputStream is = IOUtils.toInputStream(content, CharEncoding.UTF_8)) { + rtf = BitstreamBuilder.createBitstream(context, item, is) + .withMimeType("text/richtext").build(); + xml = BitstreamBuilder.createBitstream(context, item, is) + .withMimeType("text/xml").build(); + txt = BitstreamBuilder.createBitstream(context, item, is) + .withMimeType("text/plain").build(); + html = BitstreamBuilder.createBitstream(context, item, is) + .withMimeType("text/html").build(); + } + context.restoreAuthSystemState(); + + // these formats are configured and files should be downloaded + verifyBitstreamDownload(rtf, "text/richtext", true); + verifyBitstreamDownload(xml, "text/xml", true); + verifyBitstreamDownload(txt, "text/plain", true); + // this format is not configured and should open inline + verifyBitstreamDownload(html, "text/html", false); + } + + private void verifyBitstreamDownload(Bitstream file, String contentType, boolean shouldDownload) throws Exception { + String token = getAuthToken(admin.getEmail(), password); + String header = getClient(token).perform(get("/api/core/bitstreams/" + file.getID() + "/content") + .header("Accept", contentType)) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andReturn().getResponse().getHeader("content-disposition"); + if (shouldDownload) { + assertTrue(header.contains("attachment")); + assertFalse(header.contains("inline")); + } else { + assertTrue(header.contains("inline")); + assertFalse(header.contains("attachment")); + } + } } diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 02c618abf420..d81346012164 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -1286,6 +1286,11 @@ websvc.opensearch.formats = atom,rss # Use -1 to force all bitstream to be served inline webui.content_disposition_threshold = 8388608 +#### Content Attachment Disposition Formats #### +# +# Set which mimetypes, file extensions will NOT be opened inline +# Files with these mimetypes/extensions will always be downloaded +webui.content_disposition_format = text/richtext #### Multi-file HTML document/site settings ##### # TODO: UNSUPPORTED in DSpace 7.0. May be re-added in a later release From a0a1844de7c6aa4fdc5a743b402e1c056c57ace5 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 5 Jun 2023 16:01:31 -0500 Subject: [PATCH 236/510] Fix test class compilation --- .../src/test/java/org/dspace/discovery/DiscoveryIT.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/dspace-api/src/test/java/org/dspace/discovery/DiscoveryIT.java b/dspace-api/src/test/java/org/dspace/discovery/DiscoveryIT.java index 164525afb11f..55be531418ae 100644 --- a/dspace-api/src/test/java/org/dspace/discovery/DiscoveryIT.java +++ b/dspace-api/src/test/java/org/dspace/discovery/DiscoveryIT.java @@ -7,6 +7,7 @@ */ package org.dspace.discovery; +import static org.dspace.discovery.SolrServiceWorkspaceWorkflowRestrictionPlugin.DISCOVER_WORKSPACE_CONFIGURATION_NAME; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -746,8 +747,8 @@ public void iteratorSearchServiceTest() throws SearchServiceException { */ @Test public void searchWithDefaultSortServiceTest() throws SearchServiceException { - - DiscoveryConfiguration workspaceConf = SearchUtils.getDiscoveryConfiguration("workspace", null); + DiscoveryConfiguration workspaceConf = + SearchUtils.getDiscoveryConfiguration(context, DISCOVER_WORKSPACE_CONFIGURATION_NAME, null); // Skip if no default sort option set for workspaceConf if (workspaceConf.getSearchSortConfiguration().getDefaultSortField() == null) { return; From 3945d164e645d12955364cfc6f6128286a2b3201 Mon Sep 17 00:00:00 2001 From: Kevin Van de Velde Date: Tue, 6 Jun 2023 08:35:18 +0200 Subject: [PATCH 237/510] [Issue: #8888] Fixing the hostname parsing for the shibboleth auth --- dspace-api/src/main/java/org/dspace/core/Utils.java | 8 +++----- dspace-api/src/test/java/org/dspace/core/UtilsTest.java | 6 ++++++ 2 files changed, 9 insertions(+), 5 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 b9fff20c7674..6a487bfcf2d6 100644 --- a/dspace-api/src/main/java/org/dspace/core/Utils.java +++ b/dspace-api/src/main/java/org/dspace/core/Utils.java @@ -16,8 +16,6 @@ import java.net.Inet4Address; import java.net.InetAddress; import java.net.MalformedURLException; -import java.net.URI; -import java.net.URISyntaxException; import java.net.URL; import java.net.UnknownHostException; import java.nio.charset.StandardCharsets; @@ -447,14 +445,14 @@ public static String getBaseUrl(String urlString) { */ public static String getHostName(String uriString) { try { - URI uri = new URI(uriString); - String hostname = uri.getHost(); + URL url = new URL(uriString); + String hostname = url.getHost(); // remove the "www." from hostname, if it exists if (hostname != null) { return hostname.startsWith("www.") ? hostname.substring(4) : hostname; } return null; - } catch (URISyntaxException e) { + } catch (MalformedURLException e) { return null; } } diff --git a/dspace-api/src/test/java/org/dspace/core/UtilsTest.java b/dspace-api/src/test/java/org/dspace/core/UtilsTest.java index 920fa69d6d31..291561ac2536 100644 --- a/dspace-api/src/test/java/org/dspace/core/UtilsTest.java +++ b/dspace-api/src/test/java/org/dspace/core/UtilsTest.java @@ -75,6 +75,12 @@ public void testGetHostName() { assertEquals("Test keep other prefixes", "demo.dspace.org", Utils.getHostName("https://demo.dspace.org")); + assertEquals("Test with parameter", "demo.dspace.org", + Utils.getHostName("https://demo.dspace.org/search?query=test")); + + assertEquals("Test with parameter with space", "demo.dspace.org", + Utils.getHostName("https://demo.dspace.org/search?query=test turbine")); + // This uses a bunch of reserved URI characters assertNull("Test invalid URI returns null", Utils.getHostName("&+,?/@=")); } From 04161a0c4d33cb4c2ab9b64699127a34abc7287e Mon Sep 17 00:00:00 2001 From: Nona Luypaert Date: Tue, 6 Jun 2023 11:24:10 +0200 Subject: [PATCH 238/510] 102124: Fix BitstreamRestController after merge --- .../org/dspace/app/rest/BitstreamRestControllerIT.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 ca4cf2538a3c..57360a97d364 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestControllerIT.java @@ -1261,11 +1261,11 @@ public void checkContentDispositionOfFormats() throws Exception { context.restoreAuthSystemState(); // these formats are configured and files should be downloaded - verifyBitstreamDownload(rtf, "text/richtext", true); - verifyBitstreamDownload(xml, "text/xml", true); - verifyBitstreamDownload(txt, "text/plain", true); + verifyBitstreamDownload(rtf, "text/richtext;charset=UTF-8", true); + verifyBitstreamDownload(xml, "text/xml;charset=UTF-8", true); + verifyBitstreamDownload(txt, "text/plain;charset=UTF-8", true); // this format is not configured and should open inline - verifyBitstreamDownload(html, "text/html", false); + verifyBitstreamDownload(html, "text/html;charset=UTF-8", false); } private void verifyBitstreamDownload(Bitstream file, String contentType, boolean shouldDownload) throws Exception { From 14bb32036c2195802a7db7d790e5994244618062 Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Tue, 6 Jun 2023 16:01:04 +0200 Subject: [PATCH 239/510] DURACOM-136 add explaination of the commandLineParameters in the javadoc --- .../org/dspace/scripts/configuration/ScriptConfiguration.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dspace-api/src/main/java/org/dspace/scripts/configuration/ScriptConfiguration.java b/dspace-api/src/main/java/org/dspace/scripts/configuration/ScriptConfiguration.java index e22063eb4954..642409a924f7 100644 --- a/dspace-api/src/main/java/org/dspace/scripts/configuration/ScriptConfiguration.java +++ b/dspace-api/src/main/java/org/dspace/scripts/configuration/ScriptConfiguration.java @@ -87,6 +87,8 @@ public void setName(String name) { * to the currentUser in the context being an admin, however this can be overwritten by each script individually * if different rules apply * @param context The relevant DSpace context + * @param commandLineParameters the parameters that will be used to start the process if known, + * null otherwise * @return A boolean indicating whether the script is allowed to execute or not */ public boolean isAllowedToExecute(Context context, List commandLineParameters) { From 2b523ba5ac1cbaeaf8bccd9f5b575e7564e14aae Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Tue, 6 Jun 2023 16:01:47 +0200 Subject: [PATCH 240/510] DURACOM-136 improve handling and testing of invalid mimetype --- .../app/rest/ScriptProcessesController.java | 18 ++++++++++++++++-- .../app/rest/ScriptRestRepositoryIT.java | 14 +++++++++++++- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/ScriptProcessesController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/ScriptProcessesController.java index 3aeec9535be5..70149bbb6b0c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/ScriptProcessesController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/ScriptProcessesController.java @@ -19,9 +19,12 @@ import org.dspace.app.rest.repository.ScriptRestRepository; import org.dspace.app.rest.utils.ContextUtil; import org.dspace.core.Context; +import org.dspace.scripts.configuration.ScriptConfiguration; +import org.dspace.scripts.service.ScriptService; import org.dspace.services.RequestService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.rest.webmvc.ControllerUtils; +import org.springframework.data.rest.webmvc.ResourceNotFoundException; import org.springframework.hateoas.RepresentationModel; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; @@ -50,6 +53,9 @@ public class ScriptProcessesController { @Autowired private ScriptRestRepository scriptRestRepository; + @Autowired + private ScriptService scriptService; + @Autowired private RequestService requestService; @@ -80,9 +86,17 @@ public ResponseEntity> startProcess( @RequestMapping(method = RequestMethod.POST, consumes = "!" + MediaType.MULTIPART_FORM_DATA_VALUE) @PreAuthorize("hasAuthority('AUTHENTICATED')") public ResponseEntity> startProcessInvalidMimeType( - @PathVariable(name = "name") String scriptName, - @RequestParam(name = "file", required = false) List files) + @PathVariable(name = "name") String scriptName) throws Exception { + if (log.isTraceEnabled()) { + log.trace("Starting Process for Script with name: " + scriptName); + } + Context context = ContextUtil.obtainContext(requestService.getCurrentRequest().getHttpServletRequest()); + ScriptConfiguration scriptToExecute = scriptService.getScriptConfiguration(scriptName); + + if (scriptToExecute == null) { + throw new ResourceNotFoundException("The script for name: " + scriptName + " wasn't found"); + } throw new DSpaceBadRequestException("Invalid mimetype"); } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ScriptRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ScriptRestRepositoryIT.java index 16e691ef6f95..29a0076d0c85 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ScriptRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ScriptRestRepositoryIT.java @@ -134,6 +134,13 @@ public void findAllScriptsGenericLoggedInUserTest() throws Exception { .andExpect(jsonPath("$.page.totalElements", is(0))); } + @Test + public void findAllScriptsAnonymousUserTest() throws Exception { + getClient().perform(get("/api/system/scripts")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", is(0))); + } + @Test public void findAllScriptsLocalAdminsTest() throws Exception { context.turnOffAuthorisationSystem(); @@ -543,8 +550,13 @@ public void postProcessAndVerifyOutput() throws Exception { public void postProcessAdminWithWrongContentTypeBadRequestException() throws Exception { String token = getAuthToken(admin.getEmail(), password); + + getClient(token) + .perform(post("/api/system/scripts/mock-script/processes")) + .andExpect(status().isBadRequest()); + getClient(token).perform(post("/api/system/scripts/mock-script-invalid/processes")) - .andExpect(status().isBadRequest()); + .andExpect(status().isNotFound()); } @Test From f2a63d9636907b34e572003d8162a2ee6e2aa631 Mon Sep 17 00:00:00 2001 From: Mohamed Saber Eskander Date: Tue, 6 Jun 2023 17:21:20 +0300 Subject: [PATCH 241/510] added new test cases added JavaDocs refactoring --- .../bulkaccesscontrol/BulkAccessControl.java | 150 +++++++++++++++++- ...lkAccessControlCliScriptConfiguration.java | 2 +- .../BulkAccessControlIT.java | 92 +++++++++++ .../BulkAccessConditionRestRepository.java | 5 +- 4 files changed, 243 insertions(+), 6 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/BulkAccessControl.java b/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/BulkAccessControl.java index bef5a15e03f1..c85ebf65f214 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 @@ -48,6 +48,7 @@ import org.dspace.authorize.factory.AuthorizeServiceFactory; import org.dspace.authorize.service.ResourcePolicyService; import org.dspace.content.Bitstream; +import org.dspace.content.Collection; import org.dspace.content.DSpaceObject; import org.dspace.content.Item; import org.dspace.content.factory.ContentServiceFactory; @@ -183,11 +184,22 @@ public void internalRun() throws Exception { updateItemsAndBitstreamsPolices(accessControl); context.complete(); } catch (Exception e) { + e.printStackTrace(); handler.handleException(e); context.abort(); } } + /** + * check the validation of mapped json data, it must + * provide item or bitstream information or both of them + * and check the validation of item node if provided, + * and check the validation of bitstream node if provided. + * + * @param accessControl mapped json data + * @throws SQLException if something goes wrong in the database + * @throws BulkAccessControlException if accessControl is invalid + */ private void validate(BulkAccessControlInput accessControl) throws SQLException { AccessConditionItem item = accessControl.getItem(); @@ -207,6 +219,16 @@ private void validate(BulkAccessControlInput accessControl) throws SQLException } } + /** + * check the validation of item node, the item mode + * must be provided with value 'add' or 'replace' + * if mode equals to add so the information + * of accessCondition must be provided, + * also checking that accessConditions information are valid. + * + * @param item the item node + * @throws BulkAccessControlException if item node is invalid + */ private void validateItemNode(AccessConditionItem item) { String mode = item.getMode(); List accessConditions = item.getAccessConditions(); @@ -228,6 +250,18 @@ private void validateItemNode(AccessConditionItem item) { } } + /** + * check the validation of bitstream node, the bitstream mode + * must be provided with value 'add' or 'replace' + * if mode equals to add so the information of accessConditions + * must be provided, + * also checking that constraint information is valid, + * also checking that accessConditions information are valid. + * + * @param bitstream the bitstream node + * @throws SQLException if something goes wrong in the database + * @throws BulkAccessControlException if bitstream node is invalid + */ private void validateBitstreamNode(AccessConditionBitstream bitstream) throws SQLException { String mode = bitstream.getMode(); List accessConditions = bitstream.getAccessConditions(); @@ -251,6 +285,15 @@ private void validateBitstreamNode(AccessConditionBitstream bitstream) throws SQ } } + /** + * check the validation of constraint node if provided, + * constraint isn't supported when multiple uuids are provided + * or when uuid isn't an Item + * + * @param bitstream the bitstream node + * @throws SQLException if something goes wrong in the database + * @throws BulkAccessControlException if constraint node is invalid + */ private void validateConstraint(AccessConditionBitstream bitstream) throws SQLException { if (uuids.size() > 1 && containsConstraints(bitstream)) { handler.logError("constraint isn't supported when multiple uuids are provided"); @@ -266,6 +309,15 @@ private void validateConstraint(AccessConditionBitstream bitstream) throws SQLEx } } + /** + * check the validation of access condition, + * the access condition name must equal to one of configured access conditions, + * then call {@link AccessConditionOption#validateResourcePolicy( + * Context, String, Date, Date)} if exception happens so, it's invalid. + * + * @param accessCondition the accessCondition + * @throws BulkAccessControlException if the accessCondition is invalid + */ private void validateAccessCondition(AccessCondition accessCondition) { if (!itemAccessConditions.containsKey(accessCondition.getName())) { @@ -282,7 +334,18 @@ private void validateAccessCondition(AccessCondition accessCondition) { } } - public void updateItemsAndBitstreamsPolices(BulkAccessControlInput accessControl) + /** + * find all items of provided {@link #uuids} from solr, + * then update the resource policies of items + * or bitstreams of items (only bitstreams of ORIGINAL bundles) + * and derivative bitstreams, or both of them. + * + * @param accessControl the access control input + * @throws SQLException if something goes wrong in the database + * @throws SearchServiceException if a search error occurs + * @throws AuthorizeException if an authorization error occurs + */ + private void updateItemsAndBitstreamsPolices(BulkAccessControlInput accessControl) throws SQLException, SearchServiceException, AuthorizeException { int counter = 0; @@ -358,6 +421,17 @@ private DiscoverQuery buildDiscoveryQuery(String query, int start, int limit) { return discoverQuery; } + /** + * update the item resource policies, + * when mode equals to 'replace' will remove + * all current resource polices of types 'TYPE_CUSTOM' + * and 'TYPE_INHERITED' then, set the new resource policies. + * + * @param item the item + * @param accessControl the access control input + * @throws SQLException if something goes wrong in the database + * @throws AuthorizeException if an authorization error occurs + */ private void updateItemPolicies(Item item, BulkAccessControlInput accessControl) throws SQLException, AuthorizeException { @@ -372,6 +446,16 @@ private void updateItemPolicies(Item item, BulkAccessControlInput accessControl) logInfo(acItem.getAccessConditions(), acItem.getMode(), item); } + /** + * create the new resource policies of item. + * then, call {@link ItemService#adjustItemPolicies( + * Context, Item, Collection)} to adjust item's default policies. + * + * @param item the item + * @param accessControl the access control input + * @throws SQLException if something goes wrong in the database + * @throws AuthorizeException if an authorization error occurs + */ private void setItemPolicies(Item item, BulkAccessControlInput accessControl) throws SQLException, AuthorizeException { @@ -384,6 +468,16 @@ private void setItemPolicies(Item item, BulkAccessControlInput accessControl) itemService.adjustItemPolicies(context, item, item.getOwningCollection()); } + /** + * update the resource policies of all item's bitstreams + * or bitstreams specified into constraint node, + * and derivative bitstreams. + * + * NOTE: only bitstreams of ORIGINAL bundles + * + * @param item the item contains bitstreams + * @param accessControl the access control input + */ private void updateBitstreamsPolicies(Item item, BulkAccessControlInput accessControl) { AccessConditionBitstream.Constraint constraints = accessControl.getBitstream().getConstraints(); @@ -396,12 +490,33 @@ private void updateBitstreamsPolicies(Item item, BulkAccessControlInput accessCo .forEach(bitstream -> updateBitstreamPolicies(bitstream, item, accessControl)); } + /** + * check that the bitstream node is existed, + * and contains constraint node, + * and constraint contains uuids. + * + * @param bitstream the bitstream node + * @return true when uuids of constraint of bitstream is not empty, + * otherwise false + */ private boolean containsConstraints(AccessConditionBitstream bitstream) { return Objects.nonNull(bitstream) && Objects.nonNull(bitstream.getConstraints()) && isNotEmpty(bitstream.getConstraints().getUuid()); } + /** + * update the bitstream resource policies, + * when mode equals to replace will remove + * all current resource polices of types 'TYPE_CUSTOM' + * and 'TYPE_INHERITED' then, set the new resource policies. + * + * @param bitstream the bitstream + * @param item the item of bitstream + * @param accessControl the access control input + * @throws RuntimeException if something goes wrong in the database + * or an authorization error occurs + */ private void updateBitstreamPolicies(Bitstream bitstream, Item item, BulkAccessControlInput accessControl) { AccessConditionBitstream acBitstream = accessControl.getBitstream(); @@ -420,6 +535,14 @@ private void updateBitstreamPolicies(Bitstream bitstream, Item item, BulkAccessC } + /** + * remove dspace object's read policies. + * + * @param dso the dspace object + * @param type resource policy type + * @throws BulkAccessControlException if something goes wrong + * in the database or an authorization error occurs + */ private void removeReadPolicies(DSpaceObject dso, String type) { try { resourcePolicyService.removePolicies(context, dso, type, Constants.READ); @@ -428,6 +551,18 @@ private void removeReadPolicies(DSpaceObject dso, String type) { } } + /** + * create the new resource policies of bitstream. + * then, call {@link ItemService#adjustItemPolicies( + * Context, Item, Collection)} to adjust bitstream's default policies. + * and also update the resource policies of its derivative bitstreams. + * + * @param bitstream the bitstream + * @param item the item of bitstream + * @param accessControl the access control input + * @throws SQLException if something goes wrong in the database + * @throws AuthorizeException if an authorization error occurs + */ private void setBitstreamPolicies(Bitstream bitstream, Item item, BulkAccessControlInput accessControl) throws SQLException, AuthorizeException { @@ -440,8 +575,17 @@ private void setBitstreamPolicies(Bitstream bitstream, Item item, BulkAccessCont mediaFilterService.updatePoliciesOfDerivativeBitstreams(context, item, bitstream); } + /** + * create the resource policy from the information + * comes from the access condition. + * + * @param obj the dspace object + * @param accessCondition the access condition + * @param accessConditionOption the access condition option + * @throws BulkAccessControlException if an exception occurs + */ private void createResourcePolicy(DSpaceObject obj, AccessCondition accessCondition, - AccessConditionOption AccessConditionOption) { + AccessConditionOption accessConditionOption) { String name = accessCondition.getName(); String description = accessCondition.getDescription(); @@ -449,7 +593,7 @@ private void createResourcePolicy(DSpaceObject obj, AccessCondition accessCondit Date endDate = accessCondition.getEndDate(); try { - AccessConditionOption.createResourcePolicy(context, obj, name, description, startDate, endDate); + accessConditionOption.createResourcePolicy(context, obj, name, description, startDate, endDate); } catch (Exception e) { throw new BulkAccessControlException(e); } diff --git a/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/BulkAccessControlCliScriptConfiguration.java b/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/BulkAccessControlCliScriptConfiguration.java index e329d8894a37..951c93db3030 100644 --- a/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/BulkAccessControlCliScriptConfiguration.java +++ b/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/BulkAccessControlCliScriptConfiguration.java @@ -32,7 +32,7 @@ public Options getOptions() { options.getOption("f").setType(InputStream.class); options.getOption("f").setRequired(true); - options.addOption("e", "eperson", true, "email of eperson doing importing"); + options.addOption("e", "eperson", true, "email of EPerson used to perform actions"); options.getOption("e").setRequired(true); options.addOption("h", "help", false, "help"); 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 6335ecd417f7..02fe4a0bb597 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 @@ -404,6 +404,98 @@ public void performBulkAccessWithNotCollectionAdminEPersonTest() throws Exceptio )); } + @Test + public void performBulkAccessWithNotCommunityAdminEPersonTest() throws Exception { + context.turnOffAuthorisationSystem(); + + // add eperson to admin group + Community communityOne = CommunityBuilder.createCommunity(context) + .withName("community") + .withAdminGroup(eperson) + .build(); + + Community communityTwo = CommunityBuilder.createCommunity(context) + .withName("community") + .build(); + + context.restoreAuthSystemState(); + + String json = "{ \"item\": {\n" + + " \"mode\": \"add\",\n" + + " \"accessConditions\": [\n" + + " {\n" + + " \"name\": \"openaccess\"\n" + + " }\n" + + " ]\n" + + " }}\n"; + + buildJsonFile(json); + + String[] args = new String[] {"bulk-access-control", + "-u", communityOne.getID().toString(), + "-u", communityTwo.getID().toString(), + "-f", tempFilePath, + "-e", eperson.getEmail()}; + + TestDSpaceRunnableHandler testDSpaceRunnableHandler = new TestDSpaceRunnableHandler(); + ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl), testDSpaceRunnableHandler, kernelImpl); + + assertThat(testDSpaceRunnableHandler.getErrorMessages(), hasSize(1)); + assertThat(testDSpaceRunnableHandler.getWarningMessages(), empty()); + + assertThat(testDSpaceRunnableHandler.getErrorMessages(), hasItem( + containsString("Current user is not eligible to execute script bulk-access-control") + )); + } + + @Test + public void performBulkAccessWithNotItemAdminEPersonTest() throws Exception { + context.turnOffAuthorisationSystem(); + + Community community = CommunityBuilder.createCommunity(context) + .withName("community") + .build(); + + Collection collection = CollectionBuilder.createCollection(context, community) + .withName("collection") + .build(); + // add eperson to admin group + Item itemOne = ItemBuilder.createItem(context, collection) + .withAdminUser(eperson) + .build(); + + Item itemTwo = ItemBuilder.createItem(context, collection).build(); + + context.restoreAuthSystemState(); + + String json = "{ \"item\": {\n" + + " \"mode\": \"add\",\n" + + " \"accessConditions\": [\n" + + " {\n" + + " \"name\": \"openaccess\"\n" + + " }\n" + + " ]\n" + + " }}\n"; + + buildJsonFile(json); + + String[] args = new String[] {"bulk-access-control", + "-u", itemOne.getID().toString(), + "-u", itemTwo.getID().toString(), + "-f", tempFilePath, + "-e", eperson.getEmail()}; + + TestDSpaceRunnableHandler testDSpaceRunnableHandler = new TestDSpaceRunnableHandler(); + ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl), testDSpaceRunnableHandler, kernelImpl); + + assertThat(testDSpaceRunnableHandler.getErrorMessages(), hasSize(1)); + assertThat(testDSpaceRunnableHandler.getWarningMessages(), empty()); + + assertThat(testDSpaceRunnableHandler.getErrorMessages(), hasItem( + containsString("Current user is not eligible to execute script bulk-access-control") + )); + } + @Test public void performBulkAccessWithoutRequiredParamTest() throws Exception { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BulkAccessConditionRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BulkAccessConditionRestRepository.java index 8bf0c93c93d7..2bf25978efc4 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BulkAccessConditionRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BulkAccessConditionRestRepository.java @@ -74,8 +74,9 @@ public Class getDomainClass() { private boolean isAuthorized(Context context) { try { - return authorizeService.isAdmin(context) || authorizeService.isComColAdmin(context) || - authorizeService.isItemAdmin(context); + return context.getCurrentUser() != null && + (authorizeService.isAdmin(context) || authorizeService.isComColAdmin(context) || + authorizeService.isItemAdmin(context)); } catch (SQLException e) { throw new RuntimeException(e); } From 32cd24b7538694ec964289bb13027d09ae06b829 Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Tue, 6 Jun 2023 19:45:31 +0200 Subject: [PATCH 242/510] DURACOM-136 restrict script endpoints to authenticated users, add test to proof that standard script are reseved to site administrator --- .../rest/repository/ScriptRestRepository.java | 4 +- .../app/rest/ScriptRestRepositoryIT.java | 42 +++++++++++++++++-- 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ScriptRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ScriptRestRepository.java index 2fc996a327cc..09d65590b6f3 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ScriptRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ScriptRestRepository.java @@ -59,7 +59,7 @@ public class ScriptRestRepository extends DSpaceRestRepository findAll(Context context, Pageable pageable) { List scriptConfigurations = scriptService.getScriptConfigurations(context); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ScriptRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ScriptRestRepositoryIT.java index 29a0076d0c85..42c9f2c9f7b4 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ScriptRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ScriptRestRepositoryIT.java @@ -136,9 +136,9 @@ public void findAllScriptsGenericLoggedInUserTest() throws Exception { @Test public void findAllScriptsAnonymousUserTest() throws Exception { + // this should be changed once we allow anonymous user to execute some scripts getClient().perform(get("/api/system/scripts")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.page.totalElements", is(0))); + .andExpect(status().isUnauthorized()); } @Test @@ -358,12 +358,48 @@ public void findOneScriptByInvalidNameBadRequestExceptionTest() throws Exception .andExpect(status().isNotFound()); } + /** + * This test will create a basic structure of communities, collections and items with some local admins at each + * level and verify that the local admins, nor generic users can run scripts reserved to administrator + * (i.e. default one that don't override the default + * {@link ScriptConfiguration#isAllowedToExecute(org.dspace.core.Context, List)} method implementation + */ @Test public void postProcessNonAdminAuthorizeException() throws Exception { - String token = getAuthToken(eperson.getEmail(), password); + context.turnOffAuthorisationSystem(); + EPerson comAdmin = EPersonBuilder.createEPerson(context) + .withEmail("comAdmin@example.com") + .withPassword(password).build(); + EPerson colAdmin = EPersonBuilder.createEPerson(context) + .withEmail("colAdmin@example.com") + .withPassword(password).build(); + EPerson itemAdmin = EPersonBuilder.createEPerson(context) + .withEmail("itemAdmin@example.com") + .withPassword(password).build(); + Community community = CommunityBuilder.createCommunity(context) + .withName("Community") + .withAdminGroup(comAdmin) + .build(); + Collection collection = CollectionBuilder.createCollection(context, community) + .withName("Collection") + .withAdminGroup(colAdmin) + .build(); + Item item = ItemBuilder.createItem(context, collection).withAdminUser(itemAdmin) + .withTitle("Test item to curate").build(); + context.restoreAuthSystemState(); + String token = getAuthToken(eperson.getEmail(), password); + String comAdmin_token = getAuthToken(eperson.getEmail(), password); + String colAdmin_token = getAuthToken(eperson.getEmail(), password); + String itemAdmin_token = getAuthToken(eperson.getEmail(), password); getClient(token).perform(multipart("/api/system/scripts/mock-script/processes")) .andExpect(status().isForbidden()); + getClient(comAdmin_token).perform(multipart("/api/system/scripts/mock-script/processes")) + .andExpect(status().isForbidden()); + getClient(colAdmin_token).perform(multipart("/api/system/scripts/mock-script/processes")) + .andExpect(status().isForbidden()); + getClient(itemAdmin_token).perform(multipart("/api/system/scripts/mock-script/processes")) + .andExpect(status().isForbidden()); } @Test From 5a8c7a397c342c5f931b209395965bbc365dfa3f Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Tue, 6 Jun 2023 20:12:23 +0200 Subject: [PATCH 243/510] DURACOM-136 open endpoints to retrieve files of process to the user that has triggered the process --- .../ProcessFileTypesLinkRepository.java | 2 +- .../ProcessFilesLinkRepository.java | 2 +- .../ProcessOutputLinkRepository.java | 2 +- .../app/rest/ProcessRestRepositoryIT.java | 81 ++++++++++++++----- 4 files changed, 66 insertions(+), 21 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ProcessFileTypesLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ProcessFileTypesLinkRepository.java index 8eb8d7ef652a..16c8115b29f8 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ProcessFileTypesLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ProcessFileTypesLinkRepository.java @@ -47,7 +47,7 @@ public class ProcessFileTypesLinkRepository extends AbstractDSpaceRestRepository * @throws SQLException If something goes wrong * @throws AuthorizeException If something goes wrong */ - @PreAuthorize("hasAuthority('ADMIN')") + @PreAuthorize("hasPermission(#processId, 'PROCESS', 'READ')") public ProcessFileTypesRest getFileTypesFromProcess(@Nullable HttpServletRequest request, Integer processId, @Nullable Pageable optionalPageable, diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ProcessFilesLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ProcessFilesLinkRepository.java index 42fcef0d628c..5d8251cf19ba 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ProcessFilesLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ProcessFilesLinkRepository.java @@ -47,7 +47,7 @@ public class ProcessFilesLinkRepository extends AbstractDSpaceRestRepository imp * @throws SQLException If something goes wrong * @throws AuthorizeException If something goes wrong */ - @PreAuthorize("hasAuthority('ADMIN')") + @PreAuthorize("hasPermission(#processId, 'PROCESS', 'READ')") public Page getFilesFromProcess(@Nullable HttpServletRequest request, Integer processId, @Nullable Pageable optionalPageable, diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ProcessOutputLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ProcessOutputLinkRepository.java index f9f665d14fd1..f5b3edced2db 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ProcessOutputLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ProcessOutputLinkRepository.java @@ -50,7 +50,7 @@ public class ProcessOutputLinkRepository extends AbstractDSpaceRestRepository im * @throws SQLException If something goes wrong * @throws AuthorizeException If something goes wrong */ - @PreAuthorize("hasAuthority('ADMIN')") + @PreAuthorize("hasPermission(#processId, 'PROCESS', 'READ')") public BitstreamRest getOutputFromProcess(@Nullable HttpServletRequest request, Integer processId, @Nullable Pageable optionalPageable, diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ProcessRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ProcessRestRepositoryIT.java index d76e20b23d19..670d8e2f35b0 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ProcessRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ProcessRestRepositoryIT.java @@ -222,22 +222,35 @@ public void getAllProcessesTestStartingUser() throws Exception { @Test public void getProcessFiles() throws Exception { + context.setCurrentUser(eperson); Process newProcess = ProcessBuilder.createProcess(context, eperson, "mock-script", new LinkedList<>()).build(); - try (InputStream is = IOUtils.toInputStream("Test File For Process", CharEncoding.UTF_8)) { - processService.appendFile(context, process, is, "inputfile", "test.csv"); + processService.appendFile(context, newProcess, is, "inputfile", "test.csv"); } - Bitstream bitstream = processService.getBitstream(context, process, "inputfile"); + Bitstream bitstream = processService.getBitstream(context, newProcess, "inputfile"); String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform(get("/api/system/processes/" + process.getID() + "/files")) + getClient(token).perform(get("/api/system/processes/" + newProcess.getID() + "/files")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.files[0].name", is("test.csv"))) .andExpect(jsonPath("$._embedded.files[0].uuid", is(bitstream.getID().toString()))) .andExpect(jsonPath("$._embedded.files[0].metadata['dspace.process.filetype']" + "[0].value", is("inputfile"))); - + getClient(token).perform(get("/api/core/bitstreams/" + bitstream.getID() + "/content")) + .andExpect(status().isOk()); + // also the user that triggered the process should be able to access the process' files + String epersonToken = getAuthToken(eperson.getEmail(), password); + getClient(epersonToken) + .perform(get("/api/system/processes/" + newProcess.getID() + "/files")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.files[0].name", is("test.csv"))) + .andExpect(jsonPath("$._embedded.files[0].uuid", is(bitstream.getID().toString()))) + .andExpect(jsonPath("$._embedded.files[0].metadata['dspace.process.filetype']" + + "[0].value", is("inputfile"))); + getClient(epersonToken) + .perform(get("/api/core/bitstreams/" + bitstream.getID() + "/content")) + .andExpect(status().isOk()); } @Test @@ -245,25 +258,34 @@ public void getProcessFilesByFileType() throws Exception { Process newProcess = ProcessBuilder.createProcess(context, eperson, "mock-script", new LinkedList<>()).build(); try (InputStream is = IOUtils.toInputStream("Test File For Process", CharEncoding.UTF_8)) { - processService.appendFile(context, process, is, "inputfile", "test.csv"); + processService.appendFile(context, newProcess, is, "inputfile", "test.csv"); } - Bitstream bitstream = processService.getBitstream(context, process, "inputfile"); + Bitstream bitstream = processService.getBitstream(context, newProcess, "inputfile"); String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform(get("/api/system/processes/" + process.getID() + "/files/inputfile")) + getClient(token).perform(get("/api/system/processes/" + newProcess.getID() + "/files/inputfile")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.bitstreams[0].name", is("test.csv"))) + .andExpect(jsonPath("$._embedded.bitstreams[0].uuid", is(bitstream.getID().toString()))) + .andExpect(jsonPath("$._embedded.bitstreams[0].metadata['dspace.process.filetype']" + + "[0].value", is("inputfile"))); + // also the user that triggered the process should be able to access the process' files + String epersonToken = getAuthToken(eperson.getEmail(), password); + getClient(epersonToken) + .perform(get("/api/system/processes/" + newProcess.getID() + "/files/inputfile")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.bitstreams[0].name", is("test.csv"))) .andExpect(jsonPath("$._embedded.bitstreams[0].uuid", is(bitstream.getID().toString()))) .andExpect(jsonPath("$._embedded.bitstreams[0].metadata['dspace.process.filetype']" + "[0].value", is("inputfile"))); - } @Test public void getProcessFilesTypes() throws Exception { + Process newProcess = ProcessBuilder.createProcess(context, eperson, "mock-script", new LinkedList<>()).build(); try (InputStream is = IOUtils.toInputStream("Test File For Process", CharEncoding.UTF_8)) { - processService.appendFile(context, process, is, "inputfile", "test.csv"); + processService.appendFile(context, newProcess, is, "inputfile", "test.csv"); } List fileTypesToCheck = new LinkedList<>(); @@ -271,12 +293,18 @@ public void getProcessFilesTypes() throws Exception { String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform(get("/api/system/processes/" + process.getID() + "/filetypes")) + getClient(token).perform(get("/api/system/processes/" + newProcess.getID() + "/filetypes")) .andExpect(status().isOk()) .andExpect(jsonPath("$", ProcessFileTypesMatcher - .matchProcessFileTypes("filetypes-" + process.getID(), fileTypesToCheck))); - + .matchProcessFileTypes("filetypes-" + newProcess.getID(), fileTypesToCheck))); + // also the user that triggered the process should be able to access the process' files + String epersonToken = getAuthToken(eperson.getEmail(), password); + getClient(epersonToken) + .perform(get("/api/system/processes/" + newProcess.getID() + "/filetypes")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", ProcessFileTypesMatcher + .matchProcessFileTypes("filetypes-" + newProcess.getID(), fileTypesToCheck))); } @Test @@ -811,25 +839,42 @@ public void testFindByCurrentUser() throws Exception { @Test public void getProcessOutput() throws Exception { + context.setCurrentUser(eperson); + Process process1 = ProcessBuilder.createProcess(context, eperson, "mock-script", parameters) + .withStartAndEndTime("10/01/1990", "20/01/1990") + .build(); + try (InputStream is = IOUtils.toInputStream("Test File For Process", CharEncoding.UTF_8)) { - processService.appendLog(process.getID(), process.getName(), "testlog", ProcessLogLevel.INFO); + processService.appendLog(process1.getID(), process1.getName(), "testlog", ProcessLogLevel.INFO); } - processService.createLogBitstream(context, process); + processService.createLogBitstream(context, process1); List fileTypesToCheck = new LinkedList<>(); fileTypesToCheck.add("inputfile"); String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform(get("/api/system/processes/" + process.getID() + "/output")) + getClient(token).perform(get("/api/system/processes/" + process1.getID() + "/output")) .andExpect(status().isOk()) .andExpect(jsonPath("$.name", - is(process.getName() + process.getID() + ".log"))) + is(process1.getName() + process1.getID() + ".log"))) .andExpect(jsonPath("$.type", is("bitstream"))) .andExpect(jsonPath("$.metadata['dc.title'][0].value", - is(process.getName() + process.getID() + ".log"))) + is(process1.getName() + process1.getID() + ".log"))) .andExpect(jsonPath("$.metadata['dspace.process.filetype'][0].value", is("script_output"))); + String epersonToken = getAuthToken(eperson.getEmail(), password); + + getClient(epersonToken) + .perform(get("/api/system/processes/" + process1.getID() + "/output")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.name", + is(process1.getName() + process1.getID() + ".log"))) + .andExpect(jsonPath("$.type", is("bitstream"))) + .andExpect(jsonPath("$.metadata['dc.title'][0].value", + is(process1.getName() + process1.getID() + ".log"))) + .andExpect(jsonPath("$.metadata['dspace.process.filetype'][0].value", + is("script_output"))); } } From bca98cd8f4e2ffda872a30341f0c951f509d2eb5 Mon Sep 17 00:00:00 2001 From: Nona Luypaert Date: Wed, 7 Jun 2023 08:33:51 +0200 Subject: [PATCH 244/510] 102124: Don't loop over extensions if mimetype was already found --- .../org/dspace/app/rest/BitstreamRestController.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java index 4e78d5dae706..a81d62b60148 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 @@ -210,10 +210,12 @@ private boolean isNotAnErrorResponse(HttpServletResponse response) { private boolean checkFormatForContentDisposition(BitstreamFormat format) { List formats = List.of((configurationService.getArrayProperty("webui.content_disposition_format"))); boolean download = formats.contains(format.getMIMEType()); - for (String ext : format.getExtensions()) { - if (formats.contains(ext)) { - download = true; - break; + if (!download) { + for (String ext : format.getExtensions()) { + if (formats.contains(ext)) { + download = true; + break; + } } } return download; From 11df3b8d632f7a917cd3912c72a32fce18848bc2 Mon Sep 17 00:00:00 2001 From: Nikita Krivonosov Date: Wed, 7 Jun 2023 08:46:22 +0200 Subject: [PATCH 245/510] [CST-5728] LYRASIS [Donated]: DSpace7: Implement signposting pattern in DSpace (REST) --- .../controller/LinksetRestController.java | 6 +- .../model/MetadataConfiguration.java | 42 +++++++++ .../processor/item/ItemAuthorProcessor.java | 48 +++------- .../item/ItemIdentifierProcessor.java | 38 ++++++-- .../controller/LinksetRestControllerIT.java | 90 +++++-------------- dspace/config/spring/rest/signposting.xml | 40 ++++++--- 6 files changed, 140 insertions(+), 124 deletions(-) create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/model/MetadataConfiguration.java diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/controller/LinksetRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/controller/LinksetRestController.java index e828820c0c5f..7a1271454302 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/controller/LinksetRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/controller/LinksetRestController.java @@ -76,7 +76,7 @@ public ResponseEntity getOne() { return ResponseEntity.status(HttpStatus.METHOD_NOT_ALLOWED).build(); } - @PreAuthorize("permitAll()") + @PreAuthorize("hasPermission(#uuid, 'ITEM', 'READ')") @RequestMapping(value = "/linksets" + REGEX_REQUESTMAPPING_IDENTIFIER_AS_UUID + "/json", method = RequestMethod.GET, produces = "application/linkset+json") public LinksetRest getJson(HttpServletRequest request, @PathVariable UUID uuid) { @@ -102,7 +102,7 @@ public LinksetRest getJson(HttpServletRequest request, @PathVariable UUID uuid) } } - @PreAuthorize("permitAll()") + @PreAuthorize("hasPermission(#uuid, 'ITEM', 'READ')") @RequestMapping(value = "/linksets" + REGEX_REQUESTMAPPING_IDENTIFIER_AS_UUID, method = RequestMethod.GET, produces = "application/linkset") public LinksetRest getLset(HttpServletRequest request, @PathVariable UUID uuid) { @@ -135,7 +135,7 @@ public LinksetRest getLset(HttpServletRequest request, @PathVariable UUID uuid) } } - @PreAuthorize("permitAll()") + @PreAuthorize("hasPermission(#uuid, 'ITEM', 'READ') && hasPermission(#uuid, 'BITSTREAM', 'READ')") @RequestMapping(value = "/links" + REGEX_REQUESTMAPPING_IDENTIFIER_AS_UUID, method = RequestMethod.GET) public List getHeader(HttpServletRequest request, @PathVariable UUID uuid) { try { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/model/MetadataConfiguration.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/model/MetadataConfiguration.java new file mode 100644 index 000000000000..99dabd003f94 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/model/MetadataConfiguration.java @@ -0,0 +1,42 @@ +/** + * 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.signposting.model; + +/** + * Represents metadata handle configuration. + */ +public class MetadataConfiguration { + + private String metadataField; + + private String pattern; + + public MetadataConfiguration() { + } + + public MetadataConfiguration(String metadataField, String pattern) { + this.metadataField = metadataField; + this.pattern = pattern; + } + + public String getMetadataField() { + return metadataField; + } + + public void setMetadataField(String metadataField) { + this.metadataField = metadataField; + } + + public String getPattern() { + return pattern; + } + + public void setPattern(String pattern) { + this.pattern = pattern; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemAuthorProcessor.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemAuthorProcessor.java index b935e50bfad8..e2dc26b82785 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemAuthorProcessor.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemAuthorProcessor.java @@ -7,24 +7,22 @@ */ package org.dspace.app.rest.signposting.processor.item; +import static java.util.Objects.nonNull; +import static org.apache.commons.lang.StringUtils.isNotBlank; +import static org.apache.commons.lang3.StringUtils.isBlank; import static org.dspace.content.Item.ANY; import java.text.MessageFormat; import java.util.List; import javax.servlet.http.HttpServletRequest; -import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; import org.dspace.app.rest.signposting.model.LinksetNode; import org.dspace.app.rest.signposting.model.LinksetRelationType; -import org.dspace.content.EntityType; import org.dspace.content.Item; import org.dspace.content.MetadataFieldName; -import org.dspace.content.Relationship; -import org.dspace.content.RelationshipType; -import org.dspace.content.service.EntityTypeService; +import org.dspace.content.MetadataSchemaEnum; import org.dspace.content.service.ItemService; -import org.dspace.content.service.RelationshipService; import org.dspace.core.Context; import org.dspace.util.FrontendUrlService; @@ -36,8 +34,6 @@ */ public class ItemAuthorProcessor extends ItemSignpostingProcessor { - private static final String IS_AUTHOR_OF = "isAuthorOf"; - /** * log4j category */ @@ -45,20 +41,12 @@ public class ItemAuthorProcessor extends ItemSignpostingProcessor { private final ItemService itemService; - private final RelationshipService relationshipService; - - private final EntityTypeService entityTypeService; - private String orcidMetadata; public ItemAuthorProcessor(FrontendUrlService frontendUrlService, - ItemService itemService, - RelationshipService relationshipService, - EntityTypeService entityTypeService) { + ItemService itemService) { super(frontendUrlService); this.itemService = itemService; - this.relationshipService = relationshipService; - this.entityTypeService = entityTypeService; setRelation(LinksetRelationType.AUTHOR); } @@ -74,26 +62,16 @@ public void setOrcidMetadata(String orcidMetadata) { public void addLinkSetNodes(Context context, HttpServletRequest request, Item item, List linksetNodes) { try { - EntityType personType = entityTypeService.findByEntityType(context, "Author"); - List itemRelationships = relationshipService.findByItem(context, item); - for (Relationship relationship : itemRelationships) { - - RelationshipType relationshipType = relationship.getRelationshipType(); - boolean hasPersonType = relationshipType.getLeftType().equals(personType) - || relationshipType.getRightType().equals(personType); - boolean isAuthor = relationshipType.getLeftwardType().startsWith(IS_AUTHOR_OF) - || relationshipType.getRightwardType().startsWith(IS_AUTHOR_OF); - - if (hasPersonType && isAuthor) { - Item authorItem = relationship.getLeftItem().getID().equals(item.getID()) - ? relationship.getRightItem() - : relationship.getLeftItem(); - + String authorId = itemService.getMetadataFirstValue(item, MetadataSchemaEnum.RELATION.getName(), + "isAuthorOfPublication", null, ANY); + if (isNotBlank(authorId)) { + Item author = itemService.findByIdOrLegacyId(context, authorId); + if (nonNull(author)) { String authorOrcid = itemService.getMetadataFirstValue( - authorItem, new MetadataFieldName(getOrcidMetadata()), ANY + author, new MetadataFieldName(getOrcidMetadata()), ANY ); - if (StringUtils.isNotBlank(authorOrcid)) { - String authorLink = StringUtils.isBlank(getPattern()) + if (isNotBlank(authorOrcid)) { + String authorLink = isBlank(getPattern()) ? authorOrcid : MessageFormat.format(getPattern(), authorOrcid); linksetNodes.add( diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemIdentifierProcessor.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemIdentifierProcessor.java index 01151fd7d37c..79a820d702d8 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemIdentifierProcessor.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemIdentifierProcessor.java @@ -7,14 +7,19 @@ */ package org.dspace.app.rest.signposting.processor.item; +import static java.util.Objects.isNull; +import static java.util.Objects.nonNull; +import static org.apache.commons.lang3.StringUtils.isNotBlank; + import java.text.MessageFormat; +import java.util.ArrayList; import java.util.List; import javax.servlet.http.HttpServletRequest; -import org.apache.commons.lang3.StringUtils; import org.apache.log4j.Logger; import org.dspace.app.rest.signposting.model.LinksetNode; import org.dspace.app.rest.signposting.model.LinksetRelationType; +import org.dspace.app.rest.signposting.model.MetadataConfiguration; import org.dspace.content.Item; import org.dspace.content.MetadataValue; import org.dspace.content.service.ItemService; @@ -35,6 +40,8 @@ public class ItemIdentifierProcessor extends ItemSignpostingProcessor { */ private static final Logger log = Logger.getLogger(ItemIdentifierProcessor.class); + private List metadataConfigurations; + private final ItemService itemService; public ItemIdentifierProcessor(FrontendUrlService frontendUrlService, ItemService itemService) { @@ -46,14 +53,23 @@ public ItemIdentifierProcessor(FrontendUrlService frontendUrlService, ItemServic @Override public void addLinkSetNodes(Context context, HttpServletRequest request, Item item, List linksetNodes) { + getMetadataConfigurations() + .forEach(metadataHandle -> handleMetadata(context, item, linksetNodes, metadataHandle)); + } + + private void handleMetadata(Context context, + Item item, + List linksetNodes, + MetadataConfiguration metadataConfiguration) { try { - List identifiers = itemService.getMetadataByMetadataString(item, getMetadataField()); + List identifiers = itemService + .getMetadataByMetadataString(item, metadataConfiguration.getMetadataField()); for (MetadataValue identifier : identifiers) { - if (identifier != null) { + if (nonNull(identifier)) { String identifierValue = identifier.getValue(); - if (StringUtils.isNotBlank(identifierValue)) { - if (StringUtils.isNotBlank(getPattern())) { - identifierValue = MessageFormat.format(getPattern(), identifierValue); + if (isNotBlank(identifierValue)) { + if (isNotBlank(metadataConfiguration.getPattern())) { + identifierValue = MessageFormat.format(metadataConfiguration.getPattern(), identifierValue); } linksetNodes.add(new LinksetNode(identifierValue, getRelation(), buildAnchor(context, item))); } @@ -64,4 +80,14 @@ public void addLinkSetNodes(Context context, HttpServletRequest request, } } + public List getMetadataConfigurations() { + if (isNull(metadataConfigurations)) { + metadataConfigurations = new ArrayList<>(); + } + return metadataConfigurations; + } + + public void setMetadataConfigurations(List metadataConfigurations) { + this.metadataConfigurations = metadataConfigurations; + } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/signposting/controller/LinksetRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/signposting/controller/LinksetRestControllerIT.java index 39a7ef664ba6..180ad3672170 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/signposting/controller/LinksetRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/signposting/controller/LinksetRestControllerIT.java @@ -15,7 +15,6 @@ import java.io.InputStream; import java.text.MessageFormat; -import java.util.List; import org.apache.commons.codec.CharEncoding; import org.apache.commons.io.IOUtils; @@ -34,7 +33,6 @@ import org.dspace.content.EntityType; import org.dspace.content.Item; import org.dspace.content.MetadataSchemaEnum; -import org.dspace.content.MetadataValue; import org.dspace.content.RelationshipType; import org.dspace.content.WorkspaceItem; import org.dspace.content.authority.Choices; @@ -57,7 +55,7 @@ public class LinksetRestControllerIT extends AbstractControllerIntegrationTest { private static final String doiPattern = "https://doi.org/{0}"; private static final String orcidPattern = "http://orcid.org/{0}"; private static final String doi = "10.1007/978-3-642-35233-1_18"; - private static final String AUTHOR = "Author"; + private static final String PERSON_ENTITY_TYPE = "Person"; private Collection collection; @@ -247,11 +245,7 @@ public void findOneItemThatIsInWorkspaceJsonLinksets() throws Exception { context.restoreAuthSystemState(); getClient().perform(get("/signposting/linksets/" + workspaceItem.getItem().getID() + "/json")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.linkset", - Matchers.hasSize(1))) - .andExpect(jsonPath("$.linkset[0].cite-as[0].href", - Matchers.hasToString(MessageFormat.format(doiPattern, doi)))); + .andExpect(status().isUnauthorized()); } @Test @@ -265,11 +259,7 @@ public void findOneWithdrawnItemJsonLinksets() throws Exception { context.restoreAuthSystemState(); getClient().perform(get("/signposting/linksets/" + item.getID() + "/json")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.linkset", - Matchers.hasSize(1))) - .andExpect(jsonPath("$.linkset[0].cite-as[0].href", - Matchers.hasToString(MessageFormat.format(doiPattern, doi)))); + .andExpect(status().isUnauthorized()); } @Test @@ -284,11 +274,7 @@ public void findOneEmbargoItemJsonLinksets() throws Exception { context.restoreAuthSystemState(); getClient().perform(get("/signposting/linksets/" + item.getID() + "/json")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.linkset", - Matchers.hasSize(1))) - .andExpect(jsonPath("$.linkset[0].cite-as[0].href", - Matchers.hasToString(MessageFormat.format(doiPattern, doi)))); + .andExpect(status().isUnauthorized()); } @Test @@ -305,11 +291,7 @@ public void findOneRestrictedItemJsonLinksets() throws Exception { context.restoreAuthSystemState(); getClient().perform(get("/signposting/linksets/" + item.getID() + "/json")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.linkset", - Matchers.hasSize(1))) - .andExpect(jsonPath("$.linkset[0].cite-as[0].href", - Matchers.hasToString(MessageFormat.format(doiPattern, doi)))); + .andExpect(status().isUnauthorized()); } @Test @@ -390,20 +372,22 @@ public void findOneItemLsetLinksets() throws Exception { } @Test - public void findTypedLinkForItem() throws Exception { + public void findTypedLinkForItemWithAuthor() throws Exception { String bitstreamContent = "ThisIsSomeDummyText"; String bitstreamMimeType = "text/plain"; String orcidValue = "orcidValue"; context.turnOffAuthorisationSystem(); - Item author = ItemBuilder.createItem(context, collection) - .withMetadata("dspace", "entity", "type", AUTHOR) + Collection personCollection = CollectionBuilder.createCollection(context, parentCommunity) + .withEntityType(PERSON_ENTITY_TYPE) + .build(); + + Item author = ItemBuilder.createItem(context, personCollection) + .withPersonIdentifierLastName("familyName") + .withPersonIdentifierFirstName("firstName") .withMetadata(PERSON.getName(), "identifier", "orcid", orcidValue) .build(); - List metadata = itemService.getMetadata(author, "dspace", "entity", - "type", Item.ANY, false); - itemService.removeMetadataValues(context, author, List.of(metadata.get(0))); Item publication = ItemBuilder.createItem(context, collection) .withTitle("Item Test") .withMetadata("dc", "identifier", "doi", doi) @@ -420,7 +404,7 @@ public void findTypedLinkForItem() throws Exception { } EntityType publicationEntityType = EntityTypeBuilder.createEntityTypeBuilder(context, "Publication").build(); - EntityType authorEntityType = EntityTypeBuilder.createEntityTypeBuilder(context, AUTHOR).build(); + EntityType authorEntityType = EntityTypeBuilder.createEntityTypeBuilder(context, PERSON_ENTITY_TYPE).build(); RelationshipType isAuthorOfPublicationRelationshipType = RelationshipTypeBuilder.createRelationshipTypeBuilder(context, publicationEntityType, authorEntityType, "isAuthorOfPublication", "isPublicationOfAuthor", @@ -434,16 +418,21 @@ public void findTypedLinkForItem() throws Exception { context.restoreAuthSystemState(); String url = configurationService.getProperty("dspace.ui.url"); + String dcIdentifierUriMetadataValue = itemService + .getMetadataFirstValue(publication, "dc", "identifier", "uri", Item.ANY); + getClient().perform(get("/signposting/links/" + publication.getID()) .header("Accept", "application/json")) .andExpect(status().isOk()) .andExpect(jsonPath("$", - Matchers.hasSize(4))) + Matchers.hasSize(5))) .andExpect(jsonPath("$[?(@.href == '" + MessageFormat.format(orcidPattern, orcidValue) + "' " + "&& @.rel == 'author' " + "&& @.type == 'text/html')]").exists()) .andExpect(jsonPath("$[?(@.href == '" + MessageFormat.format(doiPattern, doi) + "' " + "&& @.rel == 'cite-as')]").exists()) + .andExpect(jsonPath("$[?(@.href == '" + dcIdentifierUriMetadataValue + "' " + + "&& @.rel == 'cite-as')]").exists()) .andExpect(jsonPath("$[?(@.href == '" + url + "/bitstreams/" + bitstream.getID() + "/download' " + "&& @.rel == 'item' " + "&& @.type == 'text/plain')]").exists()) @@ -569,18 +558,7 @@ public void findTypedLinkForRestrictedBitstream() throws Exception { String uiUrl = configurationService.getProperty("dspace.ui.url"); getClient().perform(get("/signposting/links/" + bitstream.getID()) .header("Accept", "application/json")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$", - Matchers.hasSize(3))) - .andExpect(jsonPath("$[?(@.href == '" + uiUrl + "/entities/publication/" + item.getID() + "' " + - "&& @.rel == 'collection' " + - "&& @.type == 'text/html')]").exists()) - .andExpect(jsonPath("$[?(@.href == '" + uiUrl + "/signposting/linksets/" + item.getID() + "' " + - "&& @.rel == 'linkset' " + - "&& @.type == 'application/linkset')]").exists()) - .andExpect(jsonPath("$[?(@.href == '" + uiUrl + "/signposting/linksets/" + item.getID() + "/json" + - "' && @.rel == 'linkset' " + - "&& @.type == 'application/linkset+json')]").exists()); + .andExpect(status().isUnauthorized()); DSpaceServicesFactory.getInstance().getConfigurationService().reloadConfig(); metadataAuthorityService.clearCache(); @@ -613,18 +591,7 @@ public void findTypedLinkForBitstreamUnderEmbargo() throws Exception { String uiUrl = configurationService.getProperty("dspace.ui.url"); getClient().perform(get("/signposting/links/" + bitstream.getID()) .header("Accept", "application/json")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$", - Matchers.hasSize(3))) - .andExpect(jsonPath("$[?(@.href == '" + uiUrl + "/entities/publication/" + item.getID() + "' " + - "&& @.rel == 'collection' " + - "&& @.type == 'text/html')]").exists()) - .andExpect(jsonPath("$[?(@.href == '" + uiUrl + "/signposting/linksets/" + item.getID() + "' " + - "&& @.rel == 'linkset' " + - "&& @.type == 'application/linkset')]").exists()) - .andExpect(jsonPath("$[?(@.href == '" + uiUrl + "/signposting/linksets/" + item.getID() + "/json" + - "' && @.rel == 'linkset' " + - "&& @.type == 'application/linkset+json')]").exists()); + .andExpect(status().isUnauthorized()); DSpaceServicesFactory.getInstance().getConfigurationService().reloadConfig(); metadataAuthorityService.clearCache(); @@ -656,18 +623,7 @@ public void findTypedLinkForBitstreamOfWorkspaceItem() throws Exception { String uiUrl = configurationService.getProperty("dspace.ui.url"); getClient().perform(get("/signposting/links/" + bitstream.getID()) .header("Accept", "application/json")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$", - Matchers.hasSize(3))) - .andExpect(jsonPath("$[?(@.href == '" + uiUrl + "/items/" + item.getID() + "' " + - "&& @.rel == 'collection' " + - "&& @.type == 'text/html')]").exists()) - .andExpect(jsonPath("$[?(@.href == '" + uiUrl + "/signposting/linksets/" + item.getID() + "' " + - "&& @.rel == 'linkset' " + - "&& @.type == 'application/linkset')]").exists()) - .andExpect(jsonPath("$[?(@.href == '" + uiUrl + "/signposting/linksets/" + item.getID() + "/json" + - "' && @.rel == 'linkset' " + - "&& @.type == 'application/linkset+json')]").exists()); + .andExpect(status().isUnauthorized()); DSpaceServicesFactory.getInstance().getConfigurationService().reloadConfig(); metadataAuthorityService.clearCache(); diff --git a/dspace/config/spring/rest/signposting.xml b/dspace/config/spring/rest/signposting.xml index ed0c8912eb00..c8bc63c6f0dd 100644 --- a/dspace/config/spring/rest/signposting.xml +++ b/dspace/config/spring/rest/signposting.xml @@ -1,6 +1,7 @@ + xmlns:util="http://www.springframework.org/schema/util" + xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd"> @@ -10,31 +11,44 @@ - - + + + This metadata field must be used on Person entity. + - - - + + - - + + + + + + + + + + - + - + - - - From f7ff85bbd55f6f7897c76b61782e4017d326de07 Mon Sep 17 00:00:00 2001 From: Mohamed Saber Eskander Date: Wed, 7 Jun 2023 12:53:19 +0300 Subject: [PATCH 246/510] solved issue of 'could not initialize proxy - no Session' --- .../bulkaccesscontrol/BulkAccessControl.java | 9 +- .../BulkAccessControlIT.java | 110 ++++++++++++++++++ 2 files changed, 118 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 c85ebf65f214..50e1022dbe37 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 @@ -184,7 +184,6 @@ public void internalRun() throws Exception { updateItemsAndBitstreamsPolices(accessControl); context.complete(); } catch (Exception e) { - e.printStackTrace(); handler.handleException(e); context.abort(); } @@ -481,6 +480,14 @@ private void setItemPolicies(Item item, BulkAccessControlInput accessControl) private void updateBitstreamsPolicies(Item item, BulkAccessControlInput accessControl) { AccessConditionBitstream.Constraint constraints = accessControl.getBitstream().getConstraints(); + // look over all the bundles and force initialization of bitstreams collection + // to avoid lazy initialization exception + long count = item.getBundles() + .stream() + .flatMap(bundle -> + bundle.getBitstreams().stream()) + .count(); + item.getBundles(CONTENT_BUNDLE_NAME).stream() .flatMap(bundle -> bundle.getBitstreams().stream()) .filter(bitstream -> constraints == null || 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 02fe4a0bb597..73f02e40494c 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 @@ -1709,6 +1709,116 @@ public void performBulkAccessWithReplaceModeAndAppendModeIsEnabledTest() throws } } + @Test + public void performBulkAccessWithReplaceModeOnItemsWithMultipleBundlesTest() throws Exception { + context.turnOffAuthorisationSystem(); + + Group adminGroup = groupService.findByName(context, Group.ADMIN); + + Community parentCommunity = CommunityBuilder.createCommunity(context) + .withName("parent community") + .build(); + + Community subCommunity = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("sub community one") + .build(); + + Collection collection = CollectionBuilder.createCollection(context, subCommunity) + .withName("collection one") + .build(); + + Item itemOne = ItemBuilder.createItem(context, collection).build(); + Item itemTwo = ItemBuilder.createItem(context, collection).build(); + ItemBuilder.createItem(context, collection).build(); + + Bundle bundleOne = BundleBuilder.createBundle(context, itemOne) + .withName("ORIGINAL") + .build(); + + Bundle bundleTwo = BundleBuilder.createBundle(context, itemTwo) + .withName("ORIGINAL") + .build(); + + BundleBuilder.createBundle(context, itemTwo) + .withName("ORIGINAL") + .build(); + + BundleBuilder.createBundle(context, itemOne) + .withName("TEXT") + .build(); + + Bitstream bitstreamOne; + Bitstream bitstreamTwo; + String bitstreamContent = "Dummy content"; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + bitstreamOne = + BitstreamBuilder.createBitstream(context, bundleOne, is) + .withName("bistream of bundle one") + .build(); + } + + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + bitstreamTwo = + BitstreamBuilder.createBitstream(context, bundleTwo, is) + .withName("bitstream of bundle two") + .build(); + } + + context.restoreAuthSystemState(); + + String jsonOne = "{\n" + + " \"bitstream\": {\n" + + " \"constraints\": {\n" + + " \"uuid\": []\n" + + " },\n" + + " \"mode\": \"replace\",\n" + + " \"accessConditions\": [\n" + + " {\n" + + " \"name\": \"administrator\",\n" + + " \"startDate\": null,\n" + + " \"endDate\": null\n" + + " }\n" + + " ]\n" + + " }\n" + + "}"; + + buildJsonFile(jsonOne); + + String[] args = new String[] { + "bulk-access-control", + "-u", subCommunity.getID().toString(), + "-f", tempFilePath, + "-e", admin.getEmail() + }; + + TestDSpaceRunnableHandler testDSpaceRunnableHandler = new TestDSpaceRunnableHandler(); + ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl), testDSpaceRunnableHandler, kernelImpl); + + assertThat(testDSpaceRunnableHandler.getErrorMessages(), empty()); + assertThat(testDSpaceRunnableHandler.getWarningMessages(), empty()); + assertThat(testDSpaceRunnableHandler.getInfoMessages(), hasSize(2)); + + assertThat(testDSpaceRunnableHandler.getInfoMessages(), containsInAnyOrder( + containsString("Replacing Bitstream {" + bitstreamOne.getID() + + "} policy to access conditions:{administrator}"), + containsString("Replacing Bitstream {" + bitstreamTwo.getID() + + "} policy to access conditions:{administrator}") + )); + + bitstreamOne = context.reloadEntity(bitstreamOne); + bitstreamTwo = context.reloadEntity(bitstreamTwo); + + assertThat(bitstreamOne.getResourcePolicies(), hasSize(1)); + assertThat(bitstreamOne.getResourcePolicies(), hasItem( + matches(READ, adminGroup, "administrator", TYPE_CUSTOM) + )); + + assertThat(bitstreamTwo.getResourcePolicies(), hasSize(1)); + assertThat(bitstreamTwo.getResourcePolicies(), hasItem( + matches(READ, adminGroup, "administrator", TYPE_CUSTOM) + )); + } + @Test public void performBulkAccessWithHelpParamTest() throws Exception { From f8e3dad2349941aff8e68bbfa048159639e42039 Mon Sep 17 00:00:00 2001 From: Nikita Krivonosov Date: Thu, 8 Jun 2023 00:08:45 +0200 Subject: [PATCH 247/510] [CST-5728] LYRASIS [Donated]: DSpace7: Implement signposting pattern in DSpace (REST) --- .../controller/LinksetRestController.java | 20 +++++++-- .../controller/LinksetRestControllerIT.java | 44 +++++++++++++++++++ 2 files changed, 60 insertions(+), 4 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/controller/LinksetRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/controller/LinksetRestController.java index 7a1271454302..fa6461a76f95 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/controller/LinksetRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/controller/LinksetRestController.java @@ -7,6 +7,7 @@ */ package org.dspace.app.rest.signposting.controller; +import static java.lang.String.format; import static org.dspace.app.rest.utils.RegexUtils.REGEX_REQUESTMAPPING_IDENTIFIER_AS_UUID; import java.sql.SQLException; @@ -38,6 +39,7 @@ import org.springframework.data.rest.webmvc.ResourceNotFoundException; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; @@ -83,17 +85,18 @@ public LinksetRest getJson(HttpServletRequest request, @PathVariable UUID uuid) try { Context context = ContextUtil.obtainContext(request); - Item dso = itemService.find(context, uuid); - if (dso == null) { + Item item = itemService.find(context, uuid); + if (item == null) { throw new ResourceNotFoundException("No such Item: " + uuid); } + verifyItemIsDiscoverable(item); List linksetNodes = new ArrayList<>(); - if (dso.getType() == Constants.ITEM) { + if (item.getType() == Constants.ITEM) { List ispp = new DSpace().getServiceManager() .getServicesByType(ItemSignpostingProcessor.class); for (ItemSignpostingProcessor sp : ispp) { - sp.addLinkSetNodes(context, request, dso, linksetNodes); + sp.addLinkSetNodes(context, request, item, linksetNodes); } } return converter.toRest(LinksetMapper.map(linksetNodes), utils.obtainProjection()); @@ -113,6 +116,7 @@ public LinksetRest getLset(HttpServletRequest request, @PathVariable UUID uuid) if (item == null) { throw new ResourceNotFoundException("No such Item: " + uuid); } + verifyItemIsDiscoverable(item); List linksetNodes = new ArrayList<>(); List ispp = new DSpace().getServiceManager() @@ -151,6 +155,7 @@ public List getHeader(HttpServletRequest request, @PathVariable U List linksetNodes = new ArrayList<>(); if (dso.getType() == Constants.ITEM) { + verifyItemIsDiscoverable((Item) dso); List ispp = new DSpace().getServiceManager() .getServicesByType(ItemSignpostingProcessor.class); for (ItemSignpostingProcessor sp : ispp) { @@ -171,4 +176,11 @@ public List getHeader(HttpServletRequest request, @PathVariable U throw new RuntimeException(e); } } + + private static void verifyItemIsDiscoverable(Item item) { + if (!item.isDiscoverable()) { + String message = format("Item with uuid [%s] is not Discoverable", item.getID().toString()); + throw new AccessDeniedException(message); + } + } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/signposting/controller/LinksetRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/signposting/controller/LinksetRestControllerIT.java index 180ad3672170..b976e40bf27e 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/signposting/controller/LinksetRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/signposting/controller/LinksetRestControllerIT.java @@ -294,6 +294,20 @@ public void findOneRestrictedItemJsonLinksets() throws Exception { .andExpect(status().isUnauthorized()); } + @Test + public void findOneUnDiscoverableItemJsonLinksets() throws Exception { + context.turnOffAuthorisationSystem(); + Item item = ItemBuilder.createItem(context, collection) + .withTitle("Withdrawn Item") + .withMetadata("dc", "identifier", "doi", doi) + .makeUnDiscoverable() + .build(); + context.restoreAuthSystemState(); + + getClient().perform(get("/signposting/linksets/" + item.getID() + "/json")) + .andExpect(status().isUnauthorized()); + } + @Test public void findOneBitstreamJsonLinksets() throws Exception { String bitstreamContent = "ThisIsSomeDummyText"; @@ -371,6 +385,19 @@ public void findOneItemLsetLinksets() throws Exception { .andExpect(content().string(Matchers.containsString(typeRelation))); } + @Test + public void findOneUnDiscoverableItemLsetLinksets() throws Exception { + context.turnOffAuthorisationSystem(); + Item item = ItemBuilder.createItem(context, collection) + .makeUnDiscoverable() + .build(); + context.restoreAuthSystemState(); + + getClient().perform(get("/signposting/linksets/" + item.getID()) + .header("Accept", "application/linkset")) + .andExpect(status().isUnauthorized()); + } + @Test public void findTypedLinkForItemWithAuthor() throws Exception { String bitstreamContent = "ThisIsSomeDummyText"; @@ -630,4 +657,21 @@ public void findTypedLinkForBitstreamOfWorkspaceItem() throws Exception { choiceAuthorityService.clearCache(); } + @Test + public void findTypedLinkForUnDiscoverableItem() throws Exception { + context.turnOffAuthorisationSystem(); + Item item = ItemBuilder.createItem(context, collection) + .makeUnDiscoverable() + .build(); + context.restoreAuthSystemState(); + + getClient().perform(get("/signposting/links/" + item.getID()) + .header("Accept", "application/json")) + .andExpect(status().isUnauthorized()); + + DSpaceServicesFactory.getInstance().getConfigurationService().reloadConfig(); + metadataAuthorityService.clearCache(); + choiceAuthorityService.clearCache(); + } + } From 39df261411729e9e6ac49b2c82dacb5a6174ccb0 Mon Sep 17 00:00:00 2001 From: Nikita Krivonosov Date: Thu, 8 Jun 2023 11:49:30 +0200 Subject: [PATCH 248/510] [CST-5728] LYRASIS [Donated]: DSpace7: Implement signposting pattern in DSpace (REST) --- .../rest/signposting/controller/LinksetRestController.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/controller/LinksetRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/controller/LinksetRestController.java index fa6461a76f95..d7718dce0139 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/controller/LinksetRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/controller/LinksetRestController.java @@ -139,6 +139,10 @@ public LinksetRest getLset(HttpServletRequest request, @PathVariable UUID uuid) } } + // In @PreAuthorize(...) we're using "&&" (and) instead of "||" (or) because if hasPermission() is unable + // to find object of specified type with specified uuid it returns "true". + // For example: if we pass uuid of Bitstream: hasPermission(#uuid, 'ITEM', 'READ') returns "true", because + // it will use ItemService with uuid of bitstream. @PreAuthorize("hasPermission(#uuid, 'ITEM', 'READ') && hasPermission(#uuid, 'BITSTREAM', 'READ')") @RequestMapping(value = "/links" + REGEX_REQUESTMAPPING_IDENTIFIER_AS_UUID, method = RequestMethod.GET) public List getHeader(HttpServletRequest request, @PathVariable UUID uuid) { From 7757c4e898373fc06b03e070eca67bd58d2ad4cf Mon Sep 17 00:00:00 2001 From: Francesco Pio Scognamiglio Date: Thu, 8 Jun 2023 13:50:11 +0200 Subject: [PATCH 249/510] [DURACOM-153] fix validation to use the retrieved zip file on saf import --- .../src/main/java/org/dspace/app/itemimport/ItemImport.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImport.java b/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImport.java index c94e163243c1..b32de11f7a7f 100644 --- a/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImport.java +++ b/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImport.java @@ -332,17 +332,19 @@ protected void process(Context context, ItemImportService itemImportService, */ protected void readZip(Context context, ItemImportService itemImportService) throws Exception { Optional optionalFileStream = Optional.empty(); + Optional validationFileStream = Optional.empty(); if (!remoteUrl) { // manage zip via upload optionalFileStream = handler.getFileStream(context, zipfilename); + validationFileStream = handler.getFileStream(context, zipfilename); } else { // manage zip via remote url optionalFileStream = Optional.ofNullable(new URL(zipfilename).openStream()); + validationFileStream = Optional.ofNullable(new URL(zipfilename).openStream()); } - if (optionalFileStream.isPresent()) { + if (validationFileStream.isPresent()) { // validate zip file - Optional validationFileStream = handler.getFileStream(context, zipfilename); if (validationFileStream.isPresent()) { validateZip(validationFileStream.get()); } From a00dd83a0c9a3272ff3004c40bfcf03691664296 Mon Sep 17 00:00:00 2001 From: Nona Luypaert Date: Thu, 8 Jun 2023 20:47:27 +0200 Subject: [PATCH 250/510] 102124: Avoid null pointer when checking disposition --- .../java/org/dspace/app/rest/BitstreamRestController.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java index a81d62b60148..b4de375fe882 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 @@ -208,6 +208,10 @@ private boolean isNotAnErrorResponse(HttpServletResponse response) { } private boolean checkFormatForContentDisposition(BitstreamFormat format) { + // never automatically download undefined formats + if (format == null) { + return false; + } List formats = List.of((configurationService.getArrayProperty("webui.content_disposition_format"))); boolean download = formats.contains(format.getMIMEType()); if (!download) { From 1b06c644548c6d34154a2dfd5162903c56539a7f Mon Sep 17 00:00:00 2001 From: Nona Luypaert Date: Thu, 8 Jun 2023 21:49:13 +0200 Subject: [PATCH 251/510] 102052: Remove getPrimaryBitstream from PrimaryBitstreamController --- .../app/rest/PrimaryBitstreamController.java | 25 ------------------- .../BundlePrimaryBitstreamLinkRepository.java | 4 +++ 2 files changed, 4 insertions(+), 25 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/PrimaryBitstreamController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/PrimaryBitstreamController.java index 59ab1bba763a..e0357b8a4117 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/PrimaryBitstreamController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/PrimaryBitstreamController.java @@ -16,7 +16,6 @@ import org.dspace.app.rest.converter.ConverterService; import org.dspace.app.rest.exception.UnprocessableEntityException; -import org.dspace.app.rest.model.BitstreamRest; import org.dspace.app.rest.model.BundleRest; import org.dspace.app.rest.model.hateoas.BundleResource; import org.dspace.app.rest.repository.BundlePrimaryBitstreamLinkRepository; @@ -53,30 +52,6 @@ public class PrimaryBitstreamController { @Autowired private Utils utils; - /** - * This method retrieves a primaryBitstream on the given Bundle. - * Returns null if Bundle doesn't have a primaryBitstream. - *
- * curl -X GET "http://{dspace.server.url}/api/core/bundles/{bundle-uuid}/primaryBitstream" - * - * - * @param uuid The UUID of the Bundle of which the primaryBitstream will be retrieved - * @param request The HttpServletRequest - * @return The primaryBitstream, or null if not found - */ - @PreAuthorize("hasPermission(#uuid, 'BUNDLE', 'READ')") - @RequestMapping(method = RequestMethod.GET) - public ResponseEntity> getPrimaryBitstream(@PathVariable UUID uuid, - HttpServletRequest request) { - BitstreamRest bitstreamRest = repository.getPrimaryBitstream(null, uuid, null, utils.obtainProjection()); - if (bitstreamRest == null) { - return ControllerUtils.toEmptyResponse(HttpStatus.NO_CONTENT); - } else { - return ControllerUtils.toResponseEntity(HttpStatus.OK, new HttpHeaders(), - (RepresentationModel) converter.toResource(bitstreamRest)); - } - } - /** * This method creates a primaryBitstream on the given Bundle. *
diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BundlePrimaryBitstreamLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BundlePrimaryBitstreamLinkRepository.java index a013aa0dd002..607751453e7f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BundlePrimaryBitstreamLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BundlePrimaryBitstreamLinkRepository.java @@ -39,6 +39,10 @@ public class BundlePrimaryBitstreamLinkRepository extends AbstractDSpaceRestRepo /** * Retrieves the primaryBitstream of a Bundle. + * Returns null if Bundle doesn't have a primaryBitstream. + *
+ * curl -X GET "http://{dspace.server.url}/api/core/bundles/{bundle-uuid}/primaryBitstream" + * * * @param request The HttpServletRequest if relevant * @param bundleId The UUID of the Bundle From c5028f7f1febab43f65528a200bb3381ea5d0542 Mon Sep 17 00:00:00 2001 From: Nona Luypaert Date: Thu, 8 Jun 2023 23:08:48 +0200 Subject: [PATCH 252/510] 102052: Small improvements --- .../app/rest/PrimaryBitstreamController.java | 3 +- .../BundlePrimaryBitstreamLinkRepository.java | 8 ++- .../rest/PrimaryBitstreamControllerIT.java | 64 +++++++++---------- 3 files changed, 40 insertions(+), 35 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/PrimaryBitstreamController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/PrimaryBitstreamController.java index e0357b8a4117..c236954dab48 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/PrimaryBitstreamController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/PrimaryBitstreamController.java @@ -128,8 +128,7 @@ public ResponseEntity> deletePrimaryBitstream(@PathVariab private Bitstream getBitstreamFromRequest(Context context, HttpServletRequest request) { List dsoList = utils.constructDSpaceObjectList(context, utils.getStringListFromRequest(request)); if (dsoList.size() != 1 || dsoList.get(0).getType() != BITSTREAM) { - throw new UnprocessableEntityException("No bitstream has been specified " + - "or the data cannot be resolved to a bitstream."); + throw new UnprocessableEntityException("URI does not resolve to an existing bitstream."); } return (Bitstream) dsoList.get(0); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BundlePrimaryBitstreamLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BundlePrimaryBitstreamLinkRepository.java index 607751453e7f..3d11379cd328 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BundlePrimaryBitstreamLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BundlePrimaryBitstreamLinkRepository.java @@ -131,8 +131,14 @@ public void deletePrimaryBitstream(Context context, UUID bundleId) { * @param context The current DSpace context * @param bundleId The UUID of the Bundle * @param bitstream The Bitstream to use as primaryBitstream - * @param shouldBeSet Whether a primaryBitstream is should already be set + * @param shouldBeSet Whether a primaryBitstream should already be set: + * primaryBitstream should be present before updating or deleting, + * it should be null before adding * @return The Bundle + * @throws ResourceNotFoundException if the bundle is not found + * @throws DSpaceBadRequestException if primaryBitstream exists during an POST, + * if primaryBitstream is null during an UPDATE or DELETE + * @throws UnprocessableEntityException if the bundle does not contain the bitstream */ private Bundle setPrimaryBitstream(Context context, UUID bundleId, Bitstream bitstream, boolean shouldBeSet) throws SQLException { diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/PrimaryBitstreamControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/PrimaryBitstreamControllerIT.java index d8bc6c8cf98a..ba1039bce631 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/PrimaryBitstreamControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/PrimaryBitstreamControllerIT.java @@ -82,7 +82,7 @@ public void testGetPrimaryBitstream() throws Exception { bundle.setPrimaryBitstreamID(bitstream); String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform(get(getBundleUrl(bundle.getID()))) + getClient(token).perform(get(getBundlePrimaryBitstreamUrl(bundle.getID()))) .andExpect(status().isOk()) .andExpect(jsonPath("$", BitstreamMatcher.matchProperties(bitstream))); } @@ -90,14 +90,14 @@ public void testGetPrimaryBitstream() throws Exception { @Test public void testGetPrimaryBitstreamBundleNotFound() throws Exception { String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform(get(getBundleUrl(UUID.randomUUID()))) + getClient(token).perform(get(getBundlePrimaryBitstreamUrl(UUID.randomUUID()))) .andExpect(status().isNotFound()); } @Test public void testGetPrimaryBitstreamNonExisting() throws Exception { String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform(get(getBundleUrl(bundle.getID()))) + getClient(token).perform(get(getBundlePrimaryBitstreamUrl(bundle.getID()))) .andExpect(status().isNoContent()) .andExpect(jsonPath("$").doesNotExist()); } @@ -105,7 +105,7 @@ public void testGetPrimaryBitstreamNonExisting() throws Exception { @Test public void testPostPrimaryBitstream() throws Exception { String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform(post(getBundleUrl(bundle.getID())) + getClient(token).perform(post(getBundlePrimaryBitstreamUrl(bundle.getID())) .contentType(textUriContentType) .content(getBitstreamUrl(bitstream.getID()))) .andExpect(status().isCreated()) @@ -119,7 +119,7 @@ public void testPostPrimaryBitstream() throws Exception { @Test public void testPostPrimaryBitstreamBundleNotFound() throws Exception { String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform(post(getBundleUrl(UUID.randomUUID())) + getClient(token).perform(post(getBundlePrimaryBitstreamUrl(UUID.randomUUID())) .contentType(textUriContentType) .content(getBitstreamUrl(bitstream.getID()))) .andExpect(status().isNotFound()); @@ -131,7 +131,7 @@ public void testPostPrimaryBitstreamBundleNotFound() throws Exception { @Test public void testPostPrimaryBitstreamInvalidBitstream() throws Exception { String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform(post(getBundleUrl(bundle.getID())) + getClient(token).perform(post(getBundlePrimaryBitstreamUrl(bundle.getID())) .contentType(textUriContentType) .content(getBitstreamUrl(UUID.randomUUID()))) .andExpect(status().isUnprocessableEntity()); @@ -148,7 +148,7 @@ public void testPostPrimaryBitstreamAlreadyExists() throws Exception { context.restoreAuthSystemState(); String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform(post(getBundleUrl(bundle.getID())) + getClient(token).perform(post(getBundlePrimaryBitstreamUrl(bundle.getID())) .contentType(textUriContentType) .content(getBitstreamUrl(bitstream2.getID()))) .andExpect(status().isBadRequest()); @@ -165,7 +165,7 @@ public void testPostPrimaryBitstreamNotInBundle() throws Exception { context.restoreAuthSystemState(); String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform(post(getBundleUrl(bundle.getID())) + getClient(token).perform(post(getBundlePrimaryBitstreamUrl(bundle.getID())) .contentType(textUriContentType) .content(getBitstreamUrl(bitstream2.getID()))) .andExpect(status().isUnprocessableEntity()); @@ -186,7 +186,7 @@ public void testPostPrimaryBitstreamCommunityAdmin() throws Exception { context.restoreAuthSystemState(); String token = getAuthToken(eperson.getEmail(), password); - getClient(token).perform(post(getBundleUrl(bundle2.getID())) + getClient(token).perform(post(getBundlePrimaryBitstreamUrl(bundle2.getID())) .contentType(textUriContentType) .content(getBitstreamUrl(bitstream2.getID()))) .andExpect(status().isCreated()) @@ -208,7 +208,7 @@ public void testPostPrimaryBitstreamCollectionAdmin() throws Exception { context.restoreAuthSystemState(); String token = getAuthToken(eperson.getEmail(), password); - getClient(token).perform(post(getBundleUrl(bundle2.getID())) + getClient(token).perform(post(getBundlePrimaryBitstreamUrl(bundle2.getID())) .contentType(textUriContentType) .content(getBitstreamUrl(bitstream2.getID()))) .andExpect(status().isCreated()) @@ -229,7 +229,7 @@ public void testPostPrimaryBitstreamItemAdmin() throws Exception { context.restoreAuthSystemState(); String token = getAuthToken(eperson.getEmail(), password); - getClient(token).perform(post(getBundleUrl(bundle2.getID())) + getClient(token).perform(post(getBundlePrimaryBitstreamUrl(bundle2.getID())) .contentType(textUriContentType) .content(getBitstreamUrl(bitstream2.getID()))) .andExpect(status().isCreated()) @@ -244,7 +244,7 @@ public void testPostPrimaryBitstreamItemAdmin() throws Exception { public void testPostPrimaryBitstreamForbidden() throws Exception { String token = getAuthToken(eperson.getEmail(), password); - getClient(token).perform(post(getBundleUrl(bundle.getID())) + getClient(token).perform(post(getBundlePrimaryBitstreamUrl(bundle.getID())) .contentType(textUriContentType) .content(getBitstreamUrl(bitstream.getID()))) .andExpect(status().isForbidden()); @@ -252,7 +252,7 @@ public void testPostPrimaryBitstreamForbidden() throws Exception { @Test public void testPostPrimaryBitstreamUnauthenticated() throws Exception { - getClient().perform(post(getBundleUrl(bundle.getID())) + getClient().perform(post(getBundlePrimaryBitstreamUrl(bundle.getID())) .contentType(textUriContentType) .content(getBitstreamUrl(bitstream.getID()))) .andExpect(status().isUnauthorized()); @@ -266,7 +266,7 @@ public void testUpdatePrimaryBitstream() throws Exception { context.restoreAuthSystemState(); String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform(put(getBundleUrl(bundle.getID())) + getClient(token).perform(put(getBundlePrimaryBitstreamUrl(bundle.getID())) .contentType(textUriContentType) .content(getBitstreamUrl(bitstream2.getID()))) .andExpect(status().isOk()) @@ -280,7 +280,7 @@ public void testUpdatePrimaryBitstream() throws Exception { @Test public void testUpdatePrimaryBitstreamBundleNotFound() throws Exception { String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform(put(getBundleUrl(UUID.randomUUID())) + getClient(token).perform(put(getBundlePrimaryBitstreamUrl(UUID.randomUUID())) .contentType(textUriContentType) .content(getBitstreamUrl(bitstream.getID()))) .andExpect(status().isNotFound()); @@ -291,7 +291,7 @@ public void testUpdatePrimaryBitstreamInvalidBitstream() throws Exception { bundle.setPrimaryBitstreamID(bitstream); String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform(put(getBundleUrl(bundle.getID())) + getClient(token).perform(put(getBundlePrimaryBitstreamUrl(bundle.getID())) .contentType(textUriContentType) .content(getBitstreamUrl(UUID.randomUUID()))) .andExpect(status().isUnprocessableEntity()); @@ -303,7 +303,7 @@ public void testUpdatePrimaryBitstreamInvalidBitstream() throws Exception { @Test public void testUpdatePrimaryBitstreamNonExisting() throws Exception { String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform(put(getBundleUrl(bundle.getID())) + getClient(token).perform(put(getBundlePrimaryBitstreamUrl(bundle.getID())) .contentType(textUriContentType) .content(getBitstreamUrl(bitstream.getID()))) .andExpect(status().isBadRequest()); @@ -321,7 +321,7 @@ public void testUpdatePrimaryBitstreamNotInBundle() throws Exception { context.restoreAuthSystemState(); String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform(put(getBundleUrl(bundle.getID())) + getClient(token).perform(put(getBundlePrimaryBitstreamUrl(bundle.getID())) .contentType(textUriContentType) .content(getBitstreamUrl(bitstream2.getID()))) .andExpect(status().isUnprocessableEntity()); @@ -344,7 +344,7 @@ public void testUpdatePrimaryBitstreamCommunityAdmin() throws Exception { context.restoreAuthSystemState(); String token = getAuthToken(eperson.getEmail(), password); - getClient(token).perform(put(getBundleUrl(bundle2.getID())) + getClient(token).perform(put(getBundlePrimaryBitstreamUrl(bundle2.getID())) .contentType(textUriContentType) .content(getBitstreamUrl(bitstream3.getID()))) .andExpect(status().isOk()) @@ -368,7 +368,7 @@ public void testUpdatePrimaryBitstreamCollectionAdmin() throws Exception { context.restoreAuthSystemState(); String token = getAuthToken(eperson.getEmail(), password); - getClient(token).perform(put(getBundleUrl(bundle2.getID())) + getClient(token).perform(put(getBundlePrimaryBitstreamUrl(bundle2.getID())) .contentType(textUriContentType) .content(getBitstreamUrl(bitstream3.getID()))) .andExpect(status().isOk()) @@ -391,7 +391,7 @@ public void testUpdatePrimaryBitstreamItemAdmin() throws Exception { context.restoreAuthSystemState(); String token = getAuthToken(eperson.getEmail(), password); - getClient(token).perform(put(getBundleUrl(bundle2.getID())) + getClient(token).perform(put(getBundlePrimaryBitstreamUrl(bundle2.getID())) .contentType(textUriContentType) .content(getBitstreamUrl(bitstream3.getID()))) .andExpect(status().isOk()) @@ -410,7 +410,7 @@ public void testUpdatePrimaryBitstreamForbidden() throws Exception { context.restoreAuthSystemState(); String token = getAuthToken(eperson.getEmail(), password); - getClient(token).perform(put(getBundleUrl(bundle.getID())) + getClient(token).perform(put(getBundlePrimaryBitstreamUrl(bundle.getID())) .contentType(textUriContentType) .content(getBitstreamUrl(bitstream2.getID()))) .andExpect(status().isForbidden()); @@ -423,7 +423,7 @@ public void testUpdatePrimaryBitstreamUnauthenticated() throws Exception { Bitstream bitstream2 = createBitstream(bundle); context.restoreAuthSystemState(); - getClient().perform(put(getBundleUrl(bundle.getID())) + getClient().perform(put(getBundlePrimaryBitstreamUrl(bundle.getID())) .contentType(textUriContentType) .content(getBitstreamUrl(bitstream2.getID()))) .andExpect(status().isUnauthorized()); @@ -434,7 +434,7 @@ public void testDeletePrimaryBitstream() throws Exception { bundle.setPrimaryBitstreamID(bitstream); String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform(delete(getBundleUrl(bundle.getID()))) + getClient(token).perform(delete(getBundlePrimaryBitstreamUrl(bundle.getID()))) .andExpect(status().isNoContent()); // verify primaryBitstream was actually deleted bundle = context.reloadEntity(bundle); @@ -447,14 +447,14 @@ public void testDeletePrimaryBitstream() throws Exception { @Test public void testDeletePrimaryBitstreamBundleNotFound() throws Exception { String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform(delete(getBundleUrl(UUID.randomUUID()))) + getClient(token).perform(delete(getBundlePrimaryBitstreamUrl(UUID.randomUUID()))) .andExpect(status().isNotFound()); } @Test public void testDeletePrimaryBitstreamBundleNonExisting() throws Exception { String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform(delete(getBundleUrl(bundle.getID()))) + getClient(token).perform(delete(getBundlePrimaryBitstreamUrl(bundle.getID()))) .andExpect(status().isBadRequest()); // verify primaryBitstream is still null bundle = context.reloadEntity(bundle); @@ -474,7 +474,7 @@ public void testDeletePrimaryBitstreamCommunityAdmin() throws Exception { context.restoreAuthSystemState(); String token = getAuthToken(eperson.getEmail(), password); - getClient(token).perform(delete(getBundleUrl(bundle2.getID()))) + getClient(token).perform(delete(getBundlePrimaryBitstreamUrl(bundle2.getID()))) .andExpect(status().isNoContent()); // verify primaryBitstream was actually deleted bundle2 = context.reloadEntity(bundle2); @@ -496,7 +496,7 @@ public void testDeletePrimaryBitstreamCollectionAdmin() throws Exception { context.restoreAuthSystemState(); String token = getAuthToken(eperson.getEmail(), password); - getClient(token).perform(delete(getBundleUrl(bundle2.getID()))) + getClient(token).perform(delete(getBundlePrimaryBitstreamUrl(bundle2.getID()))) .andExpect(status().isNoContent()); // verify primaryBitstream was actually deleted bundle2 = context.reloadEntity(bundle2); @@ -517,7 +517,7 @@ public void testDeletePrimaryBitstreamItemAdmin() throws Exception { context.restoreAuthSystemState(); String token = getAuthToken(eperson.getEmail(), password); - getClient(token).perform(delete(getBundleUrl(bundle2.getID()))) + getClient(token).perform(delete(getBundlePrimaryBitstreamUrl(bundle2.getID()))) .andExpect(status().isNoContent()); // verify primaryBitstream was actually deleted bundle2 = context.reloadEntity(bundle2); @@ -532,7 +532,7 @@ public void testDeletePrimaryBitstreamForbidden() throws Exception { bundle.setPrimaryBitstreamID(bitstream); String token = getAuthToken(eperson.getEmail(), password); - getClient(token).perform(delete(getBundleUrl(bundle.getID()))) + getClient(token).perform(delete(getBundlePrimaryBitstreamUrl(bundle.getID()))) .andExpect(status().isForbidden()); } @@ -540,11 +540,11 @@ public void testDeletePrimaryBitstreamForbidden() throws Exception { public void testDeletePrimaryBitstreamUnauthenticated() throws Exception { bundle.setPrimaryBitstreamID(bitstream); - getClient().perform(delete(getBundleUrl(bundle.getID()))) + getClient().perform(delete(getBundlePrimaryBitstreamUrl(bundle.getID()))) .andExpect(status().isUnauthorized()); } - private String getBundleUrl(UUID uuid) { + private String getBundlePrimaryBitstreamUrl(UUID uuid) { return "/api/core/bundles/" + uuid + "/primaryBitstream"; } From 1b1830f037453ec5e58225cc23d8ec9767b8526d Mon Sep 17 00:00:00 2001 From: Yury Bondarenko Date: Fri, 9 Jun 2023 13:46:28 +0200 Subject: [PATCH 253/510] Remove unnecessary DescribeStep change As far as I can tell this never did anything - `relationship.*` is not a valid MDF, so it's unclear what it was supposed to do - everything keeps working if I leave this part out --- .../app/rest/submit/step/DescribeStep.java | 25 ------------------- 1 file changed, 25 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/DescribeStep.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/DescribeStep.java index fb4fc5bb9449..ee81d14ca7a6 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/DescribeStep.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/DescribeStep.java @@ -7,9 +7,6 @@ */ package org.dspace.app.rest.submit.step; -import static org.apache.commons.collections4.CollectionUtils.isEmpty; -import static org.apache.commons.lang3.StringUtils.isBlank; - import java.util.ArrayList; import java.util.List; import javax.servlet.http.HttpServletRequest; @@ -31,10 +28,8 @@ import org.dspace.app.util.DCInputsReaderException; import org.dspace.app.util.SubmissionStepConfig; import org.dspace.content.InProgressSubmission; -import org.dspace.content.Item; import org.dspace.content.MetadataValue; import org.dspace.content.RelationshipMetadataService; -import org.dspace.content.RelationshipMetadataValue; import org.dspace.content.factory.ContentServiceFactory; import org.dspace.core.Context; import org.dspace.core.Utils; @@ -147,26 +142,6 @@ private void readField(InProgressSubmission obj, SubmissionStepConfig config, Da } } } - - if (input.isRelationshipField() && isBlank(input.getFieldName())) { - Item item = obj.getItem(); - String key = "relationship." + input.getRelationshipType(); - if (isEmpty(data.getMetadata().get(key))) { - data.getMetadata().put(key, new ArrayList<>()); - } - for (RelationshipMetadataValue metadataValue : - relationshipMetadataService.getRelationshipMetadata(item, true)) { - if (metadataValue.getMetadataField().getElement().equals(input.getRelationshipType())) { - MetadataValueRest dto = new MetadataValueRest(); - dto.setAuthority(metadataValue.getAuthority()); - dto.setConfidence(metadataValue.getConfidence()); - dto.setLanguage(metadataValue.getLanguage()); - dto.setPlace(metadataValue.getPlace()); - dto.setValue(metadataValue.getValue()); - data.getMetadata().get(key).add(dto); - } - } - } } } } From 1ab04518a31a0a9e8916a61688954a33206134cc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 9 Jun 2023 14:42:46 +0000 Subject: [PATCH 254/510] Bump scala-library from 2.13.2 to 2.13.9 in /dspace-api Bumps [scala-library](https://github.com/scala/scala) from 2.13.2 to 2.13.9. - [Release notes](https://github.com/scala/scala/releases) - [Commits](https://github.com/scala/scala/compare/v2.13.2...v2.13.9) --- updated-dependencies: - dependency-name: org.scala-lang:scala-library dependency-type: direct:development ... 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 8f20f423aab5..d41c51638c3a 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -917,7 +917,7 @@ org.scala-lang scala-library - 2.13.2 + 2.13.9 test
From ff504e35d1a5b721c47a3ddde7e4e909630631bd Mon Sep 17 00:00:00 2001 From: damian Date: Fri, 9 Jun 2023 16:56:26 +0200 Subject: [PATCH 255/510] Variable naming change. Javadocs addded. --- .../main/java/org/dspace/browse/ItemCounter.java | 13 +++++++++++++ .../main/java/org/dspace/content/Collection.java | 5 +++++ .../org/dspace/content/CollectionServiceImpl.java | 7 +++++++ .../src/main/java/org/dspace/content/Community.java | 5 +++++ .../org/dspace/content/CommunityServiceImpl.java | 7 +++++++ .../dspace/content/service/CollectionService.java | 8 ++++++++ .../dspace/content/service/CommunityService.java | 7 +++++++ .../app/rest/converter/CollectionConverter.java | 3 ++- .../app/rest/converter/CommunityConverter.java | 2 +- .../org/dspace/app/rest/model/CollectionRest.java | 10 +++++----- .../org/dspace/app/rest/model/CommunityRest.java | 10 +++++----- 11 files changed, 65 insertions(+), 12 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/browse/ItemCounter.java b/dspace-api/src/main/java/org/dspace/browse/ItemCounter.java index da88b0657ff8..1718e07ab2ca 100644 --- a/dspace-api/src/main/java/org/dspace/browse/ItemCounter.java +++ b/dspace-api/src/main/java/org/dspace/browse/ItemCounter.java @@ -50,6 +50,12 @@ public class ItemCounter { */ private Context context; + /** + * This field is used to hold singular instance of a class. + * Singleton pattern is used but this class should be + * refactored to modern DSpace approach (injectible service). + */ + private static ItemCounter instance; protected ItemService itemService; @@ -73,6 +79,13 @@ public ItemCounter(Context context) throws ItemCountException { this.useCache = configurationService.getBooleanProperty("webui.strengths.cache", true); } + /** + * Get the singular instance of a class. + * It creates a new instance at the first usage of this method. + * + * @return instance af a class + * @throws ItemCountException when error occurs + */ public static ItemCounter getInstance() throws ItemCountException { if (instance == null) { instance = new ItemCounter(ContextUtil.obtainCurrentRequestContext()); diff --git a/dspace-api/src/main/java/org/dspace/content/Collection.java b/dspace-api/src/main/java/org/dspace/content/Collection.java index 6ee61abf5720..6caae7f47b24 100644 --- a/dspace-api/src/main/java/org/dspace/content/Collection.java +++ b/dspace-api/src/main/java/org/dspace/content/Collection.java @@ -337,6 +337,11 @@ private CollectionService getCollectionService() { return collectionService; } + /** + * return count of the collection items + * + * @return int + */ public int countArchivedItem() { try { return collectionService.countArchivedItem(this); 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 0c5ac94e1c9f..a5fcf0c1aa37 100644 --- a/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java @@ -1069,6 +1069,13 @@ public List findAllCollectionsByEntityType(Context context, String e return collectionList; } + /** + * Returns total collection archived items + * + * @param collection Collection + * @return total collection archived items + * @throws ItemCountException + */ @Override public int countArchivedItem(Collection collection) throws ItemCountException { return ItemCounter.getInstance().getCount(collection); diff --git a/dspace-api/src/main/java/org/dspace/content/Community.java b/dspace-api/src/main/java/org/dspace/content/Community.java index d0477d116f3a..39705369e3d6 100644 --- a/dspace-api/src/main/java/org/dspace/content/Community.java +++ b/dspace-api/src/main/java/org/dspace/content/Community.java @@ -265,6 +265,11 @@ private CommunityService getCommunityService() { return communityService; } + /** + * return count of the community items + * + * @return int + */ public int countArchivedItem() { try { return communityService.getArchivedItems(this); diff --git a/dspace-api/src/main/java/org/dspace/content/CommunityServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/CommunityServiceImpl.java index 496a06fd33a5..f693c62e9247 100644 --- a/dspace-api/src/main/java/org/dspace/content/CommunityServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/CommunityServiceImpl.java @@ -711,6 +711,13 @@ public int countTotal(Context context) throws SQLException { return communityDAO.countRows(context); } + /** + * Returns total community archived items + * + * @param community Community + * @return total community archived items + * @throws ItemCountException + */ @Override public int getArchivedItems(Community community) throws ItemCountException { return ItemCounter.getInstance().getCount(community); diff --git a/dspace-api/src/main/java/org/dspace/content/service/CollectionService.java b/dspace-api/src/main/java/org/dspace/content/service/CollectionService.java index 3a562d4e5b51..d7f47882c085 100644 --- a/dspace-api/src/main/java/org/dspace/content/service/CollectionService.java +++ b/dspace-api/src/main/java/org/dspace/content/service/CollectionService.java @@ -470,5 +470,13 @@ public int countCollectionsWithSubmit(String q, Context context, Community commu */ public List findAllCollectionsByEntityType(Context context, String entityType) throws SearchServiceException; + + /** + * Returns total collection archived items + * + * @param collection Collection + * @return total collection archived items + * @throws ItemCountException + */ int countArchivedItem(Collection collection) throws ItemCountException; } diff --git a/dspace-api/src/main/java/org/dspace/content/service/CommunityService.java b/dspace-api/src/main/java/org/dspace/content/service/CommunityService.java index 65454f26ea72..9326da2203e9 100644 --- a/dspace-api/src/main/java/org/dspace/content/service/CommunityService.java +++ b/dspace-api/src/main/java/org/dspace/content/service/CommunityService.java @@ -294,5 +294,12 @@ public void removeSubcommunity(Context context, Community parentCommunity, Commu int countTotal(Context context) throws SQLException; + /** + * Returns total community archived items + * + * @param community Community + * @return total community archived items + * @throws ItemCountException + */ int getArchivedItems(Community community) throws ItemCountException; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/CollectionConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/CollectionConverter.java index 836af81ef016..a5d06b5b3b45 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/CollectionConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/CollectionConverter.java @@ -26,9 +26,10 @@ public class CollectionConverter extends DSpaceObjectConverter Date: Fri, 9 Jun 2023 22:23:00 +0200 Subject: [PATCH 256/510] 102052: 403 checks for Admin ITs in PrimaryBitstreamControllerIT --- .../rest/PrimaryBitstreamControllerIT.java | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/PrimaryBitstreamControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/PrimaryBitstreamControllerIT.java index ba1039bce631..b5c67c640fff 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/PrimaryBitstreamControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/PrimaryBitstreamControllerIT.java @@ -195,6 +195,12 @@ public void testPostPrimaryBitstreamCommunityAdmin() throws Exception { // verify primaryBitstream was actually added bundle2 = context.reloadEntity(bundle2); Assert.assertEquals(bitstream2, bundle2.getPrimaryBitstream()); + + // verify Community Admin can't set a primaryBitstream outside their own Community + getClient(token).perform(post(getBundlePrimaryBitstreamUrl(bundle.getID())) + .contentType(textUriContentType) + .content(getBitstreamUrl(bitstream.getID()))) + .andExpect(status().isForbidden()); } @Test @@ -217,6 +223,12 @@ public void testPostPrimaryBitstreamCollectionAdmin() throws Exception { // verify primaryBitstream was actually added bundle2 = context.reloadEntity(bundle2); Assert.assertEquals(bitstream2, bundle2.getPrimaryBitstream()); + + // verify Collection Admin can't set a primaryBitstream outside their own Collection + getClient(token).perform(post(getBundlePrimaryBitstreamUrl(bundle.getID())) + .contentType(textUriContentType) + .content(getBitstreamUrl(bitstream.getID()))) + .andExpect(status().isForbidden()); } @Test @@ -238,6 +250,12 @@ public void testPostPrimaryBitstreamItemAdmin() throws Exception { // verify primaryBitstream was actually added bundle2 = context.reloadEntity(bundle2); Assert.assertEquals(bitstream2, bundle2.getPrimaryBitstream()); + + // verify Item Admin can't set a primaryBitstream outside their own Item + getClient(token).perform(post(getBundlePrimaryBitstreamUrl(bundle.getID())) + .contentType(textUriContentType) + .content(getBitstreamUrl(bitstream.getID()))) + .andExpect(status().isForbidden()); } @Test @@ -353,6 +371,13 @@ public void testUpdatePrimaryBitstreamCommunityAdmin() throws Exception { // verify primaryBitstream was actually updated bundle2 = context.reloadEntity(bundle2); Assert.assertEquals(bitstream3, bundle2.getPrimaryBitstream()); + + bundle.setPrimaryBitstreamID(bitstream); + // verify Community Admin can't update a primaryBitstream outside their own Community + getClient(token).perform(put(getBundlePrimaryBitstreamUrl(bundle.getID())) + .contentType(textUriContentType) + .content(getBitstreamUrl(bitstream.getID()))) + .andExpect(status().isForbidden()); } @Test @@ -377,6 +402,13 @@ public void testUpdatePrimaryBitstreamCollectionAdmin() throws Exception { // verify primaryBitstream was actually updated bundle2 = context.reloadEntity(bundle2); Assert.assertEquals(bitstream3, bundle2.getPrimaryBitstream()); + + bundle.setPrimaryBitstreamID(bitstream); + // verify Collection Admin can't update a primaryBitstream outside their own Collection + getClient(token).perform(put(getBundlePrimaryBitstreamUrl(bundle.getID())) + .contentType(textUriContentType) + .content(getBitstreamUrl(bitstream.getID()))) + .andExpect(status().isForbidden()); } @Test @@ -400,6 +432,13 @@ public void testUpdatePrimaryBitstreamItemAdmin() throws Exception { // verify primaryBitstream was actually updated bundle2 = context.reloadEntity(bundle2); Assert.assertEquals(bitstream3, bundle2.getPrimaryBitstream()); + + bundle.setPrimaryBitstreamID(bitstream); + // verify Item Admin can't update a primaryBitstream outside their own Item + getClient(token).perform(put(getBundlePrimaryBitstreamUrl(bundle.getID())) + .contentType(textUriContentType) + .content(getBitstreamUrl(bitstream.getID()))) + .andExpect(status().isForbidden()); } @Test @@ -482,6 +521,13 @@ public void testDeletePrimaryBitstreamCommunityAdmin() throws Exception { // verify bitstream itself still exists Assert.assertEquals(1, bundle2.getBitstreams().size()); Assert.assertEquals(bitstream2, bundle2.getBitstreams().get(0)); + + bundle.setPrimaryBitstreamID(bitstream); + // verify Community Admin can't delete a primaryBitstream outside their own Community + getClient(token).perform(delete(getBundlePrimaryBitstreamUrl(bundle.getID())) + .contentType(textUriContentType) + .content(getBitstreamUrl(bitstream.getID()))) + .andExpect(status().isForbidden()); } @Test @@ -504,6 +550,13 @@ public void testDeletePrimaryBitstreamCollectionAdmin() throws Exception { // verify bitstream itself still exists Assert.assertEquals(1, bundle2.getBitstreams().size()); Assert.assertEquals(bitstream2, bundle2.getBitstreams().get(0)); + + bundle.setPrimaryBitstreamID(bitstream); + // verify Collection Admin can't delete a primaryBitstream outside their own Collection + getClient(token).perform(delete(getBundlePrimaryBitstreamUrl(bundle.getID())) + .contentType(textUriContentType) + .content(getBitstreamUrl(bitstream.getID()))) + .andExpect(status().isForbidden()); } @Test @@ -525,6 +578,13 @@ public void testDeletePrimaryBitstreamItemAdmin() throws Exception { // verify bitstream itself still exists Assert.assertEquals(1, bundle2.getBitstreams().size()); Assert.assertEquals(bitstream2, bundle2.getBitstreams().get(0)); + + bundle.setPrimaryBitstreamID(bitstream); + // verify Item Admin can't delete a primaryBitstream outside their own Item + getClient(token).perform(delete(getBundlePrimaryBitstreamUrl(bundle.getID())) + .contentType(textUriContentType) + .content(getBitstreamUrl(bitstream.getID()))) + .andExpect(status().isForbidden()); } @Test From af8cf8671bdf99bf6390aa29c1fb1da275b7327a Mon Sep 17 00:00:00 2001 From: Nona Luypaert Date: Mon, 12 Jun 2023 11:41:33 +0200 Subject: [PATCH 257/510] 102124: Update webui.content_disposition_format comment --- 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 d81346012164..9adf065fac93 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -1289,7 +1289,8 @@ webui.content_disposition_threshold = 8388608 #### Content Attachment Disposition Formats #### # # Set which mimetypes, file extensions will NOT be opened inline -# Files with these mimetypes/extensions will always be downloaded +# Files with these mimetypes/extensions will always be downloaded, +# regardless of the threshold above webui.content_disposition_format = text/richtext #### Multi-file HTML document/site settings ##### From 63e1519c6ecec30efc9222cbc38e6d10d86f2c23 Mon Sep 17 00:00:00 2001 From: Nikita Krivonosov Date: Tue, 13 Jun 2023 15:19:19 +0200 Subject: [PATCH 258/510] [DURACOM-156] Singposting: feedback after initial deployment --- .../controller/LinksetRestController.java | 8 +-- .../bitstream/BitstreamTypeProcessor.java | 4 +- .../processor/item/ItemAuthorProcessor.java | 4 +- .../processor/item/ItemLicenseProcessor.java | 2 +- .../processor/item/ItemTypeProcessor.java | 4 +- .../controller/LinksetRestControllerIT.java | 58 +++++-------------- 6 files changed, 19 insertions(+), 61 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/controller/LinksetRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/controller/LinksetRestController.java index d7718dce0139..8089c518fff0 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/controller/LinksetRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/controller/LinksetRestController.java @@ -72,15 +72,9 @@ public ResponseEntity getAll() { return ResponseEntity.status(HttpStatus.METHOD_NOT_ALLOWED).build(); } - @PreAuthorize("permitAll()") - @RequestMapping(value = "/linksets" + REGEX_REQUESTMAPPING_IDENTIFIER_AS_UUID, method = RequestMethod.GET) - public ResponseEntity getOne() { - return ResponseEntity.status(HttpStatus.METHOD_NOT_ALLOWED).build(); - } - @PreAuthorize("hasPermission(#uuid, 'ITEM', 'READ')") @RequestMapping(value = "/linksets" + REGEX_REQUESTMAPPING_IDENTIFIER_AS_UUID + "/json", - method = RequestMethod.GET, produces = "application/linkset+json") + method = RequestMethod.GET) public LinksetRest getJson(HttpServletRequest request, @PathVariable UUID uuid) { try { Context context = ContextUtil.obtainContext(request); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/bitstream/BitstreamTypeProcessor.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/bitstream/BitstreamTypeProcessor.java index 2f46193ca81d..005a8009836d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/bitstream/BitstreamTypeProcessor.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/bitstream/BitstreamTypeProcessor.java @@ -48,9 +48,7 @@ public void addLinkSetNodes(Context context, HttpServletRequest request, String type = bitstreamService.getMetadataFirstValue(bitstream, "dc", "type", null, Item.ANY); if (StringUtils.isNotBlank(type)) { String typeSchemeUri = mapConverterDSpaceToSchemaOrgUri.getValue(type); - linksetNodes.add( - new LinksetNode(typeSchemeUri, getRelation(), "text/html", buildAnchor(bitstream)) - ); + linksetNodes.add(new LinksetNode(typeSchemeUri, getRelation(), buildAnchor(bitstream))); } } catch (Exception e) { log.error(e.getMessage(), e); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemAuthorProcessor.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemAuthorProcessor.java index e2dc26b82785..1bb215c46864 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemAuthorProcessor.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemAuthorProcessor.java @@ -74,9 +74,7 @@ author, new MetadataFieldName(getOrcidMetadata()), ANY String authorLink = isBlank(getPattern()) ? authorOrcid : MessageFormat.format(getPattern(), authorOrcid); - linksetNodes.add( - new LinksetNode(authorLink, getRelation(), "text/html", buildAnchor(context, item)) - ); + linksetNodes.add(new LinksetNode(authorLink, getRelation(), buildAnchor(context, item))); } } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemLicenseProcessor.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemLicenseProcessor.java index 2c356129963e..1a26fa7695b1 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemLicenseProcessor.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemLicenseProcessor.java @@ -41,7 +41,7 @@ public void addLinkSetNodes(Context context, HttpServletRequest request, try { String licenseUrl = creativeCommonsService.getLicenseURL(context, item); if (StringUtils.isNotBlank(licenseUrl)) { - linksetNodes.add(new LinksetNode(licenseUrl, getRelation(), "text/html", buildAnchor(context, item))); + linksetNodes.add(new LinksetNode(licenseUrl, getRelation(), buildAnchor(context, item))); } } catch (Exception e) { log.error(e.getMessage(), e); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemTypeProcessor.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemTypeProcessor.java index f1f41de055ee..ddd2da12d59a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemTypeProcessor.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemTypeProcessor.java @@ -45,12 +45,12 @@ public ItemTypeProcessor(FrontendUrlService frontendUrlService) { public void addLinkSetNodes(Context context, HttpServletRequest request, Item item, List linksetNodes) { try { - linksetNodes.add(new LinksetNode(ABOUT_PAGE_URI, getRelation(), "text/html", buildAnchor(context, item))); + linksetNodes.add(new LinksetNode(ABOUT_PAGE_URI, getRelation(), buildAnchor(context, item))); String type = itemService.getMetadataFirstValue(item, "dc", "type", null, Item.ANY); if (StringUtils.isNotBlank(type)) { String typeSchemeUri = mapConverterDSpaceToSchemaOrgUri.getValue(type); linksetNodes.add( - new LinksetNode(typeSchemeUri, getRelation(), "text/html", buildAnchor(context, item)) + new LinksetNode(typeSchemeUri, getRelation(), buildAnchor(context, item)) ); } } catch (Exception e) { diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/signposting/controller/LinksetRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/signposting/controller/LinksetRestControllerIT.java index b976e40bf27e..7375ee3322a6 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/signposting/controller/LinksetRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/signposting/controller/LinksetRestControllerIT.java @@ -101,19 +101,6 @@ public void findAllItemsLinksets() throws Exception { .andExpect(status().isMethodNotAllowed()); } - @Test - public void findOneItemGenericLinksets() throws Exception { - context.turnOffAuthorisationSystem(); - Item item = ItemBuilder.createItem(context, collection) - .withTitle("Item Test") - .withMetadata("dc", "identifier", "doi", doi) - .build(); - context.restoreAuthSystemState(); - - getClient().perform(get("/signposting/linksets/" + item.getID())) - .andExpect(status().isMethodNotAllowed()); - } - @Test public void findOneItemJsonLinksets() throws Exception { context.turnOffAuthorisationSystem(); @@ -152,12 +139,8 @@ public void findOneItemJsonLinksetsWithType() throws Exception { Matchers.hasSize(2))) .andExpect(jsonPath("$.linkset[0].type[0].href", Matchers.hasToString("https://schema.org/AboutPage"))) - .andExpect(jsonPath("$.linkset[0].type[0].type", - Matchers.hasToString("text/html"))) .andExpect(jsonPath("$.linkset[0].type[1].href", - Matchers.hasToString(articleUri))) - .andExpect(jsonPath("$.linkset[0].type[1].type", - Matchers.hasToString("text/html"))); + Matchers.hasToString(articleUri))); } @Test @@ -176,13 +159,8 @@ public void findOneItemJsonLinksetsWithLicence() throws Exception { Matchers.hasSize(1))) .andExpect(jsonPath("$.linkset[0].type[0].href", Matchers.hasToString("https://schema.org/AboutPage"))) - .andExpect(jsonPath("$.linkset[0].type[0].type", - Matchers.hasToString("text/html"))) .andExpect(jsonPath("$.linkset[0].license[0].href", - Matchers.hasToString(licenceUrl))) - .andExpect(jsonPath("$.linkset[0].license[0].type", - Matchers.hasToString("text/html"))); - + Matchers.hasToString(licenceUrl))); } @Test @@ -216,8 +194,7 @@ public void findOneItemJsonLinksetsWithBitstreams() throws Exception { context.restoreAuthSystemState(); String url = configurationService.getProperty("dspace.ui.url"); - getClient().perform(get("/signposting/linksets/" + item.getID() + "/json") - .header("Accept", "application/linkset+json")) + getClient().perform(get("/signposting/linksets/" + item.getID() + "/json")) .andExpect(status().isOk()) .andExpect(jsonPath("$.linkset", Matchers.hasSize(1))) @@ -329,22 +306,19 @@ public void findOneBitstreamJsonLinksets() throws Exception { } context.restoreAuthSystemState(); - getClient().perform(get("/signposting/linksets/" + bitstream.getID() + "/json") - .header("Accept", "application/linkset+json")) + getClient().perform(get("/signposting/linksets/" + bitstream.getID() + "/json")) .andExpect(status().isNotFound()); } @Test public void findOneCollectionJsonLinksets() throws Exception { - getClient().perform(get("/signposting/linksets/" + collection.getID() + "/json") - .header("Accept", "application/linkset+json")) + getClient().perform(get("/signposting/linksets/" + collection.getID() + "/json")) .andExpect(status().isNotFound()); } @Test public void findOneCommunityJsonLinksets() throws Exception { - getClient().perform(get("/signposting/linksets/" + parentCommunity.getID() + "/json") - .header("Accept", "application/linkset+json")) + getClient().perform(get("/signposting/linksets/" + parentCommunity.getID() + "/json")) .andExpect(status().isNotFound()); } @@ -374,12 +348,10 @@ public void findOneItemLsetLinksets() throws Exception { String itemRelation = "<" + url + "/bitstreams/" + bitstream1.getID() + "/download> ; rel=\"item\" ; " + "type=\"text/plain\" ; anchor=\"" + url + "/entities/publication/" + item.getID() + "\" ,"; - String typeRelation = " ; rel=\"type\" ; type=\"text/html\" ; anchor=\"" + - url + "/entities/publication/" + - item.getID() + "\" ,"; + String typeRelation = " ; rel=\"type\" ; anchor=\"" + + url + "/entities/publication/" + item.getID() + "\" ,"; - getClient().perform(get("/signposting/linksets/" + item.getID()) - .header("Accept", "application/linkset")) + getClient().perform(get("/signposting/linksets/" + item.getID())) .andExpect(content().string(Matchers.containsString(siteAsRelation))) .andExpect(content().string(Matchers.containsString(itemRelation))) .andExpect(content().string(Matchers.containsString(typeRelation))); @@ -393,8 +365,7 @@ public void findOneUnDiscoverableItemLsetLinksets() throws Exception { .build(); context.restoreAuthSystemState(); - getClient().perform(get("/signposting/linksets/" + item.getID()) - .header("Accept", "application/linkset")) + getClient().perform(get("/signposting/linksets/" + item.getID())) .andExpect(status().isUnauthorized()); } @@ -454,8 +425,7 @@ public void findTypedLinkForItemWithAuthor() throws Exception { .andExpect(jsonPath("$", Matchers.hasSize(5))) .andExpect(jsonPath("$[?(@.href == '" + MessageFormat.format(orcidPattern, orcidValue) + "' " + - "&& @.rel == 'author' " + - "&& @.type == 'text/html')]").exists()) + "&& @.rel == 'author')]").exists()) .andExpect(jsonPath("$[?(@.href == '" + MessageFormat.format(doiPattern, doi) + "' " + "&& @.rel == 'cite-as')]").exists()) .andExpect(jsonPath("$[?(@.href == '" + dcIdentifierUriMetadataValue + "' " + @@ -464,8 +434,7 @@ public void findTypedLinkForItemWithAuthor() throws Exception { "&& @.rel == 'item' " + "&& @.type == 'text/plain')]").exists()) .andExpect(jsonPath("$[?(@.href == 'https://schema.org/AboutPage' " + - "&& @.rel == 'type' " + - "&& @.type == 'text/html')]").exists()); + "&& @.rel == 'type')]").exists()); } @Test @@ -549,8 +518,7 @@ public void findTypedLinkForBitstreamWithType() throws Exception { "' && @.rel == 'linkset' " + "&& @.type == 'application/linkset+json')]").exists()) .andExpect(jsonPath("$[?(@.href == 'https://schema.org/ScholarlyArticle' " + - "&& @.rel == 'type' " + - "&& @.type == 'text/html')]").exists()); + "&& @.rel == 'type')]").exists()); DSpaceServicesFactory.getInstance().getConfigurationService().reloadConfig(); metadataAuthorityService.clearCache(); From ca7c75987e106ac99c335960e95c39bcf77882be Mon Sep 17 00:00:00 2001 From: damian Date: Tue, 13 Jun 2023 19:58:42 +0200 Subject: [PATCH 259/510] Items count are now disabled by default. Naming changes. --- .../src/main/java/org/dspace/browse/ItemCounter.java | 8 +++++--- .../src/main/java/org/dspace/content/Collection.java | 4 ++-- .../java/org/dspace/content/CollectionServiceImpl.java | 2 +- .../src/main/java/org/dspace/content/Community.java | 4 ++-- .../java/org/dspace/content/CommunityServiceImpl.java | 2 +- .../org/dspace/content/service/CollectionService.java | 2 +- .../java/org/dspace/content/service/CommunityService.java | 2 +- .../dspace/app/rest/converter/CollectionConverter.java | 2 +- .../org/dspace/app/rest/converter/CommunityConverter.java | 3 ++- dspace/config/dspace.cfg | 4 ++-- 10 files changed, 18 insertions(+), 15 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/browse/ItemCounter.java b/dspace-api/src/main/java/org/dspace/browse/ItemCounter.java index 1718e07ab2ca..20c43fc37298 100644 --- a/dspace-api/src/main/java/org/dspace/browse/ItemCounter.java +++ b/dspace-api/src/main/java/org/dspace/browse/ItemCounter.java @@ -75,7 +75,7 @@ public ItemCounter(Context context) throws ItemCountException { this.dao = ItemCountDAOFactory.getInstance(this.context); this.itemService = ContentServiceFactory.getInstance().getItemService(); this.configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); - this.showStrengths = configurationService.getBooleanProperty("webui.strengths.show", true); + this.showStrengths = configurationService.getBooleanProperty("webui.strengths.show", false); this.useCache = configurationService.getBooleanProperty("webui.strengths.cache", true); } @@ -96,7 +96,9 @@ public static ItemCounter getInstance() throws ItemCountException { /** * Get the count of the items in the given container. If the configuration * value webui.strengths.show is equal to 'true' this method will return all - * archived items. If the configuration value webui.strengths.cache + * archived items. If the configuration value webui.strengths.show is equal to + * 'false' this method will return -1. + * If the configuration value webui.strengths.cache * is equal to 'true' this will return the cached value if it exists. * If it is equal to 'false' it will count the number of items * in the container in real time. @@ -107,7 +109,7 @@ public static ItemCounter getInstance() throws ItemCountException { */ public int getCount(DSpaceObject dso) throws ItemCountException { if (!showStrengths) { - return 0; + return -1; } if (useCache) { diff --git a/dspace-api/src/main/java/org/dspace/content/Collection.java b/dspace-api/src/main/java/org/dspace/content/Collection.java index 6caae7f47b24..53b63dbef1fa 100644 --- a/dspace-api/src/main/java/org/dspace/content/Collection.java +++ b/dspace-api/src/main/java/org/dspace/content/Collection.java @@ -342,9 +342,9 @@ private CollectionService getCollectionService() { * * @return int */ - public int countArchivedItem() { + public int countArchivedItems() { try { - return collectionService.countArchivedItem(this); + return collectionService.countArchivedItems(this); } catch (ItemCountException e) { throw new RuntimeException(e); } 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 a5fcf0c1aa37..84ca1692ccf2 100644 --- a/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java @@ -1077,7 +1077,7 @@ public List findAllCollectionsByEntityType(Context context, String e * @throws ItemCountException */ @Override - public int countArchivedItem(Collection collection) throws ItemCountException { + public int countArchivedItems(Collection collection) throws ItemCountException { return ItemCounter.getInstance().getCount(collection); } } diff --git a/dspace-api/src/main/java/org/dspace/content/Community.java b/dspace-api/src/main/java/org/dspace/content/Community.java index 39705369e3d6..dd6d978936df 100644 --- a/dspace-api/src/main/java/org/dspace/content/Community.java +++ b/dspace-api/src/main/java/org/dspace/content/Community.java @@ -270,9 +270,9 @@ private CommunityService getCommunityService() { * * @return int */ - public int countArchivedItem() { + public int countArchivedItems() { try { - return communityService.getArchivedItems(this); + return communityService.countArchivedItems(this); } catch (ItemCountException e) { throw new RuntimeException(e); } diff --git a/dspace-api/src/main/java/org/dspace/content/CommunityServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/CommunityServiceImpl.java index f693c62e9247..15ac1c58a690 100644 --- a/dspace-api/src/main/java/org/dspace/content/CommunityServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/CommunityServiceImpl.java @@ -719,7 +719,7 @@ public int countTotal(Context context) throws SQLException { * @throws ItemCountException */ @Override - public int getArchivedItems(Community community) throws ItemCountException { + public int countArchivedItems(Community community) throws ItemCountException { return ItemCounter.getInstance().getCount(community); } } diff --git a/dspace-api/src/main/java/org/dspace/content/service/CollectionService.java b/dspace-api/src/main/java/org/dspace/content/service/CollectionService.java index d7f47882c085..90db5c731402 100644 --- a/dspace-api/src/main/java/org/dspace/content/service/CollectionService.java +++ b/dspace-api/src/main/java/org/dspace/content/service/CollectionService.java @@ -478,5 +478,5 @@ public List findAllCollectionsByEntityType(Context context, String e * @return total collection archived items * @throws ItemCountException */ - int countArchivedItem(Collection collection) throws ItemCountException; + int countArchivedItems(Collection collection) throws ItemCountException; } diff --git a/dspace-api/src/main/java/org/dspace/content/service/CommunityService.java b/dspace-api/src/main/java/org/dspace/content/service/CommunityService.java index 9326da2203e9..c089bcec8df1 100644 --- a/dspace-api/src/main/java/org/dspace/content/service/CommunityService.java +++ b/dspace-api/src/main/java/org/dspace/content/service/CommunityService.java @@ -301,5 +301,5 @@ public void removeSubcommunity(Context context, Community parentCommunity, Commu * @return total community archived items * @throws ItemCountException */ - int getArchivedItems(Community community) throws ItemCountException; + int countArchivedItems(Community community) throws ItemCountException; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/CollectionConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/CollectionConverter.java index a5d06b5b3b45..e9b6aa03b85a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/CollectionConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/CollectionConverter.java @@ -26,7 +26,7 @@ public class CollectionConverter extends DSpaceObjectConverter Date: Wed, 14 Jun 2023 10:17:20 -0400 Subject: [PATCH 260/510] Rework message to make it more useful, more businesslike, not expose author's email by default. --- .../app/requestitem/RequestItemEmailNotifier.java | 2 +- .../org/dspace/app/rest/utils/ApplicationConfig.java | 2 +- dspace/config/emails/request_item.granted | 12 ++++++------ 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemEmailNotifier.java b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemEmailNotifier.java index c44a65d95462..1f0c2683da5d 100644 --- a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemEmailNotifier.java +++ b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemEmailNotifier.java @@ -178,7 +178,7 @@ public void sendResponse(Context context, RequestItem ri, String subject, Email email = Email.getEmail(I18nUtil.getEmailFilename(context.getCurrentLocale(), "request_item.granted")); email.addArgument(ri.getReqName()); // {0} requestor's name - email.addArgument(ri.getItem().getID().toString()); // {1} URL of the requested Item + email.addArgument(handleService.getCanonicalForm(ri.getItem().getHandle())); // {1} URL of the requested Item email.addArgument(ri.getItem().getName()); // {2} title of the requested Item email.addArgument(grantorName); // {3} name of the grantor email.addArgument(grantorAddress); // {4} email of the grantor 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 492d650cab0f..23e217a5dd42 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 @@ -27,7 +27,7 @@ // bean which is in the scope of both will be duplicated. dspace-services makes // its context the parent of this one. If a bean is explicitly configured in // the parent, it won't be so configured in this context and you may have -// trouble. IOW be careful what you add here. +// trouble. Be careful what you add here. @ComponentScan( { "org.dspace.app.rest.converter", "org.dspace.app.rest.repository", diff --git a/dspace/config/emails/request_item.granted b/dspace/config/emails/request_item.granted index 33d4e9a43376..290ae89b300c 100644 --- a/dspace/config/emails/request_item.granted +++ b/dspace/config/emails/request_item.granted @@ -11,16 +11,16 @@ #set($subject = 'Request for Copy of Restricted Document is Granted') Dear ${params[0]}: -In response to your request I have the pleasure to send you in attachment a copy of the file(s) concerning the document: +Your request for a copy of the file(s) from the below document has been approved by ${params[3]}. You may find the requested file(s) attached. - ${params[2]} - ${params[1]} - -of which I am an author. + ${params[2]} + ${params[1]} #if( $params[5] ) +An additional message from ${params[3]} follows: + ${params[5]} #end Best regards, -${params[3]} <${params[4]}> +The ${config.get('dspace.name')} Team From 411ac4a9a27262473e4e8965b771a72c246650fb Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Wed, 14 Jun 2023 10:21:01 -0400 Subject: [PATCH 261/510] Clarify template comments. --- dspace/config/emails/request_item.granted | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dspace/config/emails/request_item.granted b/dspace/config/emails/request_item.granted index 290ae89b300c..37ee5c29bd0c 100644 --- a/dspace/config/emails/request_item.granted +++ b/dspace/config/emails/request_item.granted @@ -3,10 +3,10 @@ ## ## Parameters: ## {0} name of the requestor -## {1} unique ID of the requested Item +## {1} Handle URL of the requested Item ## {2} title of the requested Item ## {3} name of the grantor -## {4} email address of the grantor +## {4} email address of the grantor (unused) ## {5} custom message sent by the grantor. #set($subject = 'Request for Copy of Restricted Document is Granted') Dear ${params[0]}: From 9caba7044b2c13b76549457c797e6a30194412a5 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Wed, 14 Jun 2023 10:53:49 -0400 Subject: [PATCH 262/510] Remove test for removed content. --- .../dspace/app/requestitem/RequestItemEmailNotifierTest.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/dspace-api/src/test/java/org/dspace/app/requestitem/RequestItemEmailNotifierTest.java b/dspace-api/src/test/java/org/dspace/app/requestitem/RequestItemEmailNotifierTest.java index 36525a912a31..5d0069e78bbb 100644 --- a/dspace-api/src/test/java/org/dspace/app/requestitem/RequestItemEmailNotifierTest.java +++ b/dspace-api/src/test/java/org/dspace/app/requestitem/RequestItemEmailNotifierTest.java @@ -164,9 +164,6 @@ public void testSendResponse() throws Exception { assertThat("Body should be a single text bodypart", content, instanceOf(String.class)); - assertThat("Should contain the helpdesk address", - (String)content, containsString(HELPDESK_ADDRESS)); - assertThat("Should contain the helpdesk name", (String)content, containsString(HELPDESK_NAME)); From bbb75df3a14a6a03a6a972ea74f4669e78debc7f Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 19 May 2023 13:04:10 -0500 Subject: [PATCH 263/510] Disable DTD parsing in all external source plugins to avoid XXEs --- .../dspace/ctask/general/MetadataWebService.java | 3 ++- .../external/provider/orcid/xml/Converter.java | 14 +++++++++++--- .../CiniiImportMetadataSourceServiceImpl.java | 6 ++++++ .../EpoImportMetadataSourceServiceImpl.java | 6 ++++++ .../PubmedImportMetadataSourceServiceImpl.java | 5 +++++ .../PubmedEuropeMetadataSourceServiceImpl.java | 4 ++++ .../ScopusImportMetadataSourceServiceImpl.java | 4 ++++ .../WOSImportMetadataSourceServiceImpl.java | 4 ++++ 8 files changed, 42 insertions(+), 4 deletions(-) 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 f7ab18c01e54..5891fa017cb0 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 @@ -207,9 +207,10 @@ public void init(Curator curator, String taskId) throws IOException { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setNamespaceAware(true); try { - // disallow DTD parsing to ensure no XXE attacks can occur. + // 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); 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 8f48cda712bc..756b8654f285 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 @@ -12,6 +12,9 @@ import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; import javax.xml.bind.Unmarshaller; +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; import org.xml.sax.SAXException; @@ -28,11 +31,16 @@ 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); + XMLStreamReader xmlStreamReader = xmlInputFactory.createXMLStreamReader(input); + JAXBContext context = JAXBContext.newInstance(type); Unmarshaller unmarshaller = context.createUnmarshaller(); - return unmarshaller.unmarshal(input); - } catch (JAXBException e) { - throw new RuntimeException("Unable to unmarshall orcid message" + e); + return unmarshaller.unmarshal(xmlStreamReader); + } catch (JAXBException | XMLStreamException e) { + throw new RuntimeException("Unable to unmarshall orcid message: " + e); } } } 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 5eff46c790e4..587ad5b25838 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 @@ -303,6 +303,8 @@ protected List search(String id, String appId) private List splitToRecords(String recordsSrc) { try { SAXBuilder saxBuilder = new SAXBuilder(); + // disallow DTD parsing to ensure no XXE attacks can occur + saxBuilder.setFeature("http://apache.org/xml/features/disallow-doctype-decl",true); Document document = saxBuilder.build(new StringReader(recordsSrc)); Element root = document.getRootElement(); return root.getChildren(); @@ -356,6 +358,8 @@ private List getCiniiIds(String appId, Integer maxResult, String author, 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); Document document = saxBuilder.build(new StringReader(response)); Element root = document.getRootElement(); List namespaces = Arrays.asList( @@ -418,6 +422,8 @@ private Integer countCiniiElement(String appId, Integer maxResult, String author 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); Document document = saxBuilder.build(new StringReader(response)); Element root = document.getRootElement(); List namespaces = Arrays 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 7240e356e371..fbae302bca6a 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 @@ -398,6 +398,8 @@ private Integer countDocument(String bearer, String query) { String response = liveImportClient.executeHttpGetRequest(1000, uriBuilder.toString(), params); SAXBuilder saxBuilder = new SAXBuilder(); + // disallow DTD parsing to ensure no XXE attacks can occur + saxBuilder.setFeature("http://apache.org/xml/features/disallow-doctype-decl",true); Document document = saxBuilder.build(new StringReader(response)); Element root = document.getRootElement(); @@ -435,6 +437,8 @@ private List searchDocumentIds(String bearer, String query, int s String response = liveImportClient.executeHttpGetRequest(1000, uriBuilder.toString(), params); SAXBuilder saxBuilder = new SAXBuilder(); + // disallow DTD parsing to ensure no XXE attacks can occur + saxBuilder.setFeature("http://apache.org/xml/features/disallow-doctype-decl",true); Document document = saxBuilder.build(new StringReader(response)); Element root = document.getRootElement(); @@ -486,6 +490,8 @@ private List searchDocument(String bearer, String id, String docTy private List splitToRecords(String recordsSrc) { try { SAXBuilder saxBuilder = new SAXBuilder(); + // disallow DTD parsing to ensure no XXE attacks can occur + saxBuilder.setFeature("http://apache.org/xml/features/disallow-doctype-decl",true); 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 933d6b1446d8..a6cfa625bbcf 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 @@ -351,6 +351,11 @@ public Collection call() throws Exception { private List splitToRecords(String recordsSrc) { try { SAXBuilder saxBuilder = new SAXBuilder(); + // Disallow external entities & entity expansion to protect against XXE attacks + // (NOTE: We receive errors if we disable all DTDs for PubMed, so this is the best we can do) + saxBuilder.setFeature("http://xml.org/sax/features/external-general-entities", false); + saxBuilder.setFeature("http://xml.org/sax/features/external-parameter-entities", false); + saxBuilder.setExpandEntities(false); 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 1ec0da74206e..92d7d9fbd3fe 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 @@ -294,6 +294,8 @@ public Integer count(String query) throws URISyntaxException, ClientProtocolExce 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); Document document = saxBuilder.build(new StringReader(response)); Element root = document.getRootElement(); Element element = root.getChild("hitCount"); @@ -365,6 +367,8 @@ public List search(String query, Integer size, Integer start) thro 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); Document document = saxBuilder.build(new StringReader(response)); XPathFactory xpfac = XPathFactory.instance(); XPathExpression xPath = xpfac.compile("//responseWrapper/resultList/result", 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 d0c2fb078a2c..944d467e3156 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 @@ -202,6 +202,8 @@ public Integer call() throws Exception { 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); Document document = saxBuilder.build(new StringReader(response)); Element root = document.getRootElement(); @@ -377,6 +379,8 @@ private Map getRequestParameters(String query, String viewMode, private List splitToRecords(String recordsSrc) { try { SAXBuilder saxBuilder = new SAXBuilder(); + // disallow DTD parsing to ensure no XXE attacks can occur + saxBuilder.setFeature("http://apache.org/xml/features/disallow-doctype-decl",true); Document document = saxBuilder.build(new StringReader(recordsSrc)); Element root = document.getRootElement(); List records = root.getChildren("entry",Namespace.getNamespace("http://www.w3.org/2005/Atom")); 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 2ccdc12b8db2..f550b659952b 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 @@ -147,6 +147,8 @@ public Integer call() throws Exception { 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); Document document = saxBuilder.build(new StringReader(response)); Element root = document.getRootElement(); XPathExpression xpath = XPathFactory.instance().compile("//*[@name=\"RecordsFound\"]", @@ -285,6 +287,8 @@ private boolean isIsi(String query) { private List splitToRecords(String recordsSrc) { try { SAXBuilder saxBuilder = new SAXBuilder(); + // disallow DTD parsing to ensure no XXE attacks can occur + saxBuilder.setFeature("http://apache.org/xml/features/disallow-doctype-decl",true); Document document = saxBuilder.build(new StringReader(recordsSrc)); Element root = document.getRootElement(); String cData = XPathFactory.instance().compile("//*[@name=\"Records\"]", From 7c7f3e2853bf6f27c09686684b83c0a798a45211 Mon Sep 17 00:00:00 2001 From: Nona Luypaert Date: Wed, 14 Jun 2023 23:47:36 +0200 Subject: [PATCH 264/510] 102124: Move content-disposition test config from local.cfg to IT itself --- dspace-api/src/test/data/dspaceFolder/config/local.cfg | 6 ------ .../java/org/dspace/app/rest/BitstreamRestControllerIT.java | 6 ++++++ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/dspace-api/src/test/data/dspaceFolder/config/local.cfg b/dspace-api/src/test/data/dspaceFolder/config/local.cfg index 144116f212eb..3c19a68e9fd1 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/local.cfg +++ b/dspace-api/src/test/data/dspaceFolder/config/local.cfg @@ -144,9 +144,3 @@ authentication-ip.Student = 6.6.6.6 useProxies = true proxies.trusted.ipranges = 7.7.7.7 proxies.trusted.include_ui_ip = true - -########################### -# BITSTREAM CONFIGURATION # -########################### -# Files with these mimetypes, extensions will never be opened inline, and should be downloaded -webui.content_disposition_format = text/richtext, text/xml, txt 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 9b4aeed1e208..e2166ad42591 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 @@ -972,6 +972,12 @@ public void updateBitstreamFormatAdmin() throws Exception { @Test public void checkContentDispositionOfFormats() throws Exception { + configurationService.setProperty("webui.content_disposition_format", new String[] { + "text/richtext", + "text/xml", + "txt" + }); + context.turnOffAuthorisationSystem(); Community community = CommunityBuilder.createCommunity(context).build(); Collection collection = CollectionBuilder.createCollection(context, community).build(); From 1cb60a90183e4d874a05b24ad3f0320131553c53 Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Wed, 14 Jun 2023 23:48:27 +0200 Subject: [PATCH 265/510] Hide publicdomain by default since it's now the same as CC0 More info: https://api.creativecommons.org/docs/readme_15.html#changes-since-1-0 --- dspace/config/dspace.cfg | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 4e6a6799274b..b8a149599b15 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -968,7 +968,7 @@ metadata.hide.person.email = true #### Creative Commons settings ###### # The url to the web service API -cc.api.rooturl = http://api.creativecommons.org/rest/1.5 +cc.api.rooturl = https://api.creativecommons.org/rest/1.5 # Metadata field to hold CC license URI of selected license cc.license.uri = dc.rights.uri @@ -985,12 +985,12 @@ cc.submit.addbitstream = true # A list of license classes that should be excluded from selection process # class names - comma-separated list - must exactly match what service returns. # At time of implementation, these are: -# publicdomain - "Public Domain" +# publicdomain - "Public Domain" (this is now the same as CC0) # standard - "Creative Commons" # recombo - "Sampling" # zero - "CC0" # mark - "Public Domain Mark" -cc.license.classfilter = recombo, mark +cc.license.classfilter = publicdomain, recombo, mark # Jurisdiction of the creative commons license -- is it ported or not? # Use the key from the url seen in the response from the api call, From 8adb16f9453edb510839d97f9ad86caf15362e5c Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Thu, 15 Jun 2023 17:01:00 -0400 Subject: [PATCH 266/510] Invent a rejection letter --- .../requestitem/RequestItemEmailNotifier.java | 2 +- .../RequestItemEmailNotifierTest.java | 103 ++++++++++++++++-- dspace/config/emails/request_item.rejected | 26 +++++ 3 files changed, 123 insertions(+), 8 deletions(-) create mode 100644 dspace/config/emails/request_item.rejected diff --git a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemEmailNotifier.java b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemEmailNotifier.java index 1f0c2683da5d..6499c45a7830 100644 --- a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemEmailNotifier.java +++ b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemEmailNotifier.java @@ -176,7 +176,7 @@ public void sendResponse(Context context, RequestItem ri, String subject, // Build an email back to the requester. Email email = Email.getEmail(I18nUtil.getEmailFilename(context.getCurrentLocale(), - "request_item.granted")); + ri.isAccept_request() ? "request_item.granted" : "request_item.rejected")); email.addArgument(ri.getReqName()); // {0} requestor's name email.addArgument(handleService.getCanonicalForm(ri.getItem().getHandle())); // {1} URL of the requested Item email.addArgument(ri.getItem().getName()); // {2} title of the requested Item diff --git a/dspace-api/src/test/java/org/dspace/app/requestitem/RequestItemEmailNotifierTest.java b/dspace-api/src/test/java/org/dspace/app/requestitem/RequestItemEmailNotifierTest.java index 5d0069e78bbb..713e007c58a2 100644 --- a/dspace-api/src/test/java/org/dspace/app/requestitem/RequestItemEmailNotifierTest.java +++ b/dspace-api/src/test/java/org/dspace/app/requestitem/RequestItemEmailNotifierTest.java @@ -48,6 +48,13 @@ public class RequestItemEmailNotifierTest public static final String TRANSPORT_CLASS_KEY = "mail.smtp.class"; + private static final String REQUESTOR_ADDRESS = "mhwood@wood.net"; + private static final String REQUESTOR_NAME = "Mark Wood"; + private static final String HELPDESK_ADDRESS = "help@example.com"; + private static final String HELPDESK_NAME = "Help Desk"; + private static final String TEST_MESSAGE = "Message"; + private static final String DUMMY_PROTO = "dummy"; + private static ConfigurationService configurationService; private static BitstreamService bitstreamService; private static HandleService handleService; @@ -86,13 +93,6 @@ public void testSendRequest() throws Exception { */ @Test public void testSendResponse() throws Exception { - final String REQUESTOR_ADDRESS = "mhwood@wood.net"; - final String REQUESTOR_NAME = "Mark Wood"; - final String HELPDESK_ADDRESS = "help@example.com"; - final String HELPDESK_NAME = "Help Desk"; - final String TEST_MESSAGE = "Message"; - final String DUMMY_PROTO = "dummy"; - // Create some content to send. context.turnOffAuthorisationSystem(); Community com = CommunityBuilder.createCommunity(context) @@ -171,6 +171,95 @@ public void testSendResponse() throws Exception { (String)content, containsString(TEST_MESSAGE)); } + /** + * Test of sendResponse method -- rejection case. + * @throws java.lang.Exception passed through. + */ + @Test + public void testSendRejection() + throws Exception { + // Create some content to send. + context.turnOffAuthorisationSystem(); + Community com = CommunityBuilder.createCommunity(context) + .withName("Top Community") + .build(); + Collection col = CollectionBuilder.createCollection(context, com) + .build(); + Item item = ItemBuilder.createItem(context, col) + .withTitle("Test Item") + .build(); + context.restoreAuthSystemState(); + + // Create a request to which we can respond. + RequestItem ri = new RequestItem(); + ri.setAccept_request(false); + ri.setItem(item); + ri.setAllfiles(true); + ri.setReqEmail(REQUESTOR_ADDRESS); + ri.setReqName(REQUESTOR_NAME); + + // Install a fake transport for RFC2822 email addresses. + Session session = DSpaceServicesFactory.getInstance().getEmailService().getSession(); + Provider transportProvider = new Provider(Provider.Type.TRANSPORT, + DUMMY_PROTO, JavaMailTestTransport.class.getCanonicalName(), + "DSpace", "1.0"); + session.addProvider(transportProvider); + session.setProvider(transportProvider); + session.setProtocolForAddress("rfc822", DUMMY_PROTO); + + // Configure the help desk strategy. + configurationService.setProperty("mail.helpdesk", HELPDESK_ADDRESS); + configurationService.setProperty("mail.helpdesk.name", HELPDESK_NAME); + configurationService.setProperty("request.item.helpdesk.override", "true"); + + // Ensure that mail is "sent". + configurationService.setProperty("mail.server.disabled", "false"); + + // Instantiate and initialize the unit, using the "help desk" strategy. + RequestItemEmailNotifier requestItemEmailNotifier + = new RequestItemEmailNotifier( + DSpaceServicesFactory.getInstance() + .getServiceManager() + .getServiceByName(RequestItemHelpdeskStrategy.class.getName(), + RequestItemAuthorExtractor.class)); + requestItemEmailNotifier.bitstreamService = bitstreamService; + requestItemEmailNotifier.configurationService = configurationService; + requestItemEmailNotifier.handleService = handleService; + requestItemEmailNotifier.requestItemService = requestItemService; + + // Test the unit. Template supplies the Subject: value + requestItemEmailNotifier.sendResponse(context, ri, null, TEST_MESSAGE); + + // Evaluate the test results. + + // Check the To: address. + Address[] myAddresses = JavaMailTestTransport.getAddresses(); + assertEquals("Should have one To: address.", + myAddresses.length, 1); + assertThat("To: should be an Internet address", + myAddresses[0], instanceOf(InternetAddress.class)); + String address = ((InternetAddress)myAddresses[0]).getAddress(); + assertEquals("To: address should match requestor.", + ri.getReqEmail(), address); + + // Check the message body. + Message myMessage = JavaMailTestTransport.getMessage(); + + Object content = myMessage.getContent(); + assertThat("Body should be a single text bodypart", + content, instanceOf(String.class)); + + assertThat("Should contain the helpdesk name", + (String)content, containsString(HELPDESK_NAME)); + + assertThat("Should contain the test custom message", + (String)content, containsString(TEST_MESSAGE)); + + // FIXME Note that this depends on the content of the rejection template! + assertThat("Should contain the word 'denied'.", + (String)content, containsString("denied")); + } + /** * Test of requestOpenAccess method, of class RequestItemEmailNotifier. * @throws java.lang.Exception passed through. diff --git a/dspace/config/emails/request_item.rejected b/dspace/config/emails/request_item.rejected new file mode 100644 index 000000000000..c5a13860b648 --- /dev/null +++ b/dspace/config/emails/request_item.rejected @@ -0,0 +1,26 @@ +## Sent to the person requesting a copy of a restricted document when the +## request is denied. +## +## Parameters: +## {0} name of the requestor +## {1} Handle URL of the requested Item +## {2} title of the requested Item +## {3} name of the grantor +## {4} email address of the grantor (unused) +## {5} custom message sent by the grantor. +#set($subject = 'Request for Copy of Restricted Document is Denied') +Dear ${params[0]}: + +Your request for a copy of the file(s) from the below document has been denied by ${params[3]}. + + ${params[2]} + ${params[1]} +#if( $params[5] ) + +An additional message from ${params[3]} follows: + +${params[5]} +#end + +Best regards, +The ${config.get('dspace.name')} Team From 55c07e90cdc1cb5eff86b83333a8fb32cdcf5ff4 Mon Sep 17 00:00:00 2001 From: Nikita Krivonosov Date: Fri, 16 Jun 2023 01:53:48 +0200 Subject: [PATCH 267/510] [DURACOM-156] Singposting: feedback after initial deployment --- .../controller/LinksetRestController.java | 8 +- .../LinksetHttpMessageConverter.java | 82 ------------------- .../LinksetRestMessageConverter.java | 49 +++++++++++ .../processor/item/ItemLinksetProcessor.java | 58 +++++++++++++ .../controller/LinksetRestControllerIT.java | 76 +++++++++++++++-- dspace/config/spring/rest/signposting.xml | 5 +- 6 files changed, 183 insertions(+), 95 deletions(-) delete mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/converter/LinksetHttpMessageConverter.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/converter/LinksetRestMessageConverter.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemLinksetProcessor.java diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/controller/LinksetRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/controller/LinksetRestController.java index 8089c518fff0..7932d94f7918 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/controller/LinksetRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/controller/LinksetRestController.java @@ -18,6 +18,7 @@ import javax.servlet.http.HttpServletRequest; import org.dspace.app.rest.converter.ConverterService; +import org.dspace.app.rest.signposting.converter.LinksetRestMessageConverter; import org.dspace.app.rest.signposting.model.LinksetNode; import org.dspace.app.rest.signposting.model.LinksetRest; import org.dspace.app.rest.signposting.model.TypedLinkRest; @@ -100,9 +101,8 @@ public LinksetRest getJson(HttpServletRequest request, @PathVariable UUID uuid) } @PreAuthorize("hasPermission(#uuid, 'ITEM', 'READ')") - @RequestMapping(value = "/linksets" + REGEX_REQUESTMAPPING_IDENTIFIER_AS_UUID, - method = RequestMethod.GET, produces = "application/linkset") - public LinksetRest getLset(HttpServletRequest request, @PathVariable UUID uuid) { + @RequestMapping(value = "/linksets" + REGEX_REQUESTMAPPING_IDENTIFIER_AS_UUID, method = RequestMethod.GET) + public String getLset(HttpServletRequest request, @PathVariable UUID uuid) { try { Context context = ContextUtil.obtainContext(request); @@ -127,7 +127,7 @@ public LinksetRest getLset(HttpServletRequest request, @PathVariable UUID uuid) linksetRest.getLinksetNodes().add(linksetNode); } } - return linksetRest; + return LinksetRestMessageConverter.convert(linksetRest); } catch (SQLException e) { throw new RuntimeException(e); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/converter/LinksetHttpMessageConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/converter/LinksetHttpMessageConverter.java deleted file mode 100644 index e5c187c9554b..000000000000 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/converter/LinksetHttpMessageConverter.java +++ /dev/null @@ -1,82 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.app.rest.signposting.converter; - -import static java.lang.String.format; -import static java.util.Objects.nonNull; -import static org.apache.commons.lang.StringUtils.isNotBlank; - -import java.io.IOException; -import java.lang.reflect.Type; - -import org.apache.commons.lang.NotImplementedException; -import org.dspace.app.rest.signposting.model.LinksetRest; -import org.springframework.http.HttpInputMessage; -import org.springframework.http.HttpOutputMessage; -import org.springframework.http.MediaType; -import org.springframework.http.converter.AbstractGenericHttpMessageConverter; -import org.springframework.http.converter.HttpMessageNotReadableException; -import org.springframework.http.converter.HttpMessageNotWritableException; - -/** - * Converter for converting LinksetRest message into application/linkset format. - */ -public class LinksetHttpMessageConverter extends AbstractGenericHttpMessageConverter { - - public LinksetHttpMessageConverter() { - super(MediaType.valueOf("application/linkset")); - } - - @Override - protected void writeInternal(LinksetRest linksetRest, Type type, HttpOutputMessage outputMessage) - throws IOException, HttpMessageNotWritableException { - StringBuilder responseBody = new StringBuilder(); - linksetRest.getLinksetNodes().forEach(linksetNodes -> { - if (isNotBlank(linksetNodes.getLink())) { - responseBody.append(format("<%s> ", linksetNodes.getLink())); - } - if (nonNull(linksetNodes.getRelation())) { - responseBody.append(format("; rel=\"%s\" ", linksetNodes.getRelation().getName())); - } - if (isNotBlank(linksetNodes.getType())) { - responseBody.append(format("; type=\"%s\" ", linksetNodes.getType())); - } - if (isNotBlank(linksetNodes.getAnchor())) { - responseBody.append(format("; anchor=\"%s\" ", linksetNodes.getAnchor())); - } - responseBody.append(", "); - }); - outputMessage.getBody().write(responseBody.toString().trim().getBytes()); - outputMessage.getBody().flush(); - } - - @Override - protected LinksetRest readInternal(Class clazz, HttpInputMessage inputMessage) - throws HttpMessageNotReadableException { - throw new NotImplementedException(); - } - - @Override - protected boolean canRead(MediaType mediaType) { - return false; - } - - @Override - public boolean canWrite(Class clazz, MediaType mediaType) { - boolean isAppropriateClass = LinksetRest.class.isAssignableFrom(clazz); - boolean isAppropriateMediaType = getSupportedMediaTypes().stream() - .anyMatch(supportedType -> supportedType.isCompatibleWith(mediaType)); - return isAppropriateClass && isAppropriateMediaType; - } - - @Override - public LinksetRest read(Type type, Class contextClass, HttpInputMessage inputMessage) - throws IOException, HttpMessageNotReadableException { - throw new NotImplementedException(); - } -} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/converter/LinksetRestMessageConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/converter/LinksetRestMessageConverter.java new file mode 100644 index 000000000000..03c27897b9cd --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/converter/LinksetRestMessageConverter.java @@ -0,0 +1,49 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.signposting.converter; + +import static java.lang.String.format; +import static java.util.Objects.nonNull; +import static org.apache.commons.lang.StringUtils.isNotBlank; + +import org.dspace.app.rest.signposting.model.LinksetRest; + +/** + * Converter for converting LinksetRest message into application/linkset format. + */ +public class LinksetRestMessageConverter { + + private LinksetRestMessageConverter() { + } + + /** + * Converts LinksetRest object into string of application/linkset format. + * + * @param linksetRest linkset rest object + * @return string of application/linkset format. + */ + public static String convert(LinksetRest linksetRest) { + StringBuilder responseBody = new StringBuilder(); + linksetRest.getLinksetNodes().forEach(linksetNodes -> { + if (isNotBlank(linksetNodes.getLink())) { + responseBody.append(format("<%s> ", linksetNodes.getLink())); + } + if (nonNull(linksetNodes.getRelation())) { + responseBody.append(format("; rel=\"%s\" ", linksetNodes.getRelation().getName())); + } + if (isNotBlank(linksetNodes.getType())) { + responseBody.append(format("; type=\"%s\" ", linksetNodes.getType())); + } + if (isNotBlank(linksetNodes.getAnchor())) { + responseBody.append(format("; anchor=\"%s\" ", linksetNodes.getAnchor())); + } + responseBody.append(", "); + }); + return responseBody.toString(); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemLinksetProcessor.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemLinksetProcessor.java new file mode 100644 index 000000000000..9008a28e29a6 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemLinksetProcessor.java @@ -0,0 +1,58 @@ +/** + * 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.signposting.processor.item; + +import java.util.List; +import javax.servlet.http.HttpServletRequest; + +import org.apache.log4j.Logger; +import org.dspace.app.rest.signposting.model.LinksetNode; +import org.dspace.app.rest.signposting.model.LinksetRelationType; +import org.dspace.content.Item; +import org.dspace.core.Context; +import org.dspace.services.ConfigurationService; +import org.dspace.util.FrontendUrlService; + +/** + * An extension of {@link ItemSignpostingProcessor} for the linkset relation. + */ +public class ItemLinksetProcessor extends ItemSignpostingProcessor { + + private static final Logger log = Logger.getLogger(ItemLinksetProcessor.class); + + private final ConfigurationService configurationService; + + public ItemLinksetProcessor(FrontendUrlService frontendUrlService, + ConfigurationService configurationService) { + super(frontendUrlService); + this.configurationService = configurationService; + setRelation(LinksetRelationType.LINKSET); + } + + @Override + public void addLinkSetNodes(Context context, HttpServletRequest request, + Item item, List linksetNodes) { + try { + if (item != null) { + String signpostingPath = configurationService.getProperty("signposting.path"); + String baseUrl = configurationService.getProperty("dspace.ui.url"); + + String linksetUrl = String.format("%s/%s/linksets/%s", baseUrl, signpostingPath, item.getID()); + String linksetJsonUrl = linksetUrl + "/json"; + String anchor = buildAnchor(context, item); + List links = List.of( + new LinksetNode(linksetUrl, getRelation(), "application/linkset", anchor), + new LinksetNode(linksetJsonUrl, getRelation(), "application/linkset+json", anchor) + ); + linksetNodes.addAll(links); + } + } catch (Exception e) { + log.error(e.getMessage(), e); + } + } +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/signposting/controller/LinksetRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/signposting/controller/LinksetRestControllerIT.java index 7375ee3322a6..7b3b6543ef3e 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/signposting/controller/LinksetRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/signposting/controller/LinksetRestControllerIT.java @@ -103,6 +103,8 @@ public void findAllItemsLinksets() throws Exception { @Test public void findOneItemJsonLinksets() throws Exception { + String url = configurationService.getProperty("dspace.ui.url"); + String signpostingUrl = configurationService.getProperty("signposting.path"); context.turnOffAuthorisationSystem(); Item item = ItemBuilder.createItem(context, collection) .withTitle("Item Test") @@ -115,11 +117,23 @@ public void findOneItemJsonLinksets() throws Exception { .andExpect(jsonPath("$.linkset", Matchers.hasSize(1))) .andExpect(jsonPath("$.linkset[0].cite-as[0].href", - Matchers.hasToString(MessageFormat.format(doiPattern, doi)))); + Matchers.hasToString(MessageFormat.format(doiPattern, doi)))) + .andExpect(jsonPath("$.linkset[0].linkset[0].href", + Matchers.hasToString(url + "/" + signpostingUrl + "/linksets/" + item.getID().toString()))) + .andExpect(jsonPath("$.linkset[0].linkset[0].type", + Matchers.hasToString("application/linkset"))) + .andExpect(jsonPath("$.linkset[0].linkset[1].href", + Matchers.hasToString(url + "/" + signpostingUrl + "/linksets/" + item.getID().toString() + + "/json"))) + .andExpect(jsonPath("$.linkset[0].linkset[1].type", + Matchers.hasToString("application/linkset+json"))) + ; } @Test public void findOneItemJsonLinksetsWithType() throws Exception { + String url = configurationService.getProperty("dspace.ui.url"); + String signpostingUrl = configurationService.getProperty("signposting.path"); String articleUri = mapConverterDSpaceToSchemaOrgUri.getValue("Article"); context.turnOffAuthorisationSystem(); Item item = ItemBuilder.createItem(context, collection) @@ -140,12 +154,23 @@ public void findOneItemJsonLinksetsWithType() throws Exception { .andExpect(jsonPath("$.linkset[0].type[0].href", Matchers.hasToString("https://schema.org/AboutPage"))) .andExpect(jsonPath("$.linkset[0].type[1].href", - Matchers.hasToString(articleUri))); + Matchers.hasToString(articleUri))) + .andExpect(jsonPath("$.linkset[0].linkset[0].href", + Matchers.hasToString(url + "/" + signpostingUrl + "/linksets/" + item.getID().toString()))) + .andExpect(jsonPath("$.linkset[0].linkset[0].type", + Matchers.hasToString("application/linkset"))) + .andExpect(jsonPath("$.linkset[0].linkset[1].href", + Matchers.hasToString(url + "/" + signpostingUrl + "/linksets/" + item.getID().toString() + + "/json"))) + .andExpect(jsonPath("$.linkset[0].linkset[1].type", + Matchers.hasToString("application/linkset+json"))); } @Test public void findOneItemJsonLinksetsWithLicence() throws Exception { String licenceUrl = "https://exmple.com/licence"; + String url = configurationService.getProperty("dspace.ui.url"); + String signpostingUrl = configurationService.getProperty("signposting.path"); context.turnOffAuthorisationSystem(); Item item = ItemBuilder.createItem(context, collection) .withTitle("Item Test") @@ -160,7 +185,16 @@ public void findOneItemJsonLinksetsWithLicence() throws Exception { .andExpect(jsonPath("$.linkset[0].type[0].href", Matchers.hasToString("https://schema.org/AboutPage"))) .andExpect(jsonPath("$.linkset[0].license[0].href", - Matchers.hasToString(licenceUrl))); + Matchers.hasToString(licenceUrl))) + .andExpect(jsonPath("$.linkset[0].linkset[0].href", + Matchers.hasToString(url + "/" + signpostingUrl + "/linksets/" + item.getID().toString()))) + .andExpect(jsonPath("$.linkset[0].linkset[0].type", + Matchers.hasToString("application/linkset"))) + .andExpect(jsonPath("$.linkset[0].linkset[1].href", + Matchers.hasToString(url + "/" + signpostingUrl + "/linksets/" + item.getID().toString() + + "/json"))) + .andExpect(jsonPath("$.linkset[0].linkset[1].type", + Matchers.hasToString("application/linkset+json"))); } @Test @@ -194,6 +228,7 @@ public void findOneItemJsonLinksetsWithBitstreams() throws Exception { context.restoreAuthSystemState(); String url = configurationService.getProperty("dspace.ui.url"); + String signpostingUrl = configurationService.getProperty("signposting.path"); getClient().perform(get("/signposting/linksets/" + item.getID() + "/json")) .andExpect(status().isOk()) .andExpect(jsonPath("$.linkset", @@ -209,7 +244,16 @@ public void findOneItemJsonLinksetsWithBitstreams() throws Exception { .andExpect(jsonPath("$.linkset[0].item[1].type", Matchers.hasToString(bitstream2MimeType))) .andExpect(jsonPath("$.linkset[0].anchor", - Matchers.hasToString(url + "/entities/publication/" + item.getID()))); + Matchers.hasToString(url + "/entities/publication/" + item.getID()))) + .andExpect(jsonPath("$.linkset[0].linkset[0].href", + Matchers.hasToString(url + "/" + signpostingUrl + "/linksets/" + item.getID().toString()))) + .andExpect(jsonPath("$.linkset[0].linkset[0].type", + Matchers.hasToString("application/linkset"))) + .andExpect(jsonPath("$.linkset[0].linkset[1].href", + Matchers.hasToString(url + "/" + signpostingUrl + "/linksets/" + item.getID().toString() + + "/json"))) + .andExpect(jsonPath("$.linkset[0].linkset[1].type", + Matchers.hasToString("application/linkset+json"))); } @Test @@ -343,6 +387,7 @@ public void findOneItemLsetLinksets() throws Exception { context.restoreAuthSystemState(); String url = configurationService.getProperty("dspace.ui.url"); + String signpostingUrl = configurationService.getProperty("signposting.path"); String siteAsRelation = "<" + MessageFormat.format(doiPattern, doi) + "> ; rel=\"cite-as\" ; anchor=\"" + url + "/entities/publication/" + item.getID() + "\" ,"; String itemRelation = "<" + url + "/bitstreams/" + bitstream1.getID() + @@ -350,11 +395,19 @@ public void findOneItemLsetLinksets() throws Exception { item.getID() + "\" ,"; String typeRelation = " ; rel=\"type\" ; anchor=\"" + url + "/entities/publication/" + item.getID() + "\" ,"; + String linksetRelation = "<" + url + "/" + signpostingUrl + "/linksets/" + item.getID().toString() + + "> ; rel=\"linkset\" ; type=\"application/linkset\" ;" + + " anchor=\"" + url + "/entities/publication/" + item.getID() + "\" ,"; + String jsonLinksetRelation = "<" + url + "/" + signpostingUrl + "/linksets/" + item.getID().toString() + + "/json> ; rel=\"linkset\" ; type=\"application/linkset+json\" ;" + + " anchor=\"" + url + "/entities/publication/" + item.getID() + "\" ,"; getClient().perform(get("/signposting/linksets/" + item.getID())) .andExpect(content().string(Matchers.containsString(siteAsRelation))) .andExpect(content().string(Matchers.containsString(itemRelation))) - .andExpect(content().string(Matchers.containsString(typeRelation))); + .andExpect(content().string(Matchers.containsString(typeRelation))) + .andExpect(content().string(Matchers.containsString(linksetRelation))) + .andExpect(content().string(Matchers.containsString(jsonLinksetRelation))); } @Test @@ -416,6 +469,7 @@ public void findTypedLinkForItemWithAuthor() throws Exception { context.restoreAuthSystemState(); String url = configurationService.getProperty("dspace.ui.url"); + String signpostingUrl = configurationService.getProperty("signposting.path"); String dcIdentifierUriMetadataValue = itemService .getMetadataFirstValue(publication, "dc", "identifier", "uri", Item.ANY); @@ -423,7 +477,7 @@ public void findTypedLinkForItemWithAuthor() throws Exception { .header("Accept", "application/json")) .andExpect(status().isOk()) .andExpect(jsonPath("$", - Matchers.hasSize(5))) + Matchers.hasSize(7))) .andExpect(jsonPath("$[?(@.href == '" + MessageFormat.format(orcidPattern, orcidValue) + "' " + "&& @.rel == 'author')]").exists()) .andExpect(jsonPath("$[?(@.href == '" + MessageFormat.format(doiPattern, doi) + "' " + @@ -434,7 +488,15 @@ public void findTypedLinkForItemWithAuthor() throws Exception { "&& @.rel == 'item' " + "&& @.type == 'text/plain')]").exists()) .andExpect(jsonPath("$[?(@.href == 'https://schema.org/AboutPage' " + - "&& @.rel == 'type')]").exists()); + "&& @.rel == 'type')]").exists()) + .andExpect(jsonPath("$[?(@.href == '" + url + "/" + signpostingUrl + "/linksets/" + + publication.getID().toString() + "' " + + "&& @.rel == 'linkset' " + + "&& @.type == 'application/linkset')]").exists()) + .andExpect(jsonPath("$[?(@.href == '" + url + "/" + signpostingUrl + "/linksets/" + + publication.getID().toString() + "/json' " + + "&& @.rel == 'linkset' " + + "&& @.type == 'application/linkset+json')]").exists()); } @Test diff --git a/dspace/config/spring/rest/signposting.xml b/dspace/config/spring/rest/signposting.xml index c8bc63c6f0dd..ba34fc6ce212 100644 --- a/dspace/config/spring/rest/signposting.xml +++ b/dspace/config/spring/rest/signposting.xml @@ -3,8 +3,6 @@ xmlns:util="http://www.springframework.org/schema/util" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd"> - - @@ -41,6 +39,9 @@ + + From 013b95ea2a8795e2f1f3a5a6eca3811457ce04b3 Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Fri, 16 Jun 2023 14:37:19 +0200 Subject: [PATCH 268/510] 102888: Implement feedback --- .../java/org/dspace/statistics/SolrLoggerServiceImpl.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 63858ce5810e..7853c3e11abf 100644 --- a/dspace-api/src/main/java/org/dspace/statistics/SolrLoggerServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/statistics/SolrLoggerServiceImpl.java @@ -219,7 +219,7 @@ public void postView(DSpaceObject dspaceObject, HttpServletRequest request, @Override public void postView(DSpaceObject dspaceObject, HttpServletRequest request, EPerson currentUser, String referrer) { - if (solr == null || locationService == null) { + if (solr == null) { return; } initSolrYearCores(); @@ -266,7 +266,7 @@ public void postView(DSpaceObject dspaceObject, @Override public void postView(DSpaceObject dspaceObject, String ip, String userAgent, String xforwardedfor, EPerson currentUser, String referrer) { - if (solr == null || locationService == null) { + if (solr == null) { return; } initSolrYearCores(); From 240bfbfdc95d3cdba4fb3d1e62dc02d0ceb3ac08 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 16 Jun 2023 11:00:03 -0500 Subject: [PATCH 269/510] Fix to adjusting bundle and bitstream policies (item install, embargo lift) Apply DEFAULT_ITEM_READ to bundles, not DEFAULT_BITSTREAM_READ so that files can be listed if the item / default item is readable --- .../java/org/dspace/content/ItemServiceImpl.java | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java index 694e3a58365f..8d1ba14b2c4a 100644 --- a/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java @@ -930,16 +930,23 @@ public void inheritCollectionDefaultPolicies(Context context, Item item, Collect @Override public void adjustBundleBitstreamPolicies(Context context, Item item, Collection collection) throws SQLException, AuthorizeException { - List defaultCollectionPolicies = authorizeService + // Bundles should inherit from DEFAULT_ITEM_READ so that if the item is readable, the files + // can be listed (even if they are themselves not readable as per DEFAULT_BITSTREAM_READ or other + // policies or embargos applied + List defaultCollectionBundlePolicies = authorizeService + .getPoliciesActionFilter(context, collection, Constants.DEFAULT_ITEM_READ); + // Bitstreams should inherit from DEFAULT_BITSTREAM_READ + List defaultCollectionBitstreamPolicies = authorizeService .getPoliciesActionFilter(context, collection, Constants.DEFAULT_BITSTREAM_READ); List defaultItemPolicies = authorizeService.findPoliciesByDSOAndType(context, item, ResourcePolicy.TYPE_CUSTOM); - if (defaultCollectionPolicies.size() < 1) { + if (defaultCollectionBitstreamPolicies.size() < 1) { throw new SQLException("Collection " + collection.getID() + " (" + collection.getHandle() + ")" + " has no default bitstream READ policies"); } + // TODO: should we also throw an exception if no DEFAULT_ITEM_READ? // remove all policies from bundles, add new ones // Remove bundles @@ -950,11 +957,12 @@ public void adjustBundleBitstreamPolicies(Context context, Item item, Collection authorizeService.removeAllPoliciesByDSOAndType(context, mybundle, ResourcePolicy.TYPE_SUBMISSION); authorizeService.removeAllPoliciesByDSOAndType(context, mybundle, ResourcePolicy.TYPE_WORKFLOW); addCustomPoliciesNotInPlace(context, mybundle, defaultItemPolicies); - addDefaultPoliciesNotInPlace(context, mybundle, defaultCollectionPolicies); + addDefaultPoliciesNotInPlace(context, mybundle, defaultCollectionBundlePolicies); for (Bitstream bitstream : mybundle.getBitstreams()) { // if come from InstallItem: remove all submission/workflow policies - removeAllPoliciesAndAddDefault(context, bitstream, defaultItemPolicies, defaultCollectionPolicies); + removeAllPoliciesAndAddDefault(context, bitstream, defaultItemPolicies, + defaultCollectionBitstreamPolicies); } } } From 3ff5eb61c0b7b80820dde771c96d7d18f8500858 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 16 Jun 2023 11:00:03 -0500 Subject: [PATCH 270/510] Fix to adjusting bundle and bitstream policies (item install, embargo lift) Apply DEFAULT_ITEM_READ to bundles, not DEFAULT_BITSTREAM_READ so that files can be listed if the item / default item is readable Co-authored-by: kshepherd Co-authored-by: tdonohue --- .../java/org/dspace/content/ItemTest.java | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/dspace-api/src/test/java/org/dspace/content/ItemTest.java b/dspace-api/src/test/java/org/dspace/content/ItemTest.java index bae6ce9e1d90..d440597ec416 100644 --- a/dspace-api/src/test/java/org/dspace/content/ItemTest.java +++ b/dspace-api/src/test/java/org/dspace/content/ItemTest.java @@ -1393,6 +1393,78 @@ public void testInheritCollectionDefaultPolicies() throws Exception { assertTrue("testInheritCollectionDefaultPolicies 2", equals); } + // Test to verify DEFAULT_*_READ policies on collection inherit properly to Item/Bundle/Bitstream + @Test + public void testInheritCollectionDefaultPolicies_custom_default_groups() throws Exception { + context.turnOffAuthorisationSystem(); + // Create a new collection + Collection c = createCollection(); + // Create a custom group with DEFAULT_ITEM_READ privileges in this Collection + Group item_read_role = collectionService.createDefaultReadGroup(context, c, "ITEM", + Constants.DEFAULT_ITEM_READ); + // Create a custom group with DEFAULT_BITSTREAM_READ privileges in this Collection + Group bitstream_read_role = collectionService.createDefaultReadGroup(context, c, "BITSTREAM", + Constants.DEFAULT_BITSTREAM_READ); + context.restoreAuthSystemState(); + + // Verify that Collection's DEFAULT_ITEM_READ now uses the newly created group. + List defaultItemReadPolicies = + authorizeService.getPoliciesActionFilter(context, c, Constants.DEFAULT_ITEM_READ); + assertEquals("One DEFAULT_ITEM_READ policy", 1, defaultItemReadPolicies.size()); + assertEquals("DEFAULT_ITEM_READ group", item_read_role.getName(), + defaultItemReadPolicies.get(0).getGroup().getName()); + + // Verify that Collection's DEFAULT_BITSTREAM_READ now uses the newly created group. + List defaultBitstreamReadPolicies = + authorizeService.getPoliciesActionFilter(context, c, Constants.DEFAULT_BITSTREAM_READ); + assertEquals("One DEFAULT_BITSTREAM_READ policy on Collection", 1, defaultBitstreamReadPolicies.size()); + assertEquals("DEFAULT_BITSTREAM_READ group", bitstream_read_role.getName(), + defaultBitstreamReadPolicies.get(0).getGroup().getName()); + + context.turnOffAuthorisationSystem(); + // Create a new Item in this Collection + WorkspaceItem workspaceItem = workspaceItemService.create(context, c, false); + Item item = workspaceItem.getItem(); + // Add a single Bitstream to the ORIGINAL bundle + File f = new File(testProps.get("test.bitstream").toString()); + Bitstream bitstream = itemService.createSingleBitstream(context, new FileInputStream(f), item); + context.restoreAuthSystemState(); + + // Allow Item WRITE perms + doNothing().when(authorizeServiceSpy).authorizeAction(context, item, Constants.WRITE, true); + // Inherit all default policies from Collection down to new Item + itemService.inheritCollectionDefaultPolicies(context, item, c); + + // Verify Item inherits DEFAULT_ITEM_READ group from Collection + List itemReadPolicies = authorizeService.getPoliciesActionFilter(context, item, Constants.READ); + assertEquals("One READ policy on Item", 1, itemReadPolicies.size()); + assertEquals("Item's READ group", item_read_role.getName(), + itemReadPolicies.get(0).getGroup().getName()); + + // Verify Bitstream inherits DEFAULT_BITSTREAM_READ group from Collection + List bitstreamReadPolicies = authorizeService.getPoliciesActionFilter(context, bitstream, + Constants.READ); + assertEquals("One READ policy on Bitstream", 1, bitstreamReadPolicies.size()); + assertEquals("Bitstream's READ group", bitstream_read_role.getName(), + bitstreamReadPolicies.get(0).getGroup().getName()); + + // Verify ORIGINAL Bundle inherits DEFAULT_ITEM_READ group from Collection + // Bundles should inherit from DEFAULT_ITEM_READ so that if the item is readable, the files + // can be listed (even if files are access restricted or embargoed) + List bundles = item.getBundles(Constants.DEFAULT_BUNDLE_NAME); + Bundle originalBundle = bundles.get(0); + List bundleReadPolicies = authorizeService.getPoliciesActionFilter(context, originalBundle, + Constants.READ); + assertEquals("One READ policy on Bundle", 1, bundleReadPolicies.size()); + assertEquals("Bundles's READ group", item_read_role.getName(), + bundleReadPolicies.get(0).getGroup().getName()); + + // Cleanup after ourselves. Delete created collection & all content under it + context.turnOffAuthorisationSystem(); + collectionService.delete(context, c); + context.restoreAuthSystemState(); + } + /** * Test of move method, of class Item. */ From 7dd34ccc5009180dab15482bd44551ea77ad4d45 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 16 Jun 2023 20:23:12 +0000 Subject: [PATCH 271/510] Bump guava from 31.0.1-jre to 32.0.0-jre Bumps [guava](https://github.com/google/guava) from 31.0.1-jre to 32.0.0-jre. - [Release notes](https://github.com/google/guava/releases) - [Commits](https://github.com/google/guava/commits) --- updated-dependencies: - dependency-name: com.google.guava:guava dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index deea90890c70..f8e873c8d4d5 100644 --- a/pom.xml +++ b/pom.xml @@ -1772,7 +1772,7 @@ com.google.guava guava - 31.0.1-jre + 32.0.0-jre From cf1257f20e3d9672c6d96eec4b1b949cf330e2b3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 20 Jun 2023 16:35:40 +0000 Subject: [PATCH 272/510] Bump netty-handler from 4.1.68.Final to 4.1.94.Final in /dspace-api Bumps [netty-handler](https://github.com/netty/netty) from 4.1.68.Final to 4.1.94.Final. - [Commits](https://github.com/netty/netty/compare/netty-4.1.68.Final...netty-4.1.94.Final) --- updated-dependencies: - dependency-name: io.netty:netty-handler dependency-type: direct:production ... 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 d41c51638c3a..b21a76f352e5 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -876,7 +876,7 @@ io.netty netty-handler - 4.1.68.Final + 4.1.94.Final io.netty From b52a4fb50dcda128b30b2aed55dfff180c0e9160 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 20 Jun 2023 13:23:49 -0500 Subject: [PATCH 273/510] Update all Netty dependencies to 4.1.94.Final Also had to add netty-transport-native-unix-common so that Solr doesn't throw dependency convergence issues. --- dspace-api/pom.xml | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index b21a76f352e5..c4c0b2182ac8 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -856,22 +856,27 @@ - io.netty netty-buffer - 4.1.68.Final + 4.1.94.Final io.netty netty-transport - 4.1.68.Final + 4.1.94.Final + + io.netty + netty-transport-native-unix-common + 4.1.94.Final + io.netty netty-common - 4.1.68.Final + 4.1.94.Final io.netty @@ -881,7 +886,7 @@ io.netty netty-codec - 4.1.68.Final + 4.1.94.Final org.apache.velocity From 907c45a455d365a1e57bafe749ab64240bcdc181 Mon Sep 17 00:00:00 2001 From: Nikita Krivonosov Date: Wed, 21 Jun 2023 17:20:01 +0200 Subject: [PATCH 274/510] [DURACOM-156] Singposting: feedback after initial deployment --- .../controller/LinksetRestController.java | 129 ++++++++---- .../converter/LinksetConverter.java | 12 +- .../converter/LinksetNodeConverter.java | 37 ---- .../LinksetRestMessageConverter.java | 30 +-- .../app/rest/signposting/model/Linkset.java | 24 +++ .../model/LinksetRelationType.java | 4 +- .../rest/signposting/model/LinksetRest.java | 12 -- .../model/MetadataConfiguration.java | 10 + .../rest/signposting/model/TypedLinkRest.java | 13 +- .../item/ItemDescribedbyProcessor.java | 88 ++++++++ .../item/ItemIdentifierProcessor.java | 53 +---- ...MetadataDescribesSignpostingProcessor.java | 53 +++++ .../MetadataSignpostingProcessor.java | 45 +++++ .../rest/signposting/utils/LinksetMapper.java | 4 +- .../controller/LinksetRestControllerIT.java | 189 +++++++++++++++--- dspace/config/spring/rest/signposting.xml | 21 +- 16 files changed, 541 insertions(+), 183 deletions(-) delete mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/converter/LinksetNodeConverter.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemDescribedbyProcessor.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/metadata/MetadataDescribesSignpostingProcessor.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/metadata/MetadataSignpostingProcessor.java diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/controller/LinksetRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/controller/LinksetRestController.java index 7932d94f7918..eecdefe84253 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/controller/LinksetRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/controller/LinksetRestController.java @@ -12,18 +12,23 @@ import java.sql.SQLException; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; import java.util.UUID; import java.util.stream.Collectors; import javax.servlet.http.HttpServletRequest; +import org.apache.log4j.Logger; import org.dspace.app.rest.converter.ConverterService; +import org.dspace.app.rest.security.BitstreamMetadataReadPermissionEvaluatorPlugin; import org.dspace.app.rest.signposting.converter.LinksetRestMessageConverter; +import org.dspace.app.rest.signposting.model.Linkset; import org.dspace.app.rest.signposting.model.LinksetNode; import org.dspace.app.rest.signposting.model.LinksetRest; import org.dspace.app.rest.signposting.model.TypedLinkRest; import org.dspace.app.rest.signposting.processor.bitstream.BitstreamSignpostingProcessor; import org.dspace.app.rest.signposting.processor.item.ItemSignpostingProcessor; +import org.dspace.app.rest.signposting.processor.metadata.MetadataSignpostingProcessor; import org.dspace.app.rest.signposting.utils.LinksetMapper; import org.dspace.app.rest.utils.ContextUtil; import org.dspace.app.rest.utils.Utils; @@ -58,6 +63,8 @@ @ConditionalOnProperty("signposting.enabled") public class LinksetRestController { + private static final Logger log = Logger.getLogger(LinksetRestController.class); + @Autowired private Utils utils; @Autowired @@ -66,6 +73,14 @@ public class LinksetRestController { private ItemService itemService; @Autowired private ConverterService converter; + @Autowired + private BitstreamMetadataReadPermissionEvaluatorPlugin bitstreamMetadataReadPermissionEvaluatorPlugin; + private List bitstreamProcessors = new DSpace().getServiceManager() + .getServicesByType(BitstreamSignpostingProcessor.class); + private List itemProcessors = new DSpace().getServiceManager() + .getServicesByType(ItemSignpostingProcessor.class); + private List metadataProcessors = new DSpace().getServiceManager() + .getServicesByType(MetadataSignpostingProcessor.class); @PreAuthorize("permitAll()") @RequestMapping(method = RequestMethod.GET) @@ -85,16 +100,9 @@ public LinksetRest getJson(HttpServletRequest request, @PathVariable UUID uuid) throw new ResourceNotFoundException("No such Item: " + uuid); } verifyItemIsDiscoverable(item); - - List linksetNodes = new ArrayList<>(); - if (item.getType() == Constants.ITEM) { - List ispp = new DSpace().getServiceManager() - .getServicesByType(ItemSignpostingProcessor.class); - for (ItemSignpostingProcessor sp : ispp) { - sp.addLinkSetNodes(context, request, item, linksetNodes); - } - } - return converter.toRest(LinksetMapper.map(linksetNodes), utils.obtainProjection()); + List> linksetNodes = createLinksetNodes(request, context, item); + List linksets = linksetNodes.stream().map(LinksetMapper::map).collect(Collectors.toList()); + return converter.toRest(linksets, utils.obtainProjection()); } catch (SQLException e) { throw new RuntimeException(e); } @@ -111,23 +119,8 @@ public String getLset(HttpServletRequest request, @PathVariable UUID uuid) { throw new ResourceNotFoundException("No such Item: " + uuid); } verifyItemIsDiscoverable(item); - - List linksetNodes = new ArrayList<>(); - List ispp = new DSpace().getServiceManager() - .getServicesByType(ItemSignpostingProcessor.class); - for (ItemSignpostingProcessor sp : ispp) { - sp.addLinkSetNodes(context, request, item, linksetNodes); - } - - LinksetRest linksetRest = null; - for (LinksetNode linksetNode : linksetNodes) { - if (linksetRest == null) { - linksetRest = converter.toRest(linksetNode, utils.obtainProjection()); - } else { - linksetRest.getLinksetNodes().add(linksetNode); - } - } - return LinksetRestMessageConverter.convert(linksetRest); + List> linksetNodes = createLinksetNodes(request, context, item); + return LinksetRestMessageConverter.convert(linksetNodes); } catch (SQLException e) { throw new RuntimeException(e); } @@ -154,27 +147,91 @@ public List getHeader(HttpServletRequest request, @PathVariable U List linksetNodes = new ArrayList<>(); if (dso.getType() == Constants.ITEM) { verifyItemIsDiscoverable((Item) dso); - List ispp = new DSpace().getServiceManager() - .getServicesByType(ItemSignpostingProcessor.class); - for (ItemSignpostingProcessor sp : ispp) { - sp.addLinkSetNodes(context, request, (Item) dso, linksetNodes); + for (ItemSignpostingProcessor processor : itemProcessors) { + processor.addLinkSetNodes(context, request, (Item) dso, linksetNodes); } } else { - List bspp = new DSpace().getServiceManager() - .getServicesByType(BitstreamSignpostingProcessor.class); - for (BitstreamSignpostingProcessor sp : bspp) { - sp.addLinkSetNodes(context, request, (Bitstream) dso, linksetNodes); + for (BitstreamSignpostingProcessor processor : bitstreamProcessors) { + processor.addLinkSetNodes(context, request, (Bitstream) dso, linksetNodes); } } return linksetNodes.stream() - .map(node -> new TypedLinkRest(node.getLink(), node.getRelation(), node.getType())) + .map(node -> + new TypedLinkRest(node.getLink(), node.getRelation(), node.getType(), node.getAnchor())) .collect(Collectors.toList()); } catch (SQLException e) { throw new RuntimeException(e); } } + private List> createLinksetNodes( + HttpServletRequest request, + Context context, Item item + ) throws SQLException { + ArrayList> linksets = new ArrayList<>(); + addItemLinksets(request, context, item, linksets); + addBitstreamLinksets(request, context, item, linksets); + addMetadataLinksets(request, context, item, linksets); + return linksets; + } + + private void addMetadataLinksets( + HttpServletRequest request, + Context context, + Item item, + ArrayList> linksets + ) { + for (MetadataSignpostingProcessor processor : metadataProcessors) { + List metadataLinkset = new ArrayList<>(); + processor.addLinkSetNodes(context, request, item, metadataLinkset); + if (!metadataLinkset.isEmpty()) { + linksets.add(metadataLinkset); + } + } + } + + private void addBitstreamLinksets( + HttpServletRequest request, + Context context, + Item item, + ArrayList> linksets + ) throws SQLException { + Iterator bitstreamsIterator = bitstreamService.getItemBitstreams(context, item); + bitstreamsIterator.forEachRemaining(bitstream -> { + try { + boolean isAuthorized = bitstreamMetadataReadPermissionEvaluatorPlugin + .metadataReadPermissionOnBitstream(context, bitstream); + if (isAuthorized) { + List bitstreamLinkset = new ArrayList<>(); + for (BitstreamSignpostingProcessor processor : bitstreamProcessors) { + processor.addLinkSetNodes(context, request, bitstream, bitstreamLinkset); + } + if (!bitstreamLinkset.isEmpty()) { + linksets.add(bitstreamLinkset); + } + } + } catch (SQLException e) { + log.error(e.getMessage(), e); + } + }); + } + + private void addItemLinksets( + HttpServletRequest request, + Context context, + Item item, + List> linksets + ) { + List linksetNodes = new ArrayList<>(); + if (item.getType() == Constants.ITEM) { + for (ItemSignpostingProcessor sp : itemProcessors) { + sp.addLinkSetNodes(context, request, item, linksetNodes); + } + } + linksets.add(linksetNodes); + } + private static void verifyItemIsDiscoverable(Item item) { if (!item.isDiscoverable()) { String message = format("Item with uuid [%s] is not Discoverable", item.getID().toString()); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/converter/LinksetConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/converter/LinksetConverter.java index e8f151f14a00..90786b9dc426 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/converter/LinksetConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/converter/LinksetConverter.java @@ -7,6 +7,8 @@ */ package org.dspace.app.rest.signposting.converter; +import java.util.List; + import org.dspace.app.rest.converter.DSpaceConverter; import org.dspace.app.rest.projection.Projection; import org.dspace.app.rest.signposting.model.Linkset; @@ -20,18 +22,18 @@ * @author Francesco Pio Scognamiglio (francescopio.scognamiglio at 4science.com) */ @Component -public class LinksetConverter implements DSpaceConverter { +public class LinksetConverter implements DSpaceConverter, LinksetRest> { @Override - public LinksetRest convert(Linkset linkset, Projection projection) { + public LinksetRest convert(List linksets, Projection projection) { LinksetRest linksetRest = new LinksetRest(); linksetRest.setProjection(projection); - linksetRest.getLinkset().add(linkset); + linksetRest.setLinkset(linksets); return linksetRest; } @Override - public Class getModelClass() { - return Linkset.class; + public Class> getModelClass() { + return (Class>) ((Class) List.class); } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/converter/LinksetNodeConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/converter/LinksetNodeConverter.java deleted file mode 100644 index e73aa260ad3e..000000000000 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/converter/LinksetNodeConverter.java +++ /dev/null @@ -1,37 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.app.rest.signposting.converter; - -import org.dspace.app.rest.converter.DSpaceConverter; -import org.dspace.app.rest.projection.Projection; -import org.dspace.app.rest.signposting.model.LinksetNode; -import org.dspace.app.rest.signposting.model.LinksetRest; -import org.springframework.stereotype.Component; - - -/** - * This is the converter from/to the Lset in the DSpace API data model and the REST data model. - * - * @author Francesco Pio Scognamiglio (francescopio.scognamiglio at 4science.com) - */ -@Component -public class LinksetNodeConverter implements DSpaceConverter { - - @Override - public LinksetRest convert(LinksetNode linkSetNode, Projection projection) { - LinksetRest linksetRest = new LinksetRest(); - linksetRest.setProjection(projection); - linksetRest.getLinksetNodes().add(linkSetNode); - return linksetRest; - } - - @Override - public Class getModelClass() { - return LinksetNode.class; - } -} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/converter/LinksetRestMessageConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/converter/LinksetRestMessageConverter.java index 03c27897b9cd..24c8e6735dc9 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/converter/LinksetRestMessageConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/converter/LinksetRestMessageConverter.java @@ -11,10 +11,12 @@ import static java.util.Objects.nonNull; import static org.apache.commons.lang.StringUtils.isNotBlank; -import org.dspace.app.rest.signposting.model.LinksetRest; +import java.util.List; + +import org.dspace.app.rest.signposting.model.LinksetNode; /** - * Converter for converting LinksetRest message into application/linkset format. + * Converter for converting list of linkset nodes into application/linkset format. */ public class LinksetRestMessageConverter { @@ -22,25 +24,25 @@ private LinksetRestMessageConverter() { } /** - * Converts LinksetRest object into string of application/linkset format. + * Converts list of linkset nodes into string of application/linkset format. * - * @param linksetRest linkset rest object + * @param linksetNodes link of linkset nodes * @return string of application/linkset format. */ - public static String convert(LinksetRest linksetRest) { + public static String convert(List> linksetNodes) { StringBuilder responseBody = new StringBuilder(); - linksetRest.getLinksetNodes().forEach(linksetNodes -> { - if (isNotBlank(linksetNodes.getLink())) { - responseBody.append(format("<%s> ", linksetNodes.getLink())); + linksetNodes.stream().flatMap(List::stream).forEach(linksetNode -> { + if (isNotBlank(linksetNode.getLink())) { + responseBody.append(format("<%s> ", linksetNode.getLink())); } - if (nonNull(linksetNodes.getRelation())) { - responseBody.append(format("; rel=\"%s\" ", linksetNodes.getRelation().getName())); + if (nonNull(linksetNode.getRelation())) { + responseBody.append(format("; rel=\"%s\" ", linksetNode.getRelation().getName())); } - if (isNotBlank(linksetNodes.getType())) { - responseBody.append(format("; type=\"%s\" ", linksetNodes.getType())); + if (isNotBlank(linksetNode.getType())) { + responseBody.append(format("; type=\"%s\" ", linksetNode.getType())); } - if (isNotBlank(linksetNodes.getAnchor())) { - responseBody.append(format("; anchor=\"%s\" ", linksetNodes.getAnchor())); + if (isNotBlank(linksetNode.getAnchor())) { + responseBody.append(format("; anchor=\"%s\" ", linksetNode.getAnchor())); } responseBody.append(", "); }); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/model/Linkset.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/model/Linkset.java index c70e4916f69c..14d6f6581c7a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/model/Linkset.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/model/Linkset.java @@ -33,6 +33,10 @@ public class Linkset { private List license; @JsonInclude(JsonInclude.Include.NON_EMPTY) private List linkset; + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private List describes; + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private List describedby; @JsonInclude(JsonInclude.Include.NON_NULL) private String anchor; @@ -106,6 +110,26 @@ public void setLinkset(List linkset) { this.linkset = linkset; } + public List getDescribes() { + if (describes == null) { + describes = new ArrayList<>(); + } + return describes; + } + public void setDescribes(List describes) { + this.describes = describes; + } + + public List getDescribedby() { + if (describedby == null) { + describes = new ArrayList<>(); + } + return describedby; + } + public void setDescribedby(List describedby) { + this.describedby = describedby; + } + public String getAnchor() { return anchor; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/model/LinksetRelationType.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/model/LinksetRelationType.java index 7a513b5d998a..285bf5a56ee1 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/model/LinksetRelationType.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/model/LinksetRelationType.java @@ -20,7 +20,9 @@ public enum LinksetRelationType { TYPE("type"), LICENSE("license"), COLLECTION("collection"), - LINKSET("linkset"); + LINKSET("linkset"), + DESCRIBES("describes"), + DESCRIBED_BY("describedby"); private final String name; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/model/LinksetRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/model/LinksetRest.java index 437e486ad097..df80cd5c2d50 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/model/LinksetRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/model/LinksetRest.java @@ -32,8 +32,6 @@ public class LinksetRest extends RestAddressableModel { @JsonInclude(Include.NON_EMPTY) private List linkset; - @JsonInclude(Include.NON_EMPTY) - private List linkSetNode; public List getLinkset() { if (this.linkset == null) { @@ -45,16 +43,6 @@ public void setLinkset(List linkset) { this.linkset = linkset; } - public List getLinksetNodes() { - if (this.linkSetNode == null) { - this.linkSetNode = new ArrayList<>(); - } - return linkSetNode; - } - public void setLset(List linkSetNode) { - this.linkSetNode = linkSetNode; - } - @JsonIgnore @Override public String getType() { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/model/MetadataConfiguration.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/model/MetadataConfiguration.java index 99dabd003f94..c49b32834686 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/model/MetadataConfiguration.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/model/MetadataConfiguration.java @@ -16,6 +16,8 @@ public class MetadataConfiguration { private String pattern; + private String mimeType; + public MetadataConfiguration() { } @@ -39,4 +41,12 @@ public String getPattern() { public void setPattern(String pattern) { this.pattern = pattern; } + + public String getMimeType() { + return mimeType; + } + + public void setMimeType(String mimeType) { + this.mimeType = mimeType; + } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/model/TypedLinkRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/model/TypedLinkRest.java index 3ba09bf1094c..5fbd10a3022a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/model/TypedLinkRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/model/TypedLinkRest.java @@ -28,13 +28,16 @@ public class TypedLinkRest extends RestAddressableModel { private String type; + private String anchor; + public TypedLinkRest() { } - public TypedLinkRest(String href, LinksetRelationType rel, String type) { + public TypedLinkRest(String href, LinksetRelationType rel, String type, String anchor) { this.href = href; this.rel = rel; this.type = type; + this.anchor = anchor; } public String getHref() { @@ -57,6 +60,14 @@ public void setType(String type) { this.type = type; } + public String getAnchor() { + return anchor; + } + + public void setAnchor(String anchor) { + this.anchor = anchor; + } + @Override public String getType() { return type; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemDescribedbyProcessor.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemDescribedbyProcessor.java new file mode 100644 index 000000000000..78d95cddb88d --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemDescribedbyProcessor.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.signposting.processor.item; + +import static java.util.Objects.isNull; +import static java.util.Objects.nonNull; +import static org.apache.commons.lang3.StringUtils.isNotBlank; + +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.List; +import javax.servlet.http.HttpServletRequest; + +import org.apache.log4j.Logger; +import org.dspace.app.rest.signposting.model.LinksetNode; +import org.dspace.app.rest.signposting.model.LinksetRelationType; +import org.dspace.app.rest.signposting.model.MetadataConfiguration; +import org.dspace.content.Item; +import org.dspace.content.MetadataValue; +import org.dspace.content.service.ItemService; +import org.dspace.core.Context; +import org.dspace.util.FrontendUrlService; + +/** + * An extension of {@link ItemSignpostingProcessor} for the describedby relation. + */ +public class ItemDescribedbyProcessor extends ItemSignpostingProcessor { + + private static final Logger log = Logger.getLogger(ItemDescribedbyProcessor.class); + + private List metadataConfigurations; + + private final ItemService itemService; + + public ItemDescribedbyProcessor(FrontendUrlService frontendUrlService, ItemService itemService) { + super(frontendUrlService); + this.itemService = itemService; + setRelation(LinksetRelationType.DESCRIBED_BY); + } + + @Override + public void addLinkSetNodes(Context context, HttpServletRequest request, + Item item, List linksetNodes) { + getMetadataConfigurations() + .forEach(metadataHandle -> handleMetadata(context, item, linksetNodes, metadataHandle)); + } + + private void handleMetadata(Context context, + Item item, + List linksetNodes, + MetadataConfiguration metadataConfiguration) { + try { + List identifiers = itemService + .getMetadataByMetadataString(item, metadataConfiguration.getMetadataField()); + for (MetadataValue identifier : identifiers) { + if (nonNull(identifier)) { + String identifierValue = identifier.getValue(); + if (isNotBlank(identifierValue)) { + if (isNotBlank(metadataConfiguration.getPattern())) { + identifierValue = MessageFormat.format(metadataConfiguration.getPattern(), identifierValue); + } + LinksetNode node = new LinksetNode(identifierValue, getRelation(), + metadataConfiguration.getMimeType(), buildAnchor(context, item)); + linksetNodes.add(node); + } + } + } + } catch (Exception e) { + log.error(e.getMessage(), e); + } + } + + public List getMetadataConfigurations() { + if (isNull(metadataConfigurations)) { + metadataConfigurations = new ArrayList<>(); + } + return metadataConfigurations; + } + + public void setMetadataConfigurations(List metadataConfigurations) { + this.metadataConfigurations = metadataConfigurations; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemIdentifierProcessor.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemIdentifierProcessor.java index 79a820d702d8..c5ebe958d97d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemIdentifierProcessor.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemIdentifierProcessor.java @@ -7,21 +7,17 @@ */ package org.dspace.app.rest.signposting.processor.item; -import static java.util.Objects.isNull; import static java.util.Objects.nonNull; import static org.apache.commons.lang3.StringUtils.isNotBlank; import java.text.MessageFormat; -import java.util.ArrayList; import java.util.List; import javax.servlet.http.HttpServletRequest; -import org.apache.log4j.Logger; import org.dspace.app.rest.signposting.model.LinksetNode; import org.dspace.app.rest.signposting.model.LinksetRelationType; -import org.dspace.app.rest.signposting.model.MetadataConfiguration; import org.dspace.content.Item; -import org.dspace.content.MetadataValue; +import org.dspace.content.MetadataFieldName; import org.dspace.content.service.ItemService; import org.dspace.core.Context; import org.dspace.util.FrontendUrlService; @@ -35,13 +31,6 @@ */ public class ItemIdentifierProcessor extends ItemSignpostingProcessor { - /** - * log4j category - */ - private static final Logger log = Logger.getLogger(ItemIdentifierProcessor.class); - - private List metadataConfigurations; - private final ItemService itemService; public ItemIdentifierProcessor(FrontendUrlService frontendUrlService, ItemService itemService) { @@ -53,41 +42,13 @@ public ItemIdentifierProcessor(FrontendUrlService frontendUrlService, ItemServic @Override public void addLinkSetNodes(Context context, HttpServletRequest request, Item item, List linksetNodes) { - getMetadataConfigurations() - .forEach(metadataHandle -> handleMetadata(context, item, linksetNodes, metadataHandle)); - } - - private void handleMetadata(Context context, - Item item, - List linksetNodes, - MetadataConfiguration metadataConfiguration) { - try { - List identifiers = itemService - .getMetadataByMetadataString(item, metadataConfiguration.getMetadataField()); - for (MetadataValue identifier : identifiers) { - if (nonNull(identifier)) { - String identifierValue = identifier.getValue(); - if (isNotBlank(identifierValue)) { - if (isNotBlank(metadataConfiguration.getPattern())) { - identifierValue = MessageFormat.format(metadataConfiguration.getPattern(), identifierValue); - } - linksetNodes.add(new LinksetNode(identifierValue, getRelation(), buildAnchor(context, item))); - } - } + String identifier = itemService + .getMetadataFirstValue(item, new MetadataFieldName(getMetadataField()), Item.ANY); + if (nonNull(identifier)) { + if (isNotBlank(getPattern())) { + identifier = MessageFormat.format(getPattern(), item); } - } catch (Exception e) { - log.error(e.getMessage(), e); + linksetNodes.add(new LinksetNode(identifier, getRelation(), buildAnchor(context, item))); } } - - public List getMetadataConfigurations() { - if (isNull(metadataConfigurations)) { - metadataConfigurations = new ArrayList<>(); - } - return metadataConfigurations; - } - - public void setMetadataConfigurations(List metadataConfigurations) { - this.metadataConfigurations = metadataConfigurations; - } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/metadata/MetadataDescribesSignpostingProcessor.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/metadata/MetadataDescribesSignpostingProcessor.java new file mode 100644 index 000000000000..8e3751bc2059 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/metadata/MetadataDescribesSignpostingProcessor.java @@ -0,0 +1,53 @@ +/** + * 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.signposting.processor.metadata; + +import static org.dspace.content.Item.ANY; + +import java.util.List; +import javax.servlet.http.HttpServletRequest; + +import org.apache.commons.lang.StringUtils; +import org.dspace.app.rest.signposting.model.LinksetNode; +import org.dspace.app.rest.signposting.model.LinksetRelationType; +import org.dspace.app.rest.signposting.model.MetadataConfiguration; +import org.dspace.content.Item; +import org.dspace.content.MetadataFieldName; +import org.dspace.content.service.ItemService; +import org.dspace.core.Context; +import org.dspace.util.FrontendUrlService; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * An extension of {@link MetadataSignpostingProcessor} for the 'describes' relation. + */ +public class MetadataDescribesSignpostingProcessor extends MetadataSignpostingProcessor { + + @Autowired + private FrontendUrlService frontendUrlService; + + public MetadataDescribesSignpostingProcessor(ItemService itemService) { + super(itemService); + setRelation(LinksetRelationType.DESCRIBES); + } + + @Override + public void addLinkSetNodes( + Context context, + HttpServletRequest request, + Item item, + List linksetNodes + ) { + String metadataValue = itemService.getMetadataFirstValue(item, new MetadataFieldName(getMetadataField()), ANY); + if (StringUtils.isNotBlank(metadataValue)) { + String itemUrl = frontendUrlService.generateUrl(context, item); + String anchor = buildAnchor(new MetadataConfiguration(getMetadataField(), getPattern()), item); + linksetNodes.add(new LinksetNode(itemUrl, getRelation(), "text/html", anchor)); + } + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/metadata/MetadataSignpostingProcessor.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/metadata/MetadataSignpostingProcessor.java new file mode 100644 index 000000000000..27f06960e753 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/metadata/MetadataSignpostingProcessor.java @@ -0,0 +1,45 @@ +/** + * 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.signposting.processor.metadata; + +import static org.apache.commons.lang.StringUtils.isNotBlank; +import static org.dspace.content.Item.ANY; + +import java.text.MessageFormat; + +import org.apache.logging.log4j.util.Strings; +import org.dspace.app.rest.signposting.model.MetadataConfiguration; +import org.dspace.app.rest.signposting.processor.AbstractSignPostingProcessor; +import org.dspace.app.rest.signposting.processor.SignPostingProcessor; +import org.dspace.content.Item; +import org.dspace.content.MetadataFieldName; +import org.dspace.content.service.ItemService; + +/** + * An abstract class represents {@link SignPostingProcessor } for a metadata. + */ +public abstract class MetadataSignpostingProcessor extends AbstractSignPostingProcessor + implements SignPostingProcessor { + + protected final ItemService itemService; + + public MetadataSignpostingProcessor(ItemService itemService) { + this.itemService = itemService; + } + + public String buildAnchor(MetadataConfiguration metadataConfiguration, Item item) { + String metadataValue = itemService + .getMetadataFirstValue(item, new MetadataFieldName(metadataConfiguration.getMetadataField()), ANY); + if (isNotBlank(metadataValue)) { + return isNotBlank(metadataConfiguration.getPattern()) + ? MessageFormat.format(metadataConfiguration.getPattern(), metadataValue) + : metadataValue; + } + return Strings.EMPTY; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/utils/LinksetMapper.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/utils/LinksetMapper.java index 31860143b997..5da05bc44059 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/utils/LinksetMapper.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/utils/LinksetMapper.java @@ -26,7 +26,7 @@ private LinksetMapper() { /** * Converts list of linkset nodes into linkset. * - * @param linksetNodes + * @param linksetNodes list of linkset nodes * @return linkset */ public static Linkset map(List linksetNodes) { @@ -38,6 +38,8 @@ public static Linkset map(List linksetNodes) { linkset.setCollection(getLinksetRelationsByType(linksetNodes, LinksetRelationType.COLLECTION)); linkset.setLicense(getLinksetRelationsByType(linksetNodes, LinksetRelationType.LICENSE)); linkset.setCiteAs(getLinksetRelationsByType(linksetNodes, LinksetRelationType.CITE_AS)); + linkset.setDescribes(getLinksetRelationsByType(linksetNodes, LinksetRelationType.DESCRIBES)); + linkset.setDescribedby(getLinksetRelationsByType(linksetNodes, LinksetRelationType.DESCRIBED_BY)); if (!linksetNodes.isEmpty()) { linkset.setAnchor(linksetNodes.get(0).getAnchor()); } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/signposting/controller/LinksetRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/signposting/controller/LinksetRestControllerIT.java index 7b3b6543ef3e..ed3e63dde570 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/signposting/controller/LinksetRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/signposting/controller/LinksetRestControllerIT.java @@ -115,9 +115,17 @@ public void findOneItemJsonLinksets() throws Exception { getClient().perform(get("/signposting/linksets/" + item.getID() + "/json")) .andExpect(status().isOk()) .andExpect(jsonPath("$.linkset", - Matchers.hasSize(1))) + Matchers.hasSize(3))) .andExpect(jsonPath("$.linkset[0].cite-as[0].href", + Matchers.hasToString(url + "/handle/" + item.getHandle()))) + .andExpect(jsonPath("$.linkset[0].describedby[0].href", Matchers.hasToString(MessageFormat.format(doiPattern, doi)))) + .andExpect(jsonPath("$.linkset[0].describedby[0].type", + Matchers.hasToString("application/vnd.datacite.datacite+json"))) + .andExpect(jsonPath("$.linkset[0].describedby[1].href", + Matchers.hasToString(url + "/handle/" + item.getHandle()))) + .andExpect(jsonPath("$.linkset[0].describedby[1].type", + Matchers.hasToString("text/html"))) .andExpect(jsonPath("$.linkset[0].linkset[0].href", Matchers.hasToString(url + "/" + signpostingUrl + "/linksets/" + item.getID().toString()))) .andExpect(jsonPath("$.linkset[0].linkset[0].type", @@ -127,6 +135,18 @@ public void findOneItemJsonLinksets() throws Exception { "/json"))) .andExpect(jsonPath("$.linkset[0].linkset[1].type", Matchers.hasToString("application/linkset+json"))) + .andExpect(jsonPath("$.linkset[1].describes[0].href", + Matchers.hasToString(url + "/entities/publication/" + item.getID()))) + .andExpect(jsonPath("$.linkset[1].describes[0].type", + Matchers.hasToString("text/html"))) + .andExpect(jsonPath("$.linkset[1].anchor", + Matchers.hasToString("http://localhost:4000/handle/" + item.getHandle()))) + .andExpect(jsonPath("$.linkset[2].describes[0].href", + Matchers.hasToString(url + "/entities/publication/" + item.getID()))) + .andExpect(jsonPath("$.linkset[2].describes[0].type", + Matchers.hasToString("text/html"))) + .andExpect(jsonPath("$.linkset[2].anchor", + Matchers.hasToString(MessageFormat.format(doiPattern, doi)))) ; } @@ -146,9 +166,17 @@ public void findOneItemJsonLinksetsWithType() throws Exception { getClient().perform(get("/signposting/linksets/" + item.getID() + "/json")) .andExpect(status().isOk()) .andExpect(jsonPath("$.linkset", - Matchers.hasSize(1))) + Matchers.hasSize(3))) .andExpect(jsonPath("$.linkset[0].cite-as[0].href", + Matchers.hasToString(url + "/handle/" + item.getHandle()))) + .andExpect(jsonPath("$.linkset[0].describedby[0].href", Matchers.hasToString(MessageFormat.format(doiPattern, doi)))) + .andExpect(jsonPath("$.linkset[0].describedby[0].type", + Matchers.hasToString("application/vnd.datacite.datacite+json"))) + .andExpect(jsonPath("$.linkset[0].describedby[1].href", + Matchers.hasToString(url + "/handle/" + item.getHandle()))) + .andExpect(jsonPath("$.linkset[0].describedby[1].type", + Matchers.hasToString("text/html"))) .andExpect(jsonPath("$.linkset[0].type", Matchers.hasSize(2))) .andExpect(jsonPath("$.linkset[0].type[0].href", @@ -163,7 +191,19 @@ public void findOneItemJsonLinksetsWithType() throws Exception { Matchers.hasToString(url + "/" + signpostingUrl + "/linksets/" + item.getID().toString() + "/json"))) .andExpect(jsonPath("$.linkset[0].linkset[1].type", - Matchers.hasToString("application/linkset+json"))); + Matchers.hasToString("application/linkset+json"))) + .andExpect(jsonPath("$.linkset[1].describes[0].href", + Matchers.hasToString(url + "/entities/publication/" + item.getID()))) + .andExpect(jsonPath("$.linkset[1].describes[0].type", + Matchers.hasToString("text/html"))) + .andExpect(jsonPath("$.linkset[1].anchor", + Matchers.hasToString("http://localhost:4000/handle/" + item.getHandle()))) + .andExpect(jsonPath("$.linkset[2].describes[0].href", + Matchers.hasToString(url + "/entities/publication/" + item.getID()))) + .andExpect(jsonPath("$.linkset[2].describes[0].type", + Matchers.hasToString("text/html"))) + .andExpect(jsonPath("$.linkset[2].anchor", + Matchers.hasToString(MessageFormat.format(doiPattern, doi)))); } @Test @@ -181,7 +221,7 @@ public void findOneItemJsonLinksetsWithLicence() throws Exception { getClient().perform(get("/signposting/linksets/" + item.getID() + "/json")) .andExpect(status().isOk()) .andExpect(jsonPath("$.linkset", - Matchers.hasSize(1))) + Matchers.hasSize(2))) .andExpect(jsonPath("$.linkset[0].type[0].href", Matchers.hasToString("https://schema.org/AboutPage"))) .andExpect(jsonPath("$.linkset[0].license[0].href", @@ -194,7 +234,13 @@ public void findOneItemJsonLinksetsWithLicence() throws Exception { Matchers.hasToString(url + "/" + signpostingUrl + "/linksets/" + item.getID().toString() + "/json"))) .andExpect(jsonPath("$.linkset[0].linkset[1].type", - Matchers.hasToString("application/linkset+json"))); + Matchers.hasToString("application/linkset+json"))) + .andExpect(jsonPath("$.linkset[1].describes[0].href", + Matchers.hasToString(url + "/entities/publication/" + item.getID()))) + .andExpect(jsonPath("$.linkset[1].describes[0].type", + Matchers.hasToString("text/html"))) + .andExpect(jsonPath("$.linkset[1].anchor", + Matchers.hasToString("http://localhost:4000/handle/" + item.getHandle()))); } @Test @@ -232,9 +278,17 @@ public void findOneItemJsonLinksetsWithBitstreams() throws Exception { getClient().perform(get("/signposting/linksets/" + item.getID() + "/json")) .andExpect(status().isOk()) .andExpect(jsonPath("$.linkset", - Matchers.hasSize(1))) + Matchers.hasSize(5))) .andExpect(jsonPath("$.linkset[0].cite-as[0].href", + Matchers.hasToString(url + "/handle/" + item.getHandle()))) + .andExpect(jsonPath("$.linkset[0].describedby[0].href", Matchers.hasToString(MessageFormat.format(doiPattern, doi)))) + .andExpect(jsonPath("$.linkset[0].describedby[0].type", + Matchers.hasToString("application/vnd.datacite.datacite+json"))) + .andExpect(jsonPath("$.linkset[0].describedby[1].href", + Matchers.hasToString(url + "/handle/" + item.getHandle()))) + .andExpect(jsonPath("$.linkset[0].describedby[1].type", + Matchers.hasToString("text/html"))) .andExpect(jsonPath("$.linkset[0].item[0].href", Matchers.hasToString(url + "/bitstreams/" + bitstream1.getID() + "/download"))) .andExpect(jsonPath("$.linkset[0].item[0].type", @@ -253,7 +307,49 @@ public void findOneItemJsonLinksetsWithBitstreams() throws Exception { Matchers.hasToString(url + "/" + signpostingUrl + "/linksets/" + item.getID().toString() + "/json"))) .andExpect(jsonPath("$.linkset[0].linkset[1].type", - Matchers.hasToString("application/linkset+json"))); + Matchers.hasToString("application/linkset+json"))) + .andExpect(jsonPath("$.linkset[1].collection[0].href", + Matchers.hasToString(url + "/entities/publication/" + item.getID()))) + .andExpect(jsonPath("$.linkset[1].collection[0].type", + Matchers.hasToString("text/html"))) + .andExpect(jsonPath("$.linkset[1].linkset[0].href", + Matchers.hasToString(url + "/" + signpostingUrl + "/linksets/" + item.getID().toString()))) + .andExpect(jsonPath("$.linkset[1].linkset[0].type", + Matchers.hasToString("application/linkset"))) + .andExpect(jsonPath("$.linkset[1].linkset[1].href", + Matchers.hasToString(url + "/" + signpostingUrl + "/linksets/" + item.getID().toString() + + "/json"))) + .andExpect(jsonPath("$.linkset[1].linkset[1].type", + Matchers.hasToString("application/linkset+json"))) + .andExpect(jsonPath("$.linkset[1].anchor", + Matchers.hasToString(url + "/bitstreams/" + bitstream1.getID() + "/download"))) + .andExpect(jsonPath("$.linkset[2].collection[0].href", + Matchers.hasToString(url + "/entities/publication/" + item.getID()))) + .andExpect(jsonPath("$.linkset[2].collection[0].type", + Matchers.hasToString("text/html"))) + .andExpect(jsonPath("$.linkset[2].linkset[0].href", + Matchers.hasToString(url + "/" + signpostingUrl + "/linksets/" + item.getID().toString()))) + .andExpect(jsonPath("$.linkset[2].linkset[0].type", + Matchers.hasToString("application/linkset"))) + .andExpect(jsonPath("$.linkset[2].linkset[1].href", + Matchers.hasToString(url + "/" + signpostingUrl + "/linksets/" + item.getID().toString() + + "/json"))) + .andExpect(jsonPath("$.linkset[2].linkset[1].type", + Matchers.hasToString("application/linkset+json"))) + .andExpect(jsonPath("$.linkset[2].anchor", + Matchers.hasToString(url + "/bitstreams/" + bitstream2.getID() + "/download"))) + .andExpect(jsonPath("$.linkset[3].describes[0].href", + Matchers.hasToString(url + "/entities/publication/" + item.getID()))) + .andExpect(jsonPath("$.linkset[3].describes[0].type", + Matchers.hasToString("text/html"))) + .andExpect(jsonPath("$.linkset[3].anchor", + Matchers.hasToString(url + "/handle/" + item.getHandle()))) + .andExpect(jsonPath("$.linkset[4].describes[0].href", + Matchers.hasToString(url + "/entities/publication/" + item.getID()))) + .andExpect(jsonPath("$.linkset[4].describes[0].type", + Matchers.hasToString("text/html"))) + .andExpect(jsonPath("$.linkset[4].anchor", + Matchers.hasToString(MessageFormat.format(doiPattern, doi)))); } @Test @@ -374,7 +470,6 @@ public void findOneItemLsetLinksets() throws Exception { context.turnOffAuthorisationSystem(); Item item = ItemBuilder.createItem(context, collection) .withTitle("Item Test") - .withMetadata("dc", "identifier", "doi", doi) .build(); Bitstream bitstream1 = null; try (InputStream is = IOUtils.toInputStream(bitstream1Content, CharEncoding.UTF_8)) { @@ -388,7 +483,7 @@ public void findOneItemLsetLinksets() throws Exception { String url = configurationService.getProperty("dspace.ui.url"); String signpostingUrl = configurationService.getProperty("signposting.path"); - String siteAsRelation = "<" + MessageFormat.format(doiPattern, doi) + "> ; rel=\"cite-as\" ; anchor=\"" + + String siteAsRelation = "<" + url + "/handle/" + item.getHandle() + "> ; rel=\"cite-as\" ; anchor=\"" + url + "/entities/publication/" + item.getID() + "\" ,"; String itemRelation = "<" + url + "/bitstreams/" + bitstream1.getID() + "/download> ; rel=\"item\" ; " + "type=\"text/plain\" ; anchor=\"" + url + "/entities/publication/" + @@ -401,13 +496,34 @@ public void findOneItemLsetLinksets() throws Exception { String jsonLinksetRelation = "<" + url + "/" + signpostingUrl + "/linksets/" + item.getID().toString() + "/json> ; rel=\"linkset\" ; type=\"application/linkset+json\" ;" + " anchor=\"" + url + "/entities/publication/" + item.getID() + "\" ,"; + String describedByRelation = "<" + url + "/handle/" + item.getHandle() + "> ; rel=\"describedby\" ;" + + " type=\"text/html\" ; anchor=\"" + url + "/entities/publication/" + item.getID() + "\" ,"; + + String bitstreamCollectionLink = "<" + url + "/entities/publication/" + item.getID() + "> ;" + + " rel=\"collection\" ; type=\"text/html\" ; anchor=\"" + url + "/bitstreams/" + + bitstream1.getID() + "/download\""; + String bitstreamLinksetLink = "<" + url + "/" + signpostingUrl + "/linksets/" + item.getID() + "> ; " + + "rel=\"linkset\" ; type=\"application/linkset\" ; " + + "anchor=\"" + url + "/bitstreams/" + bitstream1.getID() + "/download\""; + String bitstreamLinksetJsonLink = "<" + url + "/" + signpostingUrl + "/linksets/" + item.getID() + "/json> ; " + + "rel=\"linkset\" ; type=\"application/linkset+json\" ; " + + "anchor=\"" + url + "/bitstreams/" + bitstream1.getID() + "/download\""; + + String describesMetadataLink = "<" + url + "/entities/publication/" + item.getID() + "> ; " + + "rel=\"describes\" ; type=\"text/html\" ; " + + "anchor=\"" + url + "/handle/" + item.getHandle() + "\""; getClient().perform(get("/signposting/linksets/" + item.getID())) .andExpect(content().string(Matchers.containsString(siteAsRelation))) .andExpect(content().string(Matchers.containsString(itemRelation))) .andExpect(content().string(Matchers.containsString(typeRelation))) .andExpect(content().string(Matchers.containsString(linksetRelation))) - .andExpect(content().string(Matchers.containsString(jsonLinksetRelation))); + .andExpect(content().string(Matchers.containsString(jsonLinksetRelation))) + .andExpect(content().string(Matchers.containsString(describedByRelation))) + .andExpect(content().string(Matchers.containsString(bitstreamCollectionLink))) + .andExpect(content().string(Matchers.containsString(bitstreamLinksetLink))) + .andExpect(content().string(Matchers.containsString(bitstreamLinksetJsonLink))) + .andExpect(content().string(Matchers.containsString(describesMetadataLink))); } @Test @@ -472,31 +588,42 @@ public void findTypedLinkForItemWithAuthor() throws Exception { String signpostingUrl = configurationService.getProperty("signposting.path"); String dcIdentifierUriMetadataValue = itemService .getMetadataFirstValue(publication, "dc", "identifier", "uri", Item.ANY); + String anchor = url + "/entities/publication/" + publication.getID(); getClient().perform(get("/signposting/links/" + publication.getID()) .header("Accept", "application/json")) .andExpect(status().isOk()) .andExpect(jsonPath("$", - Matchers.hasSize(7))) + Matchers.hasSize(8))) .andExpect(jsonPath("$[?(@.href == '" + MessageFormat.format(orcidPattern, orcidValue) + "' " + - "&& @.rel == 'author')]").exists()) + "&& @.rel == 'author' " + + "&& @.anchor == '" + anchor + "')]").exists()) .andExpect(jsonPath("$[?(@.href == '" + MessageFormat.format(doiPattern, doi) + "' " + - "&& @.rel == 'cite-as')]").exists()) + "&& @.rel == 'describedby' " + + "&& @.anchor == '" + anchor + "')]").exists()) .andExpect(jsonPath("$[?(@.href == '" + dcIdentifierUriMetadataValue + "' " + - "&& @.rel == 'cite-as')]").exists()) + "&& @.rel == 'describedby' " + + "&& @.anchor == '" + anchor + "')]").exists()) + .andExpect(jsonPath("$[?(@.href == '" + dcIdentifierUriMetadataValue + "' " + + "&& @.rel == 'cite-as' " + + "&& @.anchor == '" + anchor + "')]").exists()) .andExpect(jsonPath("$[?(@.href == '" + url + "/bitstreams/" + bitstream.getID() + "/download' " + "&& @.rel == 'item' " + - "&& @.type == 'text/plain')]").exists()) + "&& @.type == 'text/plain' " + + "&& @.anchor == '" + anchor + "')]").exists()) .andExpect(jsonPath("$[?(@.href == 'https://schema.org/AboutPage' " + - "&& @.rel == 'type')]").exists()) + "&& @.rel == 'type' " + + "&& @.anchor == '" + anchor + "')]").exists()) .andExpect(jsonPath("$[?(@.href == '" + url + "/" + signpostingUrl + "/linksets/" + publication.getID().toString() + "' " + "&& @.rel == 'linkset' " + - "&& @.type == 'application/linkset')]").exists()) + "&& @.type == 'application/linkset' " + + "&& @.anchor == '" + anchor + "')]").exists()) .andExpect(jsonPath("$[?(@.href == '" + url + "/" + signpostingUrl + "/linksets/" + publication.getID().toString() + "/json' " + "&& @.rel == 'linkset' " + - "&& @.type == 'application/linkset+json')]").exists()); + "&& @.type == 'application/linkset+json' " + + "&& @.anchor == '" + anchor + "')]").exists()); } @Test @@ -521,6 +648,7 @@ public void findTypedLinkForBitstream() throws Exception { context.restoreAuthSystemState(); String uiUrl = configurationService.getProperty("dspace.ui.url"); + String anchor = uiUrl + "/bitstreams/" + bitstream.getID() + "/download"; getClient().perform(get("/signposting/links/" + bitstream.getID()) .header("Accept", "application/json")) .andExpect(status().isOk()) @@ -528,13 +656,16 @@ public void findTypedLinkForBitstream() throws Exception { Matchers.hasSize(3))) .andExpect(jsonPath("$[?(@.href == '" + uiUrl + "/entities/publication/" + item.getID() + "' " + "&& @.rel == 'collection' " + - "&& @.type == 'text/html')]").exists()) + "&& @.type == 'text/html' " + + "&& @.anchor == '" + anchor + "')]").exists()) .andExpect(jsonPath("$[?(@.href == '" + uiUrl + "/signposting/linksets/" + item.getID() + "' " + "&& @.rel == 'linkset' " + - "&& @.type == 'application/linkset')]").exists()) + "&& @.type == 'application/linkset' " + + "&& @.anchor == '" + anchor + "')]").exists()) .andExpect(jsonPath("$[?(@.href == '" + uiUrl + "/signposting/linksets/" + item.getID() + "/json" + "' && @.rel == 'linkset' " + - "&& @.type == 'application/linkset+json')]").exists()); + "&& @.type == 'application/linkset+json' " + + "&& @.anchor == '" + anchor + "')]").exists()); DSpaceServicesFactory.getInstance().getConfigurationService().reloadConfig(); metadataAuthorityService.clearCache(); @@ -565,6 +696,7 @@ public void findTypedLinkForBitstreamWithType() throws Exception { context.restoreAuthSystemState(); String uiUrl = configurationService.getProperty("dspace.ui.url"); + String anchor = uiUrl + "/bitstreams/" + bitstream.getID() + "/download"; getClient().perform(get("/signposting/links/" + bitstream.getID()) .header("Accept", "application/json")) .andExpect(status().isOk()) @@ -572,15 +704,19 @@ public void findTypedLinkForBitstreamWithType() throws Exception { Matchers.hasSize(4))) .andExpect(jsonPath("$[?(@.href == '" + uiUrl + "/entities/publication/" + item.getID() + "' " + "&& @.rel == 'collection' " + - "&& @.type == 'text/html')]").exists()) + "&& @.type == 'text/html' " + + "&& @.anchor == '" + anchor + "')]").exists()) .andExpect(jsonPath("$[?(@.href == '" + uiUrl + "/signposting/linksets/" + item.getID() + "' " + "&& @.rel == 'linkset' " + - "&& @.type == 'application/linkset')]").exists()) + "&& @.type == 'application/linkset' " + + "&& @.anchor == '" + anchor + "')]").exists()) .andExpect(jsonPath("$[?(@.href == '" + uiUrl + "/signposting/linksets/" + item.getID() + "/json" + "' && @.rel == 'linkset' " + - "&& @.type == 'application/linkset+json')]").exists()) + "&& @.type == 'application/linkset+json' " + + "&& @.anchor == '" + anchor + "')]").exists()) .andExpect(jsonPath("$[?(@.href == 'https://schema.org/ScholarlyArticle' " + - "&& @.rel == 'type')]").exists()); + "&& @.rel == 'type' " + + "&& @.anchor == '" + anchor + "')]").exists()); DSpaceServicesFactory.getInstance().getConfigurationService().reloadConfig(); metadataAuthorityService.clearCache(); @@ -612,7 +748,6 @@ public void findTypedLinkForRestrictedBitstream() throws Exception { } context.restoreAuthSystemState(); - String uiUrl = configurationService.getProperty("dspace.ui.url"); getClient().perform(get("/signposting/links/" + bitstream.getID()) .header("Accept", "application/json")) .andExpect(status().isUnauthorized()); @@ -645,7 +780,6 @@ public void findTypedLinkForBitstreamUnderEmbargo() throws Exception { } context.restoreAuthSystemState(); - String uiUrl = configurationService.getProperty("dspace.ui.url"); getClient().perform(get("/signposting/links/" + bitstream.getID()) .header("Accept", "application/json")) .andExpect(status().isUnauthorized()); @@ -677,7 +811,6 @@ public void findTypedLinkForBitstreamOfWorkspaceItem() throws Exception { } context.restoreAuthSystemState(); - String uiUrl = configurationService.getProperty("dspace.ui.url"); getClient().perform(get("/signposting/links/" + bitstream.getID()) .header("Accept", "application/json")) .andExpect(status().isUnauthorized()); diff --git a/dspace/config/spring/rest/signposting.xml b/dspace/config/spring/rest/signposting.xml index ba34fc6ce212..51060590d280 100644 --- a/dspace/config/spring/rest/signposting.xml +++ b/dspace/config/spring/rest/signposting.xml @@ -19,15 +19,22 @@ - + - + + + + + + + @@ -52,4 +59,14 @@ + + + + + + + + From 55bfd4b2e54460f968a23875cb669dc12938ec8a Mon Sep 17 00:00:00 2001 From: Nikita Krivonosov Date: Thu, 22 Jun 2023 13:50:20 +0200 Subject: [PATCH 275/510] [DURACOM-156] Singposting: feedback after initial deployment --- .../controller/LinksetRestController.java | 166 +++++-------- .../rest/signposting/model/TypedLinkRest.java | 13 +- .../item/ItemDescribedbyProcessor.java | 58 +---- ...MetadataDescribesSignpostingProcessor.java | 18 +- .../MetadataSignpostingProcessor.java | 31 +-- .../signposting/service/LinksetService.java | 50 ++++ .../service/impl/LinksetServiceImpl.java | 151 ++++++++++++ .../controller/LinksetRestControllerIT.java | 232 +++++++++++------- dspace/config/modules/signposting.cfg | 8 +- dspace/config/spring/rest/signposting.xml | 30 +-- 10 files changed, 441 insertions(+), 316 deletions(-) create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/service/LinksetService.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/service/impl/LinksetServiceImpl.java diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/controller/LinksetRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/controller/LinksetRestController.java index eecdefe84253..98642bb703ed 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/controller/LinksetRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/controller/LinksetRestController.java @@ -8,38 +8,42 @@ package org.dspace.app.rest.signposting.controller; import static java.lang.String.format; +import static java.util.Objects.isNull; import static org.dspace.app.rest.utils.RegexUtils.REGEX_REQUESTMAPPING_IDENTIFIER_AS_UUID; +import java.io.IOException; import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Iterator; import java.util.List; import java.util.UUID; import java.util.stream.Collectors; import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; -import org.apache.log4j.Logger; import org.dspace.app.rest.converter.ConverterService; -import org.dspace.app.rest.security.BitstreamMetadataReadPermissionEvaluatorPlugin; import org.dspace.app.rest.signposting.converter.LinksetRestMessageConverter; import org.dspace.app.rest.signposting.model.Linkset; import org.dspace.app.rest.signposting.model.LinksetNode; import org.dspace.app.rest.signposting.model.LinksetRest; import org.dspace.app.rest.signposting.model.TypedLinkRest; -import org.dspace.app.rest.signposting.processor.bitstream.BitstreamSignpostingProcessor; -import org.dspace.app.rest.signposting.processor.item.ItemSignpostingProcessor; -import org.dspace.app.rest.signposting.processor.metadata.MetadataSignpostingProcessor; +import org.dspace.app.rest.signposting.service.LinksetService; import org.dspace.app.rest.signposting.utils.LinksetMapper; import org.dspace.app.rest.utils.ContextUtil; import org.dspace.app.rest.utils.Utils; +import org.dspace.authorize.AuthorizeException; import org.dspace.content.Bitstream; import org.dspace.content.DSpaceObject; import org.dspace.content.Item; +import org.dspace.content.crosswalk.CrosswalkException; +import org.dspace.content.crosswalk.DisseminationCrosswalk; import org.dspace.content.service.BitstreamService; import org.dspace.content.service.ItemService; -import org.dspace.core.Constants; import org.dspace.core.Context; -import org.dspace.utils.DSpace; +import org.dspace.core.factory.CoreServiceFactory; +import org.dspace.core.service.PluginService; +import org.dspace.services.ConfigurationService; +import org.jdom2.Element; +import org.jdom2.output.Format; +import org.jdom2.output.XMLOutputter; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.data.rest.webmvc.ResourceNotFoundException; @@ -63,8 +67,6 @@ @ConditionalOnProperty("signposting.enabled") public class LinksetRestController { - private static final Logger log = Logger.getLogger(LinksetRestController.class); - @Autowired private Utils utils; @Autowired @@ -74,13 +76,10 @@ public class LinksetRestController { @Autowired private ConverterService converter; @Autowired - private BitstreamMetadataReadPermissionEvaluatorPlugin bitstreamMetadataReadPermissionEvaluatorPlugin; - private List bitstreamProcessors = new DSpace().getServiceManager() - .getServicesByType(BitstreamSignpostingProcessor.class); - private List itemProcessors = new DSpace().getServiceManager() - .getServicesByType(ItemSignpostingProcessor.class); - private List metadataProcessors = new DSpace().getServiceManager() - .getServicesByType(MetadataSignpostingProcessor.class); + private LinksetService linksetService; + @Autowired + private ConfigurationService configurationService; + private final PluginService pluginService = CoreServiceFactory.getInstance().getPluginService(); @PreAuthorize("permitAll()") @RequestMapping(method = RequestMethod.GET) @@ -100,7 +99,8 @@ public LinksetRest getJson(HttpServletRequest request, @PathVariable UUID uuid) throw new ResourceNotFoundException("No such Item: " + uuid); } verifyItemIsDiscoverable(item); - List> linksetNodes = createLinksetNodes(request, context, item); + List> linksetNodes = linksetService + .createLinksetNodesForMultipleLinksets(request, context, item); List linksets = linksetNodes.stream().map(LinksetMapper::map).collect(Collectors.toList()); return converter.toRest(linksets, utils.obtainProjection()); } catch (SQLException e) { @@ -113,13 +113,13 @@ public LinksetRest getJson(HttpServletRequest request, @PathVariable UUID uuid) public String getLset(HttpServletRequest request, @PathVariable UUID uuid) { try { Context context = ContextUtil.obtainContext(request); - Item item = itemService.find(context, uuid); if (item == null) { throw new ResourceNotFoundException("No such Item: " + uuid); } verifyItemIsDiscoverable(item); - List> linksetNodes = createLinksetNodes(request, context, item); + List> linksetNodes = linksetService + .createLinksetNodesForMultipleLinksets(request, context, item); return LinksetRestMessageConverter.convert(linksetNodes); } catch (SQLException e) { throw new RuntimeException(e); @@ -133,103 +133,49 @@ public String getLset(HttpServletRequest request, @PathVariable UUID uuid) { @PreAuthorize("hasPermission(#uuid, 'ITEM', 'READ') && hasPermission(#uuid, 'BITSTREAM', 'READ')") @RequestMapping(value = "/links" + REGEX_REQUESTMAPPING_IDENTIFIER_AS_UUID, method = RequestMethod.GET) public List getHeader(HttpServletRequest request, @PathVariable UUID uuid) { - try { - Context context = ContextUtil.obtainContext(request); - - DSpaceObject dso = bitstreamService.find(context, uuid); - if (dso == null) { - dso = itemService.find(context, uuid); - if (dso == null) { - throw new ResourceNotFoundException("No such resource: " + uuid); - } - } - - List linksetNodes = new ArrayList<>(); - if (dso.getType() == Constants.ITEM) { - verifyItemIsDiscoverable((Item) dso); - for (ItemSignpostingProcessor processor : itemProcessors) { - processor.addLinkSetNodes(context, request, (Item) dso, linksetNodes); - } - } else { - for (BitstreamSignpostingProcessor processor : bitstreamProcessors) { - processor.addLinkSetNodes(context, request, (Bitstream) dso, linksetNodes); - } - } - - return linksetNodes.stream() - .map(node -> - new TypedLinkRest(node.getLink(), node.getRelation(), node.getType(), node.getAnchor())) - .collect(Collectors.toList()); - } catch (SQLException e) { - throw new RuntimeException(e); - } + Context context = ContextUtil.obtainContext(request); + DSpaceObject dso = findObject(context, uuid); + List linksetNodes = linksetService.createLinksetNodesForSingleLinkset(request, context, dso); + return linksetNodes.stream() + .map(node -> new TypedLinkRest(node.getLink(), node.getRelation(), node.getType())) + .collect(Collectors.toList()); } - private List> createLinksetNodes( + @PreAuthorize("hasPermission(#uuid, 'ITEM', 'READ')") + @RequestMapping(value = "/describedby" + REGEX_REQUESTMAPPING_IDENTIFIER_AS_UUID, method = RequestMethod.GET) + public String getDescribedBy( HttpServletRequest request, - Context context, Item item - ) throws SQLException { - ArrayList> linksets = new ArrayList<>(); - addItemLinksets(request, context, item, linksets); - addBitstreamLinksets(request, context, item, linksets); - addMetadataLinksets(request, context, item, linksets); - return linksets; + HttpServletResponse response, + @PathVariable UUID uuid + ) throws SQLException, AuthorizeException, IOException, CrosswalkException { + Context context = ContextUtil.obtainContext(request); + String xwalkName = configurationService.getProperty("signposting.describedby.crosswalk-name"); + String responseMimeType = configurationService.getProperty("signposting.describedby.mime-type"); + response.addHeader("Content-Type", responseMimeType); + + DSpaceObject object = findObject(context, uuid); + DisseminationCrosswalk xwalk = (DisseminationCrosswalk) + pluginService.getNamedPlugin(DisseminationCrosswalk.class, xwalkName); + List elements = xwalk.disseminateList(context, object); + XMLOutputter outputter = new XMLOutputter(Format.getCompactFormat()); + return outputter.outputString(elements); } - private void addMetadataLinksets( - HttpServletRequest request, - Context context, - Item item, - ArrayList> linksets - ) { - for (MetadataSignpostingProcessor processor : metadataProcessors) { - List metadataLinkset = new ArrayList<>(); - processor.addLinkSetNodes(context, request, item, metadataLinkset); - if (!metadataLinkset.isEmpty()) { - linksets.add(metadataLinkset); - } - } - } - - private void addBitstreamLinksets( - HttpServletRequest request, - Context context, - Item item, - ArrayList> linksets - ) throws SQLException { - Iterator bitstreamsIterator = bitstreamService.getItemBitstreams(context, item); - bitstreamsIterator.forEachRemaining(bitstream -> { - try { - boolean isAuthorized = bitstreamMetadataReadPermissionEvaluatorPlugin - .metadataReadPermissionOnBitstream(context, bitstream); - if (isAuthorized) { - List bitstreamLinkset = new ArrayList<>(); - for (BitstreamSignpostingProcessor processor : bitstreamProcessors) { - processor.addLinkSetNodes(context, request, bitstream, bitstreamLinkset); - } - if (!bitstreamLinkset.isEmpty()) { - linksets.add(bitstreamLinkset); - } + private DSpaceObject findObject(Context context, UUID uuid) { + try { + DSpaceObject object = itemService.find(context, uuid); + if (isNull(object)) { + object = bitstreamService.find(context, uuid); + if (isNull(object)) { + throw new ResourceNotFoundException("No such resource: " + uuid); } - } catch (SQLException e) { - log.error(e.getMessage(), e); - } - }); - } - - private void addItemLinksets( - HttpServletRequest request, - Context context, - Item item, - List> linksets - ) { - List linksetNodes = new ArrayList<>(); - if (item.getType() == Constants.ITEM) { - for (ItemSignpostingProcessor sp : itemProcessors) { - sp.addLinkSetNodes(context, request, item, linksetNodes); + } else { + verifyItemIsDiscoverable((Item) object); } + return object; + } catch (SQLException e) { + throw new RuntimeException(e); } - linksets.add(linksetNodes); } private static void verifyItemIsDiscoverable(Item item) { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/model/TypedLinkRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/model/TypedLinkRest.java index 5fbd10a3022a..3ba09bf1094c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/model/TypedLinkRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/model/TypedLinkRest.java @@ -28,16 +28,13 @@ public class TypedLinkRest extends RestAddressableModel { private String type; - private String anchor; - public TypedLinkRest() { } - public TypedLinkRest(String href, LinksetRelationType rel, String type, String anchor) { + public TypedLinkRest(String href, LinksetRelationType rel, String type) { this.href = href; this.rel = rel; this.type = type; - this.anchor = anchor; } public String getHref() { @@ -60,14 +57,6 @@ public void setType(String type) { this.type = type; } - public String getAnchor() { - return anchor; - } - - public void setAnchor(String anchor) { - this.anchor = anchor; - } - @Override public String getType() { return type; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemDescribedbyProcessor.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemDescribedbyProcessor.java index 78d95cddb88d..a16770c4d103 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemDescribedbyProcessor.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemDescribedbyProcessor.java @@ -7,23 +7,15 @@ */ package org.dspace.app.rest.signposting.processor.item; -import static java.util.Objects.isNull; -import static java.util.Objects.nonNull; -import static org.apache.commons.lang3.StringUtils.isNotBlank; - -import java.text.MessageFormat; -import java.util.ArrayList; import java.util.List; import javax.servlet.http.HttpServletRequest; import org.apache.log4j.Logger; import org.dspace.app.rest.signposting.model.LinksetNode; import org.dspace.app.rest.signposting.model.LinksetRelationType; -import org.dspace.app.rest.signposting.model.MetadataConfiguration; import org.dspace.content.Item; -import org.dspace.content.MetadataValue; -import org.dspace.content.service.ItemService; import org.dspace.core.Context; +import org.dspace.services.ConfigurationService; import org.dspace.util.FrontendUrlService; /** @@ -33,56 +25,26 @@ public class ItemDescribedbyProcessor extends ItemSignpostingProcessor { private static final Logger log = Logger.getLogger(ItemDescribedbyProcessor.class); - private List metadataConfigurations; - - private final ItemService itemService; + private final ConfigurationService configurationService; - public ItemDescribedbyProcessor(FrontendUrlService frontendUrlService, ItemService itemService) { + public ItemDescribedbyProcessor(FrontendUrlService frontendUrlService, ConfigurationService configurationService) { super(frontendUrlService); - this.itemService = itemService; + this.configurationService = configurationService; setRelation(LinksetRelationType.DESCRIBED_BY); } @Override public void addLinkSetNodes(Context context, HttpServletRequest request, Item item, List linksetNodes) { - getMetadataConfigurations() - .forEach(metadataHandle -> handleMetadata(context, item, linksetNodes, metadataHandle)); - } - - private void handleMetadata(Context context, - Item item, - List linksetNodes, - MetadataConfiguration metadataConfiguration) { try { - List identifiers = itemService - .getMetadataByMetadataString(item, metadataConfiguration.getMetadataField()); - for (MetadataValue identifier : identifiers) { - if (nonNull(identifier)) { - String identifierValue = identifier.getValue(); - if (isNotBlank(identifierValue)) { - if (isNotBlank(metadataConfiguration.getPattern())) { - identifierValue = MessageFormat.format(metadataConfiguration.getPattern(), identifierValue); - } - LinksetNode node = new LinksetNode(identifierValue, getRelation(), - metadataConfiguration.getMimeType(), buildAnchor(context, item)); - linksetNodes.add(node); - } - } - } + String signpostingPath = configurationService.getProperty("signposting.path"); + String baseUrl = configurationService.getProperty("dspace.ui.url"); + String mimeType = configurationService.getProperty("signposting.describedby.mime-type"); + String describedByUrl = baseUrl + "/" + signpostingPath + "/describedby/" + item.getID(); + LinksetNode node = new LinksetNode(describedByUrl, getRelation(), mimeType, buildAnchor(context, item)); + linksetNodes.add(node); } catch (Exception e) { log.error(e.getMessage(), e); } } - - public List getMetadataConfigurations() { - if (isNull(metadataConfigurations)) { - metadataConfigurations = new ArrayList<>(); - } - return metadataConfigurations; - } - - public void setMetadataConfigurations(List metadataConfigurations) { - this.metadataConfigurations = metadataConfigurations; - } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/metadata/MetadataDescribesSignpostingProcessor.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/metadata/MetadataDescribesSignpostingProcessor.java index 8e3751bc2059..baae16b88389 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/metadata/MetadataDescribesSignpostingProcessor.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/metadata/MetadataDescribesSignpostingProcessor.java @@ -7,18 +7,12 @@ */ package org.dspace.app.rest.signposting.processor.metadata; -import static org.dspace.content.Item.ANY; - import java.util.List; import javax.servlet.http.HttpServletRequest; -import org.apache.commons.lang.StringUtils; import org.dspace.app.rest.signposting.model.LinksetNode; import org.dspace.app.rest.signposting.model.LinksetRelationType; -import org.dspace.app.rest.signposting.model.MetadataConfiguration; import org.dspace.content.Item; -import org.dspace.content.MetadataFieldName; -import org.dspace.content.service.ItemService; import org.dspace.core.Context; import org.dspace.util.FrontendUrlService; import org.springframework.beans.factory.annotation.Autowired; @@ -31,8 +25,7 @@ public class MetadataDescribesSignpostingProcessor extends MetadataSignpostingPr @Autowired private FrontendUrlService frontendUrlService; - public MetadataDescribesSignpostingProcessor(ItemService itemService) { - super(itemService); + public MetadataDescribesSignpostingProcessor() { setRelation(LinksetRelationType.DESCRIBES); } @@ -43,11 +36,8 @@ public void addLinkSetNodes( Item item, List linksetNodes ) { - String metadataValue = itemService.getMetadataFirstValue(item, new MetadataFieldName(getMetadataField()), ANY); - if (StringUtils.isNotBlank(metadataValue)) { - String itemUrl = frontendUrlService.generateUrl(context, item); - String anchor = buildAnchor(new MetadataConfiguration(getMetadataField(), getPattern()), item); - linksetNodes.add(new LinksetNode(itemUrl, getRelation(), "text/html", anchor)); - } + String itemUrl = frontendUrlService.generateUrl(context, item); + String anchor = buildAnchor(item); + linksetNodes.add(new LinksetNode(itemUrl, getRelation(), "text/html", anchor)); } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/metadata/MetadataSignpostingProcessor.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/metadata/MetadataSignpostingProcessor.java index 27f06960e753..7b4e9135f1a8 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/metadata/MetadataSignpostingProcessor.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/metadata/MetadataSignpostingProcessor.java @@ -7,18 +7,11 @@ */ package org.dspace.app.rest.signposting.processor.metadata; -import static org.apache.commons.lang.StringUtils.isNotBlank; -import static org.dspace.content.Item.ANY; - -import java.text.MessageFormat; - -import org.apache.logging.log4j.util.Strings; -import org.dspace.app.rest.signposting.model.MetadataConfiguration; import org.dspace.app.rest.signposting.processor.AbstractSignPostingProcessor; import org.dspace.app.rest.signposting.processor.SignPostingProcessor; import org.dspace.content.Item; -import org.dspace.content.MetadataFieldName; -import org.dspace.content.service.ItemService; +import org.dspace.services.ConfigurationService; +import org.dspace.services.factory.DSpaceServicesFactory; /** * An abstract class represents {@link SignPostingProcessor } for a metadata. @@ -26,20 +19,12 @@ public abstract class MetadataSignpostingProcessor extends AbstractSignPostingProcessor implements SignPostingProcessor { - protected final ItemService itemService; - - public MetadataSignpostingProcessor(ItemService itemService) { - this.itemService = itemService; - } + private final ConfigurationService configurationService = + DSpaceServicesFactory.getInstance().getConfigurationService(); - public String buildAnchor(MetadataConfiguration metadataConfiguration, Item item) { - String metadataValue = itemService - .getMetadataFirstValue(item, new MetadataFieldName(metadataConfiguration.getMetadataField()), ANY); - if (isNotBlank(metadataValue)) { - return isNotBlank(metadataConfiguration.getPattern()) - ? MessageFormat.format(metadataConfiguration.getPattern(), metadataValue) - : metadataValue; - } - return Strings.EMPTY; + public String buildAnchor(Item item) { + String baseUrl = configurationService.getProperty("dspace.ui.url"); + String signpostingPath = configurationService.getProperty("signposting.path"); + return baseUrl + "/" + signpostingPath + "/describedby/" + item.getID(); } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/service/LinksetService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/service/LinksetService.java new file mode 100644 index 000000000000..33d0c10b7415 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/service/LinksetService.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.signposting.service; + +import java.util.List; +import javax.servlet.http.HttpServletRequest; + +import org.dspace.app.rest.signposting.model.LinksetNode; +import org.dspace.content.DSpaceObject; +import org.dspace.content.Item; +import org.dspace.core.Context; + +/** + * Service for work with linksets. + */ +public interface LinksetService { + + /** + * Returns list of linkset nodes for multiple linksets. + * + * @param request request + * @param context context + * @param item item + * @return two-dimensional list representing a list of lists where each list represents the linkset nodes. + */ + List> createLinksetNodesForMultipleLinksets( + HttpServletRequest request, + Context context, + Item item + ); + + /** + * Returns list of linkset nodes for single linkset. + * + * @param request request + * @param context context + * @param object dspace object + * @return two-dimensional list representing a list of lists where each list represents the linkset nodes. + */ + List createLinksetNodesForSingleLinkset( + HttpServletRequest request, + Context context, + DSpaceObject object + ); +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/service/impl/LinksetServiceImpl.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/service/impl/LinksetServiceImpl.java new file mode 100644 index 000000000000..e7fa25de93bb --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/service/impl/LinksetServiceImpl.java @@ -0,0 +1,151 @@ +/** + * 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.signposting.service.impl; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import javax.servlet.http.HttpServletRequest; + +import org.apache.log4j.Logger; +import org.dspace.app.rest.security.BitstreamMetadataReadPermissionEvaluatorPlugin; +import org.dspace.app.rest.signposting.model.LinksetNode; +import org.dspace.app.rest.signposting.processor.bitstream.BitstreamSignpostingProcessor; +import org.dspace.app.rest.signposting.processor.item.ItemSignpostingProcessor; +import org.dspace.app.rest.signposting.processor.metadata.MetadataSignpostingProcessor; +import org.dspace.app.rest.signposting.service.LinksetService; +import org.dspace.content.Bitstream; +import org.dspace.content.DSpaceObject; +import org.dspace.content.Item; +import org.dspace.content.service.BitstreamService; +import org.dspace.core.Constants; +import org.dspace.core.Context; +import org.dspace.utils.DSpace; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +/** + * Default implementation of {@link LinksetService}. + */ +@Service +public class LinksetServiceImpl implements LinksetService { + + private static final Logger log = Logger.getLogger(LinksetServiceImpl.class); + + @Autowired + private BitstreamService bitstreamService; + + @Autowired + private BitstreamMetadataReadPermissionEvaluatorPlugin bitstreamMetadataReadPermissionEvaluatorPlugin; + + private final List bitstreamProcessors = new DSpace().getServiceManager() + .getServicesByType(BitstreamSignpostingProcessor.class); + + private final List itemProcessors = new DSpace().getServiceManager() + .getServicesByType(ItemSignpostingProcessor.class); + + private final List metadataProcessors = new DSpace().getServiceManager() + .getServicesByType(MetadataSignpostingProcessor.class); + + @Override + public List> createLinksetNodesForMultipleLinksets( + HttpServletRequest request, + Context context, + Item item + ) { + ArrayList> linksets = new ArrayList<>(); + addItemLinksets(request, context, item, linksets); + addBitstreamLinksets(request, context, item, linksets); + addMetadataLinksets(request, context, item, linksets); + return linksets; + } + + @Override + public List createLinksetNodesForSingleLinkset( + HttpServletRequest request, + Context context, + DSpaceObject object + ) { + List linksetNodes = new ArrayList<>(); + if (object.getType() == Constants.ITEM) { + for (ItemSignpostingProcessor processor : itemProcessors) { + processor.addLinkSetNodes(context, request, (Item) object, linksetNodes); + } + } else if (object.getType() == Constants.BITSTREAM) { + for (BitstreamSignpostingProcessor processor : bitstreamProcessors) { + processor.addLinkSetNodes(context, request, (Bitstream) object, linksetNodes); + } + } + return linksetNodes; + } + + private void addItemLinksets( + HttpServletRequest request, + Context context, + Item item, + List> linksets + ) { + List linksetNodes = new ArrayList<>(); + if (item.getType() == Constants.ITEM) { + for (ItemSignpostingProcessor sp : itemProcessors) { + sp.addLinkSetNodes(context, request, item, linksetNodes); + } + } + linksets.add(linksetNodes); + } + + private void addBitstreamLinksets( + HttpServletRequest request, + Context context, + Item item, + ArrayList> linksets + ) { + Iterator bitstreamsIterator = getItemBitstreams(context, item); + bitstreamsIterator.forEachRemaining(bitstream -> { + try { + boolean isAuthorized = bitstreamMetadataReadPermissionEvaluatorPlugin + .metadataReadPermissionOnBitstream(context, bitstream); + if (isAuthorized) { + List bitstreamLinkset = new ArrayList<>(); + for (BitstreamSignpostingProcessor processor : bitstreamProcessors) { + processor.addLinkSetNodes(context, request, bitstream, bitstreamLinkset); + } + if (!bitstreamLinkset.isEmpty()) { + linksets.add(bitstreamLinkset); + } + } + } catch (SQLException e) { + log.error(e.getMessage(), e); + } + }); + } + + private void addMetadataLinksets( + HttpServletRequest request, + Context context, + Item item, + ArrayList> linksets + ) { + for (MetadataSignpostingProcessor processor : metadataProcessors) { + List metadataLinkset = new ArrayList<>(); + processor.addLinkSetNodes(context, request, item, metadataLinkset); + if (!metadataLinkset.isEmpty()) { + linksets.add(metadataLinkset); + } + } + } + + private Iterator getItemBitstreams(Context context, Item item) { + try { + return bitstreamService.getItemBitstreams(context, item); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/signposting/controller/LinksetRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/signposting/controller/LinksetRestControllerIT.java index ed3e63dde570..d348a7d3cd51 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/signposting/controller/LinksetRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/signposting/controller/LinksetRestControllerIT.java @@ -14,7 +14,10 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import java.io.InputStream; +import java.text.DateFormat; import java.text.MessageFormat; +import java.text.SimpleDateFormat; +import java.util.Date; import org.apache.commons.codec.CharEncoding; import org.apache.commons.io.IOUtils; @@ -49,6 +52,7 @@ import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.web.servlet.result.MockMvcResultMatchers; public class LinksetRestControllerIT extends AbstractControllerIntegrationTest { @@ -105,6 +109,7 @@ public void findAllItemsLinksets() throws Exception { public void findOneItemJsonLinksets() throws Exception { String url = configurationService.getProperty("dspace.ui.url"); String signpostingUrl = configurationService.getProperty("signposting.path"); + String mimeType = configurationService.getProperty("signposting.describedby.mime-type"); context.turnOffAuthorisationSystem(); Item item = ItemBuilder.createItem(context, collection) .withTitle("Item Test") @@ -115,17 +120,13 @@ public void findOneItemJsonLinksets() throws Exception { getClient().perform(get("/signposting/linksets/" + item.getID() + "/json")) .andExpect(status().isOk()) .andExpect(jsonPath("$.linkset", - Matchers.hasSize(3))) + Matchers.hasSize(2))) .andExpect(jsonPath("$.linkset[0].cite-as[0].href", Matchers.hasToString(url + "/handle/" + item.getHandle()))) .andExpect(jsonPath("$.linkset[0].describedby[0].href", - Matchers.hasToString(MessageFormat.format(doiPattern, doi)))) + Matchers.hasToString(url + "/" + signpostingUrl + "/describedby/" + item.getID()))) .andExpect(jsonPath("$.linkset[0].describedby[0].type", - Matchers.hasToString("application/vnd.datacite.datacite+json"))) - .andExpect(jsonPath("$.linkset[0].describedby[1].href", - Matchers.hasToString(url + "/handle/" + item.getHandle()))) - .andExpect(jsonPath("$.linkset[0].describedby[1].type", - Matchers.hasToString("text/html"))) + Matchers.hasToString(mimeType))) .andExpect(jsonPath("$.linkset[0].linkset[0].href", Matchers.hasToString(url + "/" + signpostingUrl + "/linksets/" + item.getID().toString()))) .andExpect(jsonPath("$.linkset[0].linkset[0].type", @@ -140,20 +141,14 @@ public void findOneItemJsonLinksets() throws Exception { .andExpect(jsonPath("$.linkset[1].describes[0].type", Matchers.hasToString("text/html"))) .andExpect(jsonPath("$.linkset[1].anchor", - Matchers.hasToString("http://localhost:4000/handle/" + item.getHandle()))) - .andExpect(jsonPath("$.linkset[2].describes[0].href", - Matchers.hasToString(url + "/entities/publication/" + item.getID()))) - .andExpect(jsonPath("$.linkset[2].describes[0].type", - Matchers.hasToString("text/html"))) - .andExpect(jsonPath("$.linkset[2].anchor", - Matchers.hasToString(MessageFormat.format(doiPattern, doi)))) - ; + Matchers.hasToString(url + "/" + signpostingUrl + "/describedby/" + item.getID()))); } @Test public void findOneItemJsonLinksetsWithType() throws Exception { String url = configurationService.getProperty("dspace.ui.url"); String signpostingUrl = configurationService.getProperty("signposting.path"); + String mimeType = configurationService.getProperty("signposting.describedby.mime-type"); String articleUri = mapConverterDSpaceToSchemaOrgUri.getValue("Article"); context.turnOffAuthorisationSystem(); Item item = ItemBuilder.createItem(context, collection) @@ -166,17 +161,13 @@ public void findOneItemJsonLinksetsWithType() throws Exception { getClient().perform(get("/signposting/linksets/" + item.getID() + "/json")) .andExpect(status().isOk()) .andExpect(jsonPath("$.linkset", - Matchers.hasSize(3))) + Matchers.hasSize(2))) .andExpect(jsonPath("$.linkset[0].cite-as[0].href", Matchers.hasToString(url + "/handle/" + item.getHandle()))) .andExpect(jsonPath("$.linkset[0].describedby[0].href", - Matchers.hasToString(MessageFormat.format(doiPattern, doi)))) + Matchers.hasToString(url + "/" + signpostingUrl + "/describedby/" + item.getID()))) .andExpect(jsonPath("$.linkset[0].describedby[0].type", - Matchers.hasToString("application/vnd.datacite.datacite+json"))) - .andExpect(jsonPath("$.linkset[0].describedby[1].href", - Matchers.hasToString(url + "/handle/" + item.getHandle()))) - .andExpect(jsonPath("$.linkset[0].describedby[1].type", - Matchers.hasToString("text/html"))) + Matchers.hasToString(mimeType))) .andExpect(jsonPath("$.linkset[0].type", Matchers.hasSize(2))) .andExpect(jsonPath("$.linkset[0].type[0].href", @@ -197,13 +188,7 @@ public void findOneItemJsonLinksetsWithType() throws Exception { .andExpect(jsonPath("$.linkset[1].describes[0].type", Matchers.hasToString("text/html"))) .andExpect(jsonPath("$.linkset[1].anchor", - Matchers.hasToString("http://localhost:4000/handle/" + item.getHandle()))) - .andExpect(jsonPath("$.linkset[2].describes[0].href", - Matchers.hasToString(url + "/entities/publication/" + item.getID()))) - .andExpect(jsonPath("$.linkset[2].describes[0].type", - Matchers.hasToString("text/html"))) - .andExpect(jsonPath("$.linkset[2].anchor", - Matchers.hasToString(MessageFormat.format(doiPattern, doi)))); + Matchers.hasToString(url + "/" + signpostingUrl + "/describedby/" + item.getID()))); } @Test @@ -240,7 +225,7 @@ public void findOneItemJsonLinksetsWithLicence() throws Exception { .andExpect(jsonPath("$.linkset[1].describes[0].type", Matchers.hasToString("text/html"))) .andExpect(jsonPath("$.linkset[1].anchor", - Matchers.hasToString("http://localhost:4000/handle/" + item.getHandle()))); + Matchers.hasToString(url + "/" + signpostingUrl + "/describedby/" + item.getID()))); } @Test @@ -275,20 +260,17 @@ public void findOneItemJsonLinksetsWithBitstreams() throws Exception { String url = configurationService.getProperty("dspace.ui.url"); String signpostingUrl = configurationService.getProperty("signposting.path"); + String mimeType = configurationService.getProperty("signposting.describedby.mime-type"); getClient().perform(get("/signposting/linksets/" + item.getID() + "/json")) .andExpect(status().isOk()) .andExpect(jsonPath("$.linkset", - Matchers.hasSize(5))) + Matchers.hasSize(4))) .andExpect(jsonPath("$.linkset[0].cite-as[0].href", Matchers.hasToString(url + "/handle/" + item.getHandle()))) .andExpect(jsonPath("$.linkset[0].describedby[0].href", - Matchers.hasToString(MessageFormat.format(doiPattern, doi)))) + Matchers.hasToString(url + "/" + signpostingUrl + "/describedby/" + item.getID()))) .andExpect(jsonPath("$.linkset[0].describedby[0].type", - Matchers.hasToString("application/vnd.datacite.datacite+json"))) - .andExpect(jsonPath("$.linkset[0].describedby[1].href", - Matchers.hasToString(url + "/handle/" + item.getHandle()))) - .andExpect(jsonPath("$.linkset[0].describedby[1].type", - Matchers.hasToString("text/html"))) + Matchers.hasToString(mimeType))) .andExpect(jsonPath("$.linkset[0].item[0].href", Matchers.hasToString(url + "/bitstreams/" + bitstream1.getID() + "/download"))) .andExpect(jsonPath("$.linkset[0].item[0].type", @@ -343,13 +325,7 @@ public void findOneItemJsonLinksetsWithBitstreams() throws Exception { .andExpect(jsonPath("$.linkset[3].describes[0].type", Matchers.hasToString("text/html"))) .andExpect(jsonPath("$.linkset[3].anchor", - Matchers.hasToString(url + "/handle/" + item.getHandle()))) - .andExpect(jsonPath("$.linkset[4].describes[0].href", - Matchers.hasToString(url + "/entities/publication/" + item.getID()))) - .andExpect(jsonPath("$.linkset[4].describes[0].type", - Matchers.hasToString("text/html"))) - .andExpect(jsonPath("$.linkset[4].anchor", - Matchers.hasToString(MessageFormat.format(doiPattern, doi)))); + Matchers.hasToString(url + "/" + signpostingUrl + "/describedby/" + item.getID()))); } @Test @@ -483,6 +459,7 @@ public void findOneItemLsetLinksets() throws Exception { String url = configurationService.getProperty("dspace.ui.url"); String signpostingUrl = configurationService.getProperty("signposting.path"); + String mimeType = configurationService.getProperty("signposting.describedby.mime-type"); String siteAsRelation = "<" + url + "/handle/" + item.getHandle() + "> ; rel=\"cite-as\" ; anchor=\"" + url + "/entities/publication/" + item.getID() + "\" ,"; String itemRelation = "<" + url + "/bitstreams/" + bitstream1.getID() + @@ -496,8 +473,9 @@ public void findOneItemLsetLinksets() throws Exception { String jsonLinksetRelation = "<" + url + "/" + signpostingUrl + "/linksets/" + item.getID().toString() + "/json> ; rel=\"linkset\" ; type=\"application/linkset+json\" ;" + " anchor=\"" + url + "/entities/publication/" + item.getID() + "\" ,"; - String describedByRelation = "<" + url + "/handle/" + item.getHandle() + "> ; rel=\"describedby\" ;" + - " type=\"text/html\" ; anchor=\"" + url + "/entities/publication/" + item.getID() + "\" ,"; + String describedByRelation = "<" + url + "/" + signpostingUrl + "/describedby/" + item.getID() + + "> ; rel=\"describedby\" ;" + " type=\"" + mimeType + "\" ; anchor=\"" + url + + "/entities/publication/" + item.getID() + "\" ,"; String bitstreamCollectionLink = "<" + url + "/entities/publication/" + item.getID() + "> ;" + " rel=\"collection\" ; type=\"text/html\" ; anchor=\"" + url + "/bitstreams/" @@ -511,7 +489,7 @@ public void findOneItemLsetLinksets() throws Exception { String describesMetadataLink = "<" + url + "/entities/publication/" + item.getID() + "> ; " + "rel=\"describes\" ; type=\"text/html\" ; " + - "anchor=\"" + url + "/handle/" + item.getHandle() + "\""; + "anchor=\"" + url + "/" + signpostingUrl + "/describedby/" + item.getID() + "\""; getClient().perform(get("/signposting/linksets/" + item.getID())) .andExpect(content().string(Matchers.containsString(siteAsRelation))) @@ -586,44 +564,36 @@ public void findTypedLinkForItemWithAuthor() throws Exception { String url = configurationService.getProperty("dspace.ui.url"); String signpostingUrl = configurationService.getProperty("signposting.path"); + String mimeType = configurationService.getProperty("signposting.describedby.mime-type"); String dcIdentifierUriMetadataValue = itemService .getMetadataFirstValue(publication, "dc", "identifier", "uri", Item.ANY); - String anchor = url + "/entities/publication/" + publication.getID(); getClient().perform(get("/signposting/links/" + publication.getID()) .header("Accept", "application/json")) .andExpect(status().isOk()) .andExpect(jsonPath("$", - Matchers.hasSize(8))) + Matchers.hasSize(7))) .andExpect(jsonPath("$[?(@.href == '" + MessageFormat.format(orcidPattern, orcidValue) + "' " + - "&& @.rel == 'author' " + - "&& @.anchor == '" + anchor + "')]").exists()) - .andExpect(jsonPath("$[?(@.href == '" + MessageFormat.format(doiPattern, doi) + "' " + - "&& @.rel == 'describedby' " + - "&& @.anchor == '" + anchor + "')]").exists()) - .andExpect(jsonPath("$[?(@.href == '" + dcIdentifierUriMetadataValue + "' " + + "&& @.rel == 'author')]").exists()) + .andExpect(jsonPath("$[?(@.href == '" + url + "/" + signpostingUrl + "/describedby/" + + publication.getID() + "' " + "&& @.rel == 'describedby' " + - "&& @.anchor == '" + anchor + "')]").exists()) + "&& @.type == '" + mimeType + "')]").exists()) .andExpect(jsonPath("$[?(@.href == '" + dcIdentifierUriMetadataValue + "' " + - "&& @.rel == 'cite-as' " + - "&& @.anchor == '" + anchor + "')]").exists()) + "&& @.rel == 'cite-as')]").exists()) .andExpect(jsonPath("$[?(@.href == '" + url + "/bitstreams/" + bitstream.getID() + "/download' " + "&& @.rel == 'item' " + - "&& @.type == 'text/plain' " + - "&& @.anchor == '" + anchor + "')]").exists()) + "&& @.type == 'text/plain')]").exists()) .andExpect(jsonPath("$[?(@.href == 'https://schema.org/AboutPage' " + - "&& @.rel == 'type' " + - "&& @.anchor == '" + anchor + "')]").exists()) + "&& @.rel == 'type')]").exists()) .andExpect(jsonPath("$[?(@.href == '" + url + "/" + signpostingUrl + "/linksets/" + publication.getID().toString() + "' " + "&& @.rel == 'linkset' " + - "&& @.type == 'application/linkset' " + - "&& @.anchor == '" + anchor + "')]").exists()) + "&& @.type == 'application/linkset')]").exists()) .andExpect(jsonPath("$[?(@.href == '" + url + "/" + signpostingUrl + "/linksets/" + publication.getID().toString() + "/json' " + "&& @.rel == 'linkset' " + - "&& @.type == 'application/linkset+json' " + - "&& @.anchor == '" + anchor + "')]").exists()); + "&& @.type == 'application/linkset+json')]").exists()); } @Test @@ -648,7 +618,6 @@ public void findTypedLinkForBitstream() throws Exception { context.restoreAuthSystemState(); String uiUrl = configurationService.getProperty("dspace.ui.url"); - String anchor = uiUrl + "/bitstreams/" + bitstream.getID() + "/download"; getClient().perform(get("/signposting/links/" + bitstream.getID()) .header("Accept", "application/json")) .andExpect(status().isOk()) @@ -656,16 +625,13 @@ public void findTypedLinkForBitstream() throws Exception { Matchers.hasSize(3))) .andExpect(jsonPath("$[?(@.href == '" + uiUrl + "/entities/publication/" + item.getID() + "' " + "&& @.rel == 'collection' " + - "&& @.type == 'text/html' " + - "&& @.anchor == '" + anchor + "')]").exists()) + "&& @.type == 'text/html')]").exists()) .andExpect(jsonPath("$[?(@.href == '" + uiUrl + "/signposting/linksets/" + item.getID() + "' " + "&& @.rel == 'linkset' " + - "&& @.type == 'application/linkset' " + - "&& @.anchor == '" + anchor + "')]").exists()) + "&& @.type == 'application/linkset')]").exists()) .andExpect(jsonPath("$[?(@.href == '" + uiUrl + "/signposting/linksets/" + item.getID() + "/json" + "' && @.rel == 'linkset' " + - "&& @.type == 'application/linkset+json' " + - "&& @.anchor == '" + anchor + "')]").exists()); + "&& @.type == 'application/linkset+json')]").exists()); DSpaceServicesFactory.getInstance().getConfigurationService().reloadConfig(); metadataAuthorityService.clearCache(); @@ -696,7 +662,6 @@ public void findTypedLinkForBitstreamWithType() throws Exception { context.restoreAuthSystemState(); String uiUrl = configurationService.getProperty("dspace.ui.url"); - String anchor = uiUrl + "/bitstreams/" + bitstream.getID() + "/download"; getClient().perform(get("/signposting/links/" + bitstream.getID()) .header("Accept", "application/json")) .andExpect(status().isOk()) @@ -704,19 +669,15 @@ public void findTypedLinkForBitstreamWithType() throws Exception { Matchers.hasSize(4))) .andExpect(jsonPath("$[?(@.href == '" + uiUrl + "/entities/publication/" + item.getID() + "' " + "&& @.rel == 'collection' " + - "&& @.type == 'text/html' " + - "&& @.anchor == '" + anchor + "')]").exists()) + "&& @.type == 'text/html')]").exists()) .andExpect(jsonPath("$[?(@.href == '" + uiUrl + "/signposting/linksets/" + item.getID() + "' " + "&& @.rel == 'linkset' " + - "&& @.type == 'application/linkset' " + - "&& @.anchor == '" + anchor + "')]").exists()) + "&& @.type == 'application/linkset')]").exists()) .andExpect(jsonPath("$[?(@.href == '" + uiUrl + "/signposting/linksets/" + item.getID() + "/json" + "' && @.rel == 'linkset' " + - "&& @.type == 'application/linkset+json' " + - "&& @.anchor == '" + anchor + "')]").exists()) + "&& @.type == 'application/linkset+json')]").exists()) .andExpect(jsonPath("$[?(@.href == 'https://schema.org/ScholarlyArticle' " + - "&& @.rel == 'type' " + - "&& @.anchor == '" + anchor + "')]").exists()); + "&& @.rel == 'type')]").exists()); DSpaceServicesFactory.getInstance().getConfigurationService().reloadConfig(); metadataAuthorityService.clearCache(); @@ -837,4 +798,111 @@ public void findTypedLinkForUnDiscoverableItem() throws Exception { choiceAuthorityService.clearCache(); } + @Test + public void getDescribedBy() throws Exception { + context.turnOffAuthorisationSystem(); + DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); + String currentDateInFormat = dateFormat.format(new Date()); + String title = "Item Test"; + Item item = ItemBuilder.createItem(context, collection) + .withTitle(title) + .withMetadata("dc", "identifier", "doi", doi) + .build(); + String responseMimeType = configurationService.getProperty("signposting.describedby.mime-type"); + context.restoreAuthSystemState(); + + String titleXml = "" + title + ""; + String doiXml = "" + doi + ""; + String handleXml = "http://localhost:4000/handle/" + item.getHandle() + + ""; + String acceptedDateXml = "" + + currentDateInFormat + ""; + String availableDateXml = "" + + currentDateInFormat + ""; + + getClient().perform(get("/signposting/describedby/" + item.getID())) + .andExpect(status().isOk()) + .andExpect(content().string(Matchers.containsString(titleXml))) + .andExpect(content().string(Matchers.containsString(doiXml))) + .andExpect(content().string(Matchers.containsString(handleXml))) + .andExpect(content().string(Matchers.containsString(acceptedDateXml))) + .andExpect(content().string(Matchers.containsString(availableDateXml))) + .andExpect(MockMvcResultMatchers.header() + .stringValues("Content-Type", responseMimeType + ";charset=UTF-8")); + } + + @Test + public void getDescribedByItemThatIsInWorkspace() throws Exception { + context.turnOffAuthorisationSystem(); + WorkspaceItem workspaceItem = WorkspaceItemBuilder.createWorkspaceItem(context, collection) + .withTitle("Workspace Item") + .build(); + itemService.addMetadata(context, workspaceItem.getItem(), "dc", "identifier", "doi", Item.ANY, doi); + context.restoreAuthSystemState(); + + getClient().perform(get("/signposting/describedby/" + workspaceItem.getItem().getID())) + .andExpect(status().isUnauthorized()); + } + + @Test + public void getDescribedByWithdrawnItem() throws Exception { + context.turnOffAuthorisationSystem(); + Item item = ItemBuilder.createItem(context, collection) + .withTitle("Withdrawn Item") + .withMetadata("dc", "identifier", "doi", doi) + .withdrawn() + .build(); + context.restoreAuthSystemState(); + + getClient().perform(get("/signposting/describedby/" + item.getID())) + .andExpect(status().isUnauthorized()); + } + + @Test + public void getDescribedByEmbargoItem() throws Exception { + context.turnOffAuthorisationSystem(); + Item item = ItemBuilder.createItem(context, collection) + .withTitle("Withdrawn Item") + .withMetadata("dc", "identifier", "doi", doi) + .withIssueDate("2017-11-18") + .withEmbargoPeriod("2 week") + .build(); + context.restoreAuthSystemState(); + + getClient().perform(get("/signposting/describedby/" + item.getID())) + .andExpect(status().isUnauthorized()); + } + + @Test + public void getDescribedByRestrictedItem() throws Exception { + context.turnOffAuthorisationSystem(); + Group internalGroup = GroupBuilder.createGroup(context) + .withName("Internal Group") + .build(); + Item item = ItemBuilder.createItem(context, collection) + .withTitle("Withdrawn Item") + .withMetadata("dc", "identifier", "doi", doi) + .withReaderGroup(internalGroup) + .build(); + context.restoreAuthSystemState(); + + getClient().perform(get("/signposting/describedby/" + item.getID())) + .andExpect(status().isUnauthorized()); + } + + @Test + public void getDescribedByUnDiscoverableItem() throws Exception { + context.turnOffAuthorisationSystem(); + Item item = ItemBuilder.createItem(context, collection) + .withTitle("Withdrawn Item") + .withMetadata("dc", "identifier", "doi", doi) + .makeUnDiscoverable() + .build(); + context.restoreAuthSystemState(); + + getClient().perform(get("/signposting/describedby/" + item.getID())) + .andExpect(status().isUnauthorized()); + } } diff --git a/dspace/config/modules/signposting.cfg b/dspace/config/modules/signposting.cfg index 2265294981a2..fba80da41481 100644 --- a/dspace/config/modules/signposting.cfg +++ b/dspace/config/modules/signposting.cfg @@ -26,4 +26,10 @@ signposting.path = signposting # When "true", the signposting controller is accessible on ${signposting.path} # When "false" or commented out, signposting is disabled/inaccessible. # (Requires reboot of servlet container, e.g. Tomcat, to reload) -signposting.enabled = true \ No newline at end of file +signposting.enabled = true + +# Name of crosswalk to use for handling of 'describedby' links. +signposting.describedby.crosswalk-name = DataCite + +# Mime-type of response of handling of 'describedby' links. +signposting.describedby.mime-type = application/vnd.datacite.datacite+xml \ No newline at end of file diff --git a/dspace/config/spring/rest/signposting.xml b/dspace/config/spring/rest/signposting.xml index 51060590d280..72109dbe26e5 100644 --- a/dspace/config/spring/rest/signposting.xml +++ b/dspace/config/spring/rest/signposting.xml @@ -1,7 +1,6 @@ + xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> @@ -23,20 +22,7 @@ - - - - - - - - - - - - - + class="org.dspace.app.rest.signposting.processor.item.ItemDescribedbyProcessor"/> @@ -59,14 +45,6 @@ - - - - - - - - + From 12fe87fc372f76e11f5b9f7c1c6c4ac7d96e2a6e Mon Sep 17 00:00:00 2001 From: Nikita Krivonosov Date: Fri, 23 Jun 2023 09:46:46 +0200 Subject: [PATCH 276/510] [DURACOM-156] Singposting: feedback after initial deployment --- .../controller/LinksetRestControllerIT.java | 25 +++++-------------- 1 file changed, 6 insertions(+), 19 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/signposting/controller/LinksetRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/signposting/controller/LinksetRestControllerIT.java index d348a7d3cd51..812a1caa33c7 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/signposting/controller/LinksetRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/signposting/controller/LinksetRestControllerIT.java @@ -109,7 +109,7 @@ public void findAllItemsLinksets() throws Exception { public void findOneItemJsonLinksets() throws Exception { String url = configurationService.getProperty("dspace.ui.url"); String signpostingUrl = configurationService.getProperty("signposting.path"); - String mimeType = configurationService.getProperty("signposting.describedby.mime-type"); + String mimeType = "application/vnd.datacite.datacite+xml"; context.turnOffAuthorisationSystem(); Item item = ItemBuilder.createItem(context, collection) .withTitle("Item Test") @@ -148,7 +148,7 @@ public void findOneItemJsonLinksets() throws Exception { public void findOneItemJsonLinksetsWithType() throws Exception { String url = configurationService.getProperty("dspace.ui.url"); String signpostingUrl = configurationService.getProperty("signposting.path"); - String mimeType = configurationService.getProperty("signposting.describedby.mime-type"); + String mimeType = "application/vnd.datacite.datacite+xml"; String articleUri = mapConverterDSpaceToSchemaOrgUri.getValue("Article"); context.turnOffAuthorisationSystem(); Item item = ItemBuilder.createItem(context, collection) @@ -260,7 +260,7 @@ public void findOneItemJsonLinksetsWithBitstreams() throws Exception { String url = configurationService.getProperty("dspace.ui.url"); String signpostingUrl = configurationService.getProperty("signposting.path"); - String mimeType = configurationService.getProperty("signposting.describedby.mime-type"); + String mimeType = "application/vnd.datacite.datacite+xml"; getClient().perform(get("/signposting/linksets/" + item.getID() + "/json")) .andExpect(status().isOk()) .andExpect(jsonPath("$.linkset", @@ -459,7 +459,7 @@ public void findOneItemLsetLinksets() throws Exception { String url = configurationService.getProperty("dspace.ui.url"); String signpostingUrl = configurationService.getProperty("signposting.path"); - String mimeType = configurationService.getProperty("signposting.describedby.mime-type"); + String mimeType = "application/vnd.datacite.datacite+xml"; String siteAsRelation = "<" + url + "/handle/" + item.getHandle() + "> ; rel=\"cite-as\" ; anchor=\"" + url + "/entities/publication/" + item.getID() + "\" ,"; String itemRelation = "<" + url + "/bitstreams/" + bitstream1.getID() + @@ -564,7 +564,7 @@ public void findTypedLinkForItemWithAuthor() throws Exception { String url = configurationService.getProperty("dspace.ui.url"); String signpostingUrl = configurationService.getProperty("signposting.path"); - String mimeType = configurationService.getProperty("signposting.describedby.mime-type"); + String mimeType = "application/vnd.datacite.datacite+xml"; String dcIdentifierUriMetadataValue = itemService .getMetadataFirstValue(publication, "dc", "identifier", "uri", Item.ANY); @@ -808,27 +808,14 @@ public void getDescribedBy() throws Exception { .withTitle(title) .withMetadata("dc", "identifier", "doi", doi) .build(); - String responseMimeType = configurationService.getProperty("signposting.describedby.mime-type"); + String responseMimeType = "application/vnd.datacite.datacite+xml"; context.restoreAuthSystemState(); String titleXml = "" + title + ""; - String doiXml = "" + doi + ""; - String handleXml = "http://localhost:4000/handle/" + item.getHandle() + - ""; - String acceptedDateXml = "" + - currentDateInFormat + ""; - String availableDateXml = "" + - currentDateInFormat + ""; getClient().perform(get("/signposting/describedby/" + item.getID())) .andExpect(status().isOk()) .andExpect(content().string(Matchers.containsString(titleXml))) - .andExpect(content().string(Matchers.containsString(doiXml))) - .andExpect(content().string(Matchers.containsString(handleXml))) - .andExpect(content().string(Matchers.containsString(acceptedDateXml))) - .andExpect(content().string(Matchers.containsString(availableDateXml))) .andExpect(MockMvcResultMatchers.header() .stringValues("Content-Type", responseMimeType + ";charset=UTF-8")); } From 890f2a15e52eaaefe75273bff266448518edbc40 Mon Sep 17 00:00:00 2001 From: Nikita Krivonosov Date: Fri, 23 Jun 2023 11:45:07 +0200 Subject: [PATCH 277/510] [DURACOM-156] Singposting: feedback after initial deployment --- .../controller/LinksetRestController.java | 13 ++++-- .../controller/LinksetRestControllerIT.java | 45 +++++++++---------- 2 files changed, 30 insertions(+), 28 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/controller/LinksetRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/controller/LinksetRestController.java index 98642bb703ed..2a940d79aba4 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/controller/LinksetRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/controller/LinksetRestController.java @@ -88,8 +88,11 @@ public ResponseEntity getAll() { } @PreAuthorize("hasPermission(#uuid, 'ITEM', 'READ')") - @RequestMapping(value = "/linksets" + REGEX_REQUESTMAPPING_IDENTIFIER_AS_UUID + "/json", - method = RequestMethod.GET) + @RequestMapping( + value = "/linksets" + REGEX_REQUESTMAPPING_IDENTIFIER_AS_UUID + "/json", + method = RequestMethod.GET, + produces = "application/linkset+json" + ) public LinksetRest getJson(HttpServletRequest request, @PathVariable UUID uuid) { try { Context context = ContextUtil.obtainContext(request); @@ -109,7 +112,11 @@ public LinksetRest getJson(HttpServletRequest request, @PathVariable UUID uuid) } @PreAuthorize("hasPermission(#uuid, 'ITEM', 'READ')") - @RequestMapping(value = "/linksets" + REGEX_REQUESTMAPPING_IDENTIFIER_AS_UUID, method = RequestMethod.GET) + @RequestMapping( + value = "/linksets" + REGEX_REQUESTMAPPING_IDENTIFIER_AS_UUID, + method = RequestMethod.GET, + produces = "application/linkset" + ) public String getLset(HttpServletRequest request, @PathVariable UUID uuid) { try { Context context = ContextUtil.obtainContext(request); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/signposting/controller/LinksetRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/signposting/controller/LinksetRestControllerIT.java index 812a1caa33c7..851752793410 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/signposting/controller/LinksetRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/signposting/controller/LinksetRestControllerIT.java @@ -10,6 +10,7 @@ import static org.dspace.content.MetadataSchemaEnum.PERSON; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -52,7 +53,6 @@ import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.test.web.servlet.result.MockMvcResultMatchers; public class LinksetRestControllerIT extends AbstractControllerIntegrationTest { @@ -141,7 +141,8 @@ public void findOneItemJsonLinksets() throws Exception { .andExpect(jsonPath("$.linkset[1].describes[0].type", Matchers.hasToString("text/html"))) .andExpect(jsonPath("$.linkset[1].anchor", - Matchers.hasToString(url + "/" + signpostingUrl + "/describedby/" + item.getID()))); + Matchers.hasToString(url + "/" + signpostingUrl + "/describedby/" + item.getID()))) + .andExpect(header().stringValues("Content-Type", "application/linkset+json;charset=UTF-8")); } @Test @@ -188,7 +189,8 @@ public void findOneItemJsonLinksetsWithType() throws Exception { .andExpect(jsonPath("$.linkset[1].describes[0].type", Matchers.hasToString("text/html"))) .andExpect(jsonPath("$.linkset[1].anchor", - Matchers.hasToString(url + "/" + signpostingUrl + "/describedby/" + item.getID()))); + Matchers.hasToString(url + "/" + signpostingUrl + "/describedby/" + item.getID()))) + .andExpect(header().stringValues("Content-Type", "application/linkset+json;charset=UTF-8")); } @Test @@ -225,7 +227,8 @@ public void findOneItemJsonLinksetsWithLicence() throws Exception { .andExpect(jsonPath("$.linkset[1].describes[0].type", Matchers.hasToString("text/html"))) .andExpect(jsonPath("$.linkset[1].anchor", - Matchers.hasToString(url + "/" + signpostingUrl + "/describedby/" + item.getID()))); + Matchers.hasToString(url + "/" + signpostingUrl + "/describedby/" + item.getID()))) + .andExpect(header().stringValues("Content-Type", "application/linkset+json;charset=UTF-8")); } @Test @@ -325,7 +328,8 @@ public void findOneItemJsonLinksetsWithBitstreams() throws Exception { .andExpect(jsonPath("$.linkset[3].describes[0].type", Matchers.hasToString("text/html"))) .andExpect(jsonPath("$.linkset[3].anchor", - Matchers.hasToString(url + "/" + signpostingUrl + "/describedby/" + item.getID()))); + Matchers.hasToString(url + "/" + signpostingUrl + "/describedby/" + item.getID()))) + .andExpect(header().stringValues("Content-Type", "application/linkset+json;charset=UTF-8")); } @Test @@ -501,7 +505,8 @@ public void findOneItemLsetLinksets() throws Exception { .andExpect(content().string(Matchers.containsString(bitstreamCollectionLink))) .andExpect(content().string(Matchers.containsString(bitstreamLinksetLink))) .andExpect(content().string(Matchers.containsString(bitstreamLinksetJsonLink))) - .andExpect(content().string(Matchers.containsString(describesMetadataLink))); + .andExpect(content().string(Matchers.containsString(describesMetadataLink))) + .andExpect(header().stringValues("Content-Type", "application/linkset;charset=UTF-8")); } @Test @@ -568,8 +573,7 @@ public void findTypedLinkForItemWithAuthor() throws Exception { String dcIdentifierUriMetadataValue = itemService .getMetadataFirstValue(publication, "dc", "identifier", "uri", Item.ANY); - getClient().perform(get("/signposting/links/" + publication.getID()) - .header("Accept", "application/json")) + getClient().perform(get("/signposting/links/" + publication.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$", Matchers.hasSize(7))) @@ -618,8 +622,7 @@ public void findTypedLinkForBitstream() throws Exception { context.restoreAuthSystemState(); String uiUrl = configurationService.getProperty("dspace.ui.url"); - getClient().perform(get("/signposting/links/" + bitstream.getID()) - .header("Accept", "application/json")) + getClient().perform(get("/signposting/links/" + bitstream.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$", Matchers.hasSize(3))) @@ -662,8 +665,7 @@ public void findTypedLinkForBitstreamWithType() throws Exception { context.restoreAuthSystemState(); String uiUrl = configurationService.getProperty("dspace.ui.url"); - getClient().perform(get("/signposting/links/" + bitstream.getID()) - .header("Accept", "application/json")) + getClient().perform(get("/signposting/links/" + bitstream.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$", Matchers.hasSize(4))) @@ -709,8 +711,7 @@ public void findTypedLinkForRestrictedBitstream() throws Exception { } context.restoreAuthSystemState(); - getClient().perform(get("/signposting/links/" + bitstream.getID()) - .header("Accept", "application/json")) + getClient().perform(get("/signposting/links/" + bitstream.getID())) .andExpect(status().isUnauthorized()); DSpaceServicesFactory.getInstance().getConfigurationService().reloadConfig(); @@ -741,8 +742,7 @@ public void findTypedLinkForBitstreamUnderEmbargo() throws Exception { } context.restoreAuthSystemState(); - getClient().perform(get("/signposting/links/" + bitstream.getID()) - .header("Accept", "application/json")) + getClient().perform(get("/signposting/links/" + bitstream.getID())) .andExpect(status().isUnauthorized()); DSpaceServicesFactory.getInstance().getConfigurationService().reloadConfig(); @@ -772,8 +772,7 @@ public void findTypedLinkForBitstreamOfWorkspaceItem() throws Exception { } context.restoreAuthSystemState(); - getClient().perform(get("/signposting/links/" + bitstream.getID()) - .header("Accept", "application/json")) + getClient().perform(get("/signposting/links/" + bitstream.getID())) .andExpect(status().isUnauthorized()); DSpaceServicesFactory.getInstance().getConfigurationService().reloadConfig(); @@ -789,8 +788,7 @@ public void findTypedLinkForUnDiscoverableItem() throws Exception { .build(); context.restoreAuthSystemState(); - getClient().perform(get("/signposting/links/" + item.getID()) - .header("Accept", "application/json")) + getClient().perform(get("/signposting/links/" + item.getID())) .andExpect(status().isUnauthorized()); DSpaceServicesFactory.getInstance().getConfigurationService().reloadConfig(); @@ -811,13 +809,10 @@ public void getDescribedBy() throws Exception { String responseMimeType = "application/vnd.datacite.datacite+xml"; context.restoreAuthSystemState(); - String titleXml = "" + title + ""; - getClient().perform(get("/signposting/describedby/" + item.getID())) .andExpect(status().isOk()) - .andExpect(content().string(Matchers.containsString(titleXml))) - .andExpect(MockMvcResultMatchers.header() - .stringValues("Content-Type", responseMimeType + ";charset=UTF-8")); + .andExpect(content().string(Matchers.containsString(title))) + .andExpect(header().stringValues("Content-Type", responseMimeType + ";charset=UTF-8")); } @Test From df4cff7dd1d79ef1e428b784329034576542ee1f Mon Sep 17 00:00:00 2001 From: Nikita Krivonosov Date: Fri, 23 Jun 2023 17:24:42 +0200 Subject: [PATCH 278/510] [DURACOM-156] Singposting: feedback after initial deployment --- .../service/impl/LinksetServiceImpl.java | 8 +- .../controller/LinksetRestControllerIT.java | 100 ++++++++++++++++++ 2 files changed, 105 insertions(+), 3 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/service/impl/LinksetServiceImpl.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/service/impl/LinksetServiceImpl.java index e7fa25de93bb..399b7bd1e6b0 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/service/impl/LinksetServiceImpl.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/service/impl/LinksetServiceImpl.java @@ -21,9 +21,10 @@ import org.dspace.app.rest.signposting.processor.metadata.MetadataSignpostingProcessor; import org.dspace.app.rest.signposting.service.LinksetService; import org.dspace.content.Bitstream; +import org.dspace.content.Bundle; import org.dspace.content.DSpaceObject; import org.dspace.content.Item; -import org.dspace.content.service.BitstreamService; +import org.dspace.content.service.ItemService; import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.utils.DSpace; @@ -39,7 +40,7 @@ public class LinksetServiceImpl implements LinksetService { private static final Logger log = Logger.getLogger(LinksetServiceImpl.class); @Autowired - private BitstreamService bitstreamService; + protected ItemService itemService; @Autowired private BitstreamMetadataReadPermissionEvaluatorPlugin bitstreamMetadataReadPermissionEvaluatorPlugin; @@ -143,7 +144,8 @@ private void addMetadataLinksets( private Iterator getItemBitstreams(Context context, Item item) { try { - return bitstreamService.getItemBitstreams(context, item); + List bundles = itemService.getBundles(item, Constants.DEFAULT_BUNDLE_NAME); + return bundles.stream().flatMap(bundle -> bundle.getBitstreams().stream()).iterator(); } catch (SQLException e) { throw new RuntimeException(e); } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/signposting/controller/LinksetRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/signposting/controller/LinksetRestControllerIT.java index 851752793410..6d1d242cad7f 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/signposting/controller/LinksetRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/signposting/controller/LinksetRestControllerIT.java @@ -45,6 +45,7 @@ import org.dspace.content.service.BitstreamService; import org.dspace.content.service.ItemService; import org.dspace.content.service.RelationshipTypeService; +import org.dspace.core.Constants; import org.dspace.eperson.Group; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; @@ -332,6 +333,105 @@ public void findOneItemJsonLinksetsWithBitstreams() throws Exception { .andExpect(header().stringValues("Content-Type", "application/linkset+json;charset=UTF-8")); } + @Test + public void findOneItemJsonLinksetsWithBitstreamsFromDifferentBundles() throws Exception { + String bitstream1Content = "ThisIsSomeDummyText"; + String bitstream1MimeType = "text/plain"; + + context.turnOffAuthorisationSystem(); + Item item = ItemBuilder.createItem(context, collection) + .withTitle("Item Test") + .withMetadata("dc", "identifier", "doi", doi) + .build(); + Bitstream bitstream1 = null; + try (InputStream is = IOUtils.toInputStream(bitstream1Content, CharEncoding.UTF_8)) { + bitstream1 = BitstreamBuilder.createBitstream(context, item, is, Constants.DEFAULT_BUNDLE_NAME) + .withName("Bitstream 1") + .withDescription("description") + .withMimeType(bitstream1MimeType) + .build(); + } + + try (InputStream is = IOUtils.toInputStream("test", CharEncoding.UTF_8)) { + Bitstream bitstream2 = BitstreamBuilder.createBitstream(context, item, is, "TEXT") + .withName("Bitstream 2") + .withDescription("description") + .withMimeType("application/pdf") + .build(); + } + + try (InputStream is = IOUtils.toInputStream("test", CharEncoding.UTF_8)) { + Bitstream bitstream3 = BitstreamBuilder.createBitstream(context, item, is, "THUMBNAIL") + .withName("Bitstream 3") + .withDescription("description") + .withMimeType("application/pdf") + .build(); + } + + try (InputStream is = IOUtils.toInputStream("test", CharEncoding.UTF_8)) { + Bitstream bitstream4 = BitstreamBuilder.createBitstream(context, item, is, "LICENSE") + .withName("Bitstream 4") + .withDescription("description") + .withMimeType("application/pdf") + .build(); + } + + context.restoreAuthSystemState(); + + String url = configurationService.getProperty("dspace.ui.url"); + String signpostingUrl = configurationService.getProperty("signposting.path"); + String mimeType = "application/vnd.datacite.datacite+xml"; + getClient().perform(get("/signposting/linksets/" + item.getID() + "/json")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.linkset", + Matchers.hasSize(3))) + .andExpect(jsonPath("$.linkset[0].cite-as[0].href", + Matchers.hasToString(url + "/handle/" + item.getHandle()))) + .andExpect(jsonPath("$.linkset[0].describedby[0].href", + Matchers.hasToString(url + "/" + signpostingUrl + "/describedby/" + item.getID()))) + .andExpect(jsonPath("$.linkset[0].describedby[0].type", + Matchers.hasToString(mimeType))) + .andExpect(jsonPath("$.linkset[0].item", + Matchers.hasSize(1))) + .andExpect(jsonPath("$.linkset[0].item[0].href", + Matchers.hasToString(url + "/bitstreams/" + bitstream1.getID() + "/download"))) + .andExpect(jsonPath("$.linkset[0].item[0].type", + Matchers.hasToString(bitstream1MimeType))) + .andExpect(jsonPath("$.linkset[0].anchor", + Matchers.hasToString(url + "/entities/publication/" + item.getID()))) + .andExpect(jsonPath("$.linkset[0].linkset[0].href", + Matchers.hasToString(url + "/" + signpostingUrl + "/linksets/" + item.getID().toString()))) + .andExpect(jsonPath("$.linkset[0].linkset[0].type", + Matchers.hasToString("application/linkset"))) + .andExpect(jsonPath("$.linkset[0].linkset[1].href", + Matchers.hasToString(url + "/" + signpostingUrl + "/linksets/" + item.getID().toString() + + "/json"))) + .andExpect(jsonPath("$.linkset[0].linkset[1].type", + Matchers.hasToString("application/linkset+json"))) + .andExpect(jsonPath("$.linkset[1].collection[0].href", + Matchers.hasToString(url + "/entities/publication/" + item.getID()))) + .andExpect(jsonPath("$.linkset[1].collection[0].type", + Matchers.hasToString("text/html"))) + .andExpect(jsonPath("$.linkset[1].linkset[0].href", + Matchers.hasToString(url + "/" + signpostingUrl + "/linksets/" + item.getID().toString()))) + .andExpect(jsonPath("$.linkset[1].linkset[0].type", + Matchers.hasToString("application/linkset"))) + .andExpect(jsonPath("$.linkset[1].linkset[1].href", + Matchers.hasToString(url + "/" + signpostingUrl + "/linksets/" + item.getID().toString() + + "/json"))) + .andExpect(jsonPath("$.linkset[1].linkset[1].type", + Matchers.hasToString("application/linkset+json"))) + .andExpect(jsonPath("$.linkset[1].anchor", + Matchers.hasToString(url + "/bitstreams/" + bitstream1.getID() + "/download"))) + .andExpect(jsonPath("$.linkset[2].describes[0].href", + Matchers.hasToString(url + "/entities/publication/" + item.getID()))) + .andExpect(jsonPath("$.linkset[2].describes[0].type", + Matchers.hasToString("text/html"))) + .andExpect(jsonPath("$.linkset[2].anchor", + Matchers.hasToString(url + "/" + signpostingUrl + "/describedby/" + item.getID()))) + .andExpect(header().stringValues("Content-Type", "application/linkset+json;charset=UTF-8")); + } + @Test public void findOneItemThatIsInWorkspaceJsonLinksets() throws Exception { context.turnOffAuthorisationSystem(); From 01c761281c00b72ba4137900ac7704b1dc08dfd0 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 23 Jun 2023 11:50:12 -0500 Subject: [PATCH 279/510] Update LICENSES_THIRD_PARTY for 7.6 --- LICENSES_THIRD_PARTY | 381 +++++++++++++++++++++++-------------------- 1 file changed, 200 insertions(+), 181 deletions(-) diff --git a/LICENSES_THIRD_PARTY b/LICENSES_THIRD_PARTY index b96ea77648a6..e494c80c5d6e 100644 --- a/LICENSES_THIRD_PARTY +++ b/LICENSES_THIRD_PARTY @@ -26,7 +26,7 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * AWS Java SDK for Amazon S3 (com.amazonaws:aws-java-sdk-s3:1.12.261 - https://aws.amazon.com/sdkforjava) * JMES Path Query library (com.amazonaws:jmespath-java:1.12.261 - https://aws.amazon.com/sdkforjava) * HPPC Collections (com.carrotsearch:hppc:0.8.1 - http://labs.carrotsearch.com/hppc.html/hppc) - * com.drewnoakes:metadata-extractor (com.drewnoakes:metadata-extractor:2.16.0 - https://drewnoakes.com/code/exif/) + * com.drewnoakes:metadata-extractor (com.drewnoakes:metadata-extractor:2.18.0 - https://drewnoakes.com/code/exif/) * parso (com.epam:parso:2.0.14 - https://github.com/epam/parso) * Esri Geometry API for Java (com.esri.geometry:esri-geometry-api:2.2.0 - https://github.com/Esri/geometry-api-java) * ClassMate (com.fasterxml:classmate:1.3.0 - http://github.com/cowtowncoder/java-classmate) @@ -34,12 +34,12 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * Jackson-core (com.fasterxml.jackson.core:jackson-core:2.13.4 - https://github.com/FasterXML/jackson-core) * jackson-databind (com.fasterxml.jackson.core:jackson-databind:2.13.4.2 - http://github.com/FasterXML/jackson) * Jackson dataformat: CBOR (com.fasterxml.jackson.dataformat:jackson-dataformat-cbor:2.12.6 - http://github.com/FasterXML/jackson-dataformats-binary) - * Jackson dataformat: Smile (com.fasterxml.jackson.dataformat:jackson-dataformat-smile:2.12.3 - http://github.com/FasterXML/jackson-dataformats-binary) + * Jackson dataformat: Smile (com.fasterxml.jackson.dataformat:jackson-dataformat-smile:2.13.3 - http://github.com/FasterXML/jackson-dataformats-binary) * Jackson-dataformat-YAML (com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.11.1 - https://github.com/FasterXML/jackson-dataformats-text) - * Jackson datatype: jdk8 (com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.13.3 - https://github.com/FasterXML/jackson-modules-java8/jackson-datatype-jdk8) + * Jackson datatype: jdk8 (com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.13.5 - https://github.com/FasterXML/jackson-modules-java8/jackson-datatype-jdk8) * Jackson datatype: JSR310 (com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.11.1 - https://github.com/FasterXML/jackson-modules-java8/jackson-datatype-jsr310) - * Jackson datatype: JSR310 (com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.13.3 - https://github.com/FasterXML/jackson-modules-java8/jackson-datatype-jsr310) - * Jackson-module-parameter-names (com.fasterxml.jackson.module:jackson-module-parameter-names:2.13.3 - https://github.com/FasterXML/jackson-modules-java8/jackson-module-parameter-names) + * Jackson datatype: JSR310 (com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.13.5 - https://github.com/FasterXML/jackson-modules-java8/jackson-datatype-jsr310) + * Jackson-module-parameter-names (com.fasterxml.jackson.module:jackson-module-parameter-names:2.13.5 - https://github.com/FasterXML/jackson-modules-java8/jackson-module-parameter-names) * Java UUID Generator (com.fasterxml.uuid:java-uuid-generator:4.0.1 - https://github.com/cowtowncoder/java-uuid-generator) * Woodstox (com.fasterxml.woodstox:woodstox-core:6.2.4 - https://github.com/FasterXML/woodstox) * zjsonpatch (com.flipkart.zjsonpatch:zjsonpatch:0.4.6 - https://github.com/flipkart-incubator/zjsonpatch/) @@ -56,19 +56,19 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * Google Analytics API v3-rev145-1.23.0 (com.google.apis:google-api-services-analytics:v3-rev145-1.23.0 - http://nexus.sonatype.org/oss-repository-hosting.html/google-api-services-analytics) * FindBugs-jsr305 (com.google.code.findbugs:jsr305:3.0.1 - http://findbugs.sourceforge.net/) * Gson (com.google.code.gson:gson:2.9.0 - https://github.com/google/gson/gson) - * error-prone annotations (com.google.errorprone:error_prone_annotations:2.7.1 - http://nexus.sonatype.org/oss-repository-hosting.html/error_prone_parent/error_prone_annotations) + * error-prone annotations (com.google.errorprone:error_prone_annotations:2.18.0 - https://errorprone.info/error_prone_annotations) * Guava InternalFutureFailureAccess and InternalFutures (com.google.guava:failureaccess:1.0.1 - https://github.com/google/guava/failureaccess) - * Guava: Google Core Libraries for Java (com.google.guava:guava:31.0.1-jre - https://github.com/google/guava) + * Guava: Google Core Libraries for Java (com.google.guava:guava:32.0.0-jre - https://github.com/google/guava) * Guava: Google Core Libraries for Java (JDK5 Backport) (com.google.guava:guava-jdk5:17.0 - http://code.google.com/p/guava-libraries/guava-jdk5) * Guava ListenableFuture only (com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava - https://github.com/google/guava/listenablefuture) * Google HTTP Client Library for Java (com.google.http-client:google-http-client:1.23.0 - https://github.com/google/google-http-java-client/google-http-client) * GSON extensions to the Google HTTP Client Library for Java. (com.google.http-client:google-http-client-gson:1.41.7 - https://github.com/googleapis/google-http-java-client/google-http-client-gson) * Jackson 2 extensions to the Google HTTP Client Library for Java. (com.google.http-client:google-http-client-jackson2:1.23.0 - https://github.com/google/google-http-java-client/google-http-client-jackson2) - * J2ObjC Annotations (com.google.j2objc:j2objc-annotations:1.3 - https://github.com/google/j2objc/) + * J2ObjC Annotations (com.google.j2objc:j2objc-annotations:2.8 - https://github.com/google/j2objc/) * Google OAuth Client Library for Java (com.google.oauth-client:google-oauth-client:1.33.3 - https://github.com/googleapis/google-oauth-java-client/google-oauth-client) * ConcurrentLinkedHashMap (com.googlecode.concurrentlinkedhashmap:concurrentlinkedhashmap-lru:1.4.2 - http://code.google.com/p/concurrentlinkedhashmap) * libphonenumber (com.googlecode.libphonenumber:libphonenumber:8.11.1 - https://github.com/google/libphonenumber/) - * Jackcess (com.healthmarketscience.jackcess:jackcess:4.0.1 - https://jackcess.sourceforge.io) + * Jackcess (com.healthmarketscience.jackcess:jackcess:4.0.2 - https://jackcess.sourceforge.io) * Jackcess Encrypt (com.healthmarketscience.jackcess:jackcess-encrypt:4.0.1 - http://jackcessencrypt.sf.net) * project ':json-path' (com.jayway.jsonpath:json-path:2.6.0 - https://github.com/jayway/JsonPath) * project ':json-path-assert' (com.jayway.jsonpath:json-path-assert:2.6.0 - https://github.com/jayway/JsonPath) @@ -79,11 +79,20 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * Nimbus JOSE+JWT (com.nimbusds:nimbus-jose-jwt:7.9 - https://bitbucket.org/connect2id/nimbus-jose-jwt) * opencsv (com.opencsv:opencsv:5.6 - http://opencsv.sf.net) * java-libpst (com.pff:java-libpst:0.9.3 - https://github.com/rjohnsondev/java-libpst) - * rome (com.rometools:rome:1.18.0 - http://rometools.com/rome) - * rome-modules (com.rometools:rome-modules:1.18.0 - http://rometools.com/rome-modules) - * rome-utils (com.rometools:rome-utils:1.18.0 - http://rometools.com/rome-utils) + * rome (com.rometools:rome:1.19.0 - http://rometools.com/rome) + * rome-modules (com.rometools:rome-modules:1.19.0 - http://rometools.com/rome-modules) + * rome-utils (com.rometools:rome-utils:1.19.0 - http://rometools.com/rome-utils) * fastinfoset (com.sun.xml.fastinfoset:FastInfoset:1.2.15 - http://fi.java.net) * T-Digest (com.tdunning:t-digest:3.1 - https://github.com/tdunning/t-digest) + * config (com.typesafe:config:1.3.3 - https://github.com/lightbend/config) + * ssl-config-core (com.typesafe:ssl-config-core_2.13:0.3.8 - https://github.com/lightbend/ssl-config) + * akka-actor (com.typesafe.akka:akka-actor_2.13:2.5.31 - https://akka.io/) + * akka-http-core (com.typesafe.akka:akka-http-core_2.13:10.1.12 - https://akka.io) + * akka-http (com.typesafe.akka:akka-http_2.13:10.1.12 - https://akka.io) + * akka-parsing (com.typesafe.akka:akka-parsing_2.13:10.1.12 - https://akka.io) + * akka-protobuf (com.typesafe.akka:akka-protobuf_2.13:2.5.31 - https://akka.io/) + * akka-stream (com.typesafe.akka:akka-stream_2.13:2.5.31 - https://akka.io/) + * scala-logging (com.typesafe.scala-logging:scala-logging_2.13:3.9.2 - https://github.com/lightbend/scala-logging) * JSON library from Android SDK (com.vaadin.external.google:android-json:0.0.20131108.vaadin1 - http://developer.android.com/sdk) * SparseBitSet (com.zaxxer:SparseBitSet:1.2 - https://github.com/brettwooldridge/SparseBitSet) * Apache Commons BeanUtils (commons-beanutils:commons-beanutils:1.9.4 - https://commons.apache.org/proper/commons-beanutils/) @@ -91,20 +100,19 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * Apache Commons Codec (commons-codec:commons-codec:1.10 - http://commons.apache.org/proper/commons-codec/) * Apache Commons Collections (commons-collections:commons-collections:3.2.2 - http://commons.apache.org/collections/) * Commons Digester (commons-digester:commons-digester:1.8.1 - http://commons.apache.org/digester/) - * Apache Commons FileUpload (commons-fileupload:commons-fileupload:1.3.3 - http://commons.apache.org/proper/commons-fileupload/) + * Apache Commons FileUpload (commons-fileupload:commons-fileupload:1.5 - https://commons.apache.org/proper/commons-fileupload/) * Apache Commons IO (commons-io:commons-io:2.7 - https://commons.apache.org/proper/commons-io/) * Commons Lang (commons-lang:commons-lang:2.6 - http://commons.apache.org/lang/) * Apache Commons Logging (commons-logging:commons-logging:1.2 - http://commons.apache.org/proper/commons-logging/) * Apache Commons Validator (commons-validator:commons-validator:1.5.0 - http://commons.apache.org/proper/commons-validator/) * GeoJson POJOs for Jackson (de.grundid.opendatalab:geojson-jackson:1.14 - https://github.com/opendatalab-de/geojson-jackson) - * Boilerpipe -- Boilerplate Removal and Fulltext Extraction from HTML pages (de.l3s.boilerpipe:boilerpipe:1.1.0 - http://code.google.com/p/boilerpipe/) * OpenAIRE Funders Model (eu.openaire:funders-model:2.0.0 - https://api.openaire.eu) * Metrics Core (io.dropwizard.metrics:metrics-core:4.1.5 - https://metrics.dropwizard.io/metrics-core) * Graphite Integration for Metrics (io.dropwizard.metrics:metrics-graphite:4.1.5 - https://metrics.dropwizard.io/metrics-graphite) * Metrics Integration for Jetty 9.3 and higher (io.dropwizard.metrics:metrics-jetty9:4.1.5 - https://metrics.dropwizard.io/metrics-jetty9) * Metrics Integration with JMX (io.dropwizard.metrics:metrics-jmx:4.1.5 - https://metrics.dropwizard.io/metrics-jmx) * JVM Integration for Metrics (io.dropwizard.metrics:metrics-jvm:4.1.5 - https://metrics.dropwizard.io/metrics-jvm) - * micrometer-core (io.micrometer:micrometer-core:1.8.6 - https://github.com/micrometer-metrics/micrometer) + * micrometer-core (io.micrometer:micrometer-core:1.9.11 - https://github.com/micrometer-metrics/micrometer) * Netty/Buffer (io.netty:netty-buffer:4.1.68.Final - https://netty.io/netty-buffer/) * Netty/Codec (io.netty:netty-codec:4.1.68.Final - https://netty.io/netty-codec/) * Netty/Codec/HTTP (io.netty:netty-codec-http:4.1.53.Final - https://netty.io/netty-codec-http/) @@ -188,88 +196,87 @@ 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.17.1 - https://logging.apache.org/log4j/2.x/log4j-1.2-api/) - * Apache Log4j API (org.apache.logging.log4j:log4j-api:2.17.1 - https://logging.apache.org/log4j/2.x/log4j-api/) - * Apache Log4j Core (org.apache.logging.log4j:log4j-core:2.17.1 - https://logging.apache.org/log4j/2.x/log4j-core/) - * Apache Log4j JUL Adapter (org.apache.logging.log4j:log4j-jul:2.17.1 - https://logging.apache.org/log4j/2.x/log4j-jul/) - * Apache Log4j Layout for JSON template (org.apache.logging.log4j:log4j-layout-template-json:2.16.0 - https://logging.apache.org/log4j/2.x/log4j-layout-template-json/) - * Apache Log4j SLF4J Binding (org.apache.logging.log4j:log4j-slf4j-impl:2.17.1 - https://logging.apache.org/log4j/2.x/log4j-slf4j-impl/) - * Apache Log4j Web (org.apache.logging.log4j:log4j-web:2.17.1 - https://logging.apache.org/log4j/2.x/log4j-web/) - * Lucene Common Analyzers (org.apache.lucene:lucene-analyzers-common:8.11.1 - https://lucene.apache.org/lucene-parent/lucene-analyzers-common) - * Lucene ICU Analysis Components (org.apache.lucene:lucene-analyzers-icu:8.11.1 - https://lucene.apache.org/lucene-parent/lucene-analyzers-icu) - * Lucene Kuromoji Japanese Morphological Analyzer (org.apache.lucene:lucene-analyzers-kuromoji:8.11.1 - https://lucene.apache.org/lucene-parent/lucene-analyzers-kuromoji) - * Lucene Nori Korean Morphological Analyzer (org.apache.lucene:lucene-analyzers-nori:8.11.1 - https://lucene.apache.org/lucene-parent/lucene-analyzers-nori) - * Lucene Phonetic Filters (org.apache.lucene:lucene-analyzers-phonetic:8.11.1 - https://lucene.apache.org/lucene-parent/lucene-analyzers-phonetic) - * Lucene Smart Chinese Analyzer (org.apache.lucene:lucene-analyzers-smartcn:8.11.1 - https://lucene.apache.org/lucene-parent/lucene-analyzers-smartcn) - * Lucene Stempel Analyzer (org.apache.lucene:lucene-analyzers-stempel:8.11.1 - https://lucene.apache.org/lucene-parent/lucene-analyzers-stempel) - * Lucene Memory (org.apache.lucene:lucene-backward-codecs:8.11.1 - https://lucene.apache.org/lucene-parent/lucene-backward-codecs) - * Lucene Classification (org.apache.lucene:lucene-classification:8.11.1 - https://lucene.apache.org/lucene-parent/lucene-classification) - * Lucene codecs (org.apache.lucene:lucene-codecs:8.11.1 - https://lucene.apache.org/lucene-parent/lucene-codecs) - * Lucene Core (org.apache.lucene:lucene-core:8.11.1 - https://lucene.apache.org/lucene-parent/lucene-core) - * Lucene Expressions (org.apache.lucene:lucene-expressions:8.11.1 - https://lucene.apache.org/lucene-parent/lucene-expressions) - * Lucene Grouping (org.apache.lucene:lucene-grouping:8.11.1 - https://lucene.apache.org/lucene-parent/lucene-grouping) - * Lucene Highlighter (org.apache.lucene:lucene-highlighter:8.11.1 - https://lucene.apache.org/lucene-parent/lucene-highlighter) - * Lucene Join (org.apache.lucene:lucene-join:8.11.1 - https://lucene.apache.org/lucene-parent/lucene-join) - * Lucene Memory (org.apache.lucene:lucene-memory:8.11.1 - https://lucene.apache.org/lucene-parent/lucene-memory) - * Lucene Miscellaneous (org.apache.lucene:lucene-misc:8.11.1 - https://lucene.apache.org/lucene-parent/lucene-misc) - * Lucene Queries (org.apache.lucene:lucene-queries:8.11.1 - https://lucene.apache.org/lucene-parent/lucene-queries) - * Lucene QueryParsers (org.apache.lucene:lucene-queryparser:8.11.1 - https://lucene.apache.org/lucene-parent/lucene-queryparser) - * Lucene Sandbox (org.apache.lucene:lucene-sandbox:8.11.1 - https://lucene.apache.org/lucene-parent/lucene-sandbox) - * Lucene Spatial Extras (org.apache.lucene:lucene-spatial-extras:8.11.1 - https://lucene.apache.org/lucene-parent/lucene-spatial-extras) - * Lucene Spatial 3D (org.apache.lucene:lucene-spatial3d:8.11.1 - https://lucene.apache.org/lucene-parent/lucene-spatial3d) - * Lucene Suggest (org.apache.lucene:lucene-suggest:8.11.1 - https://lucene.apache.org/lucene-parent/lucene-suggest) - * Apache FontBox (org.apache.pdfbox:fontbox:2.0.27 - http://pdfbox.apache.org/) - * PDFBox JBIG2 ImageIO plugin (org.apache.pdfbox:jbig2-imageio:3.0.3 - https://www.apache.org/jbig2-imageio/) - * Apache JempBox (org.apache.pdfbox:jempbox:1.8.16 - http://www.apache.org/pdfbox-parent/jempbox/) - * Apache PDFBox (org.apache.pdfbox:pdfbox:2.0.27 - https://www.apache.org/pdfbox-parent/pdfbox/) - * Apache PDFBox Debugger (org.apache.pdfbox:pdfbox-debugger:2.0.25 - https://www.apache.org/pdfbox-parent/pdfbox-debugger/) - * Apache PDFBox tools (org.apache.pdfbox:pdfbox-tools:2.0.25 - https://www.apache.org/pdfbox-parent/pdfbox-tools/) - * Apache XmpBox (org.apache.pdfbox:xmpbox:2.0.25 - https://www.apache.org/pdfbox-parent/xmpbox/) - * Apache POI - Common (org.apache.poi:poi:5.2.0 - https://poi.apache.org/) - * Apache POI - API based on OPC and OOXML schemas (org.apache.poi:poi-ooxml:5.2.0 - https://poi.apache.org/) - * Apache POI (org.apache.poi:poi-ooxml-lite:5.2.0 - https://poi.apache.org/) - * Apache POI (org.apache.poi:poi-scratchpad:5.2.0 - https://poi.apache.org/) - * Apache Solr Core (org.apache.solr:solr-core:8.11.1 - https://lucene.apache.org/solr-parent/solr-core) - * Apache Solr Solrj (org.apache.solr:solr-solrj:8.11.1 - https://lucene.apache.org/solr-parent/solr-solrj) + * Apache Log4j 1.x Compatibility API (org.apache.logging.log4j:log4j-1.2-api:2.20.0 - https://logging.apache.org/log4j/2.x/log4j-1.2-api/) + * Apache Log4j API (org.apache.logging.log4j:log4j-api:2.20.0 - https://logging.apache.org/log4j/2.x/log4j-api/) + * Apache Log4j Core (org.apache.logging.log4j:log4j-core:2.20.0 - https://logging.apache.org/log4j/2.x/log4j-core/) + * Apache Log4j JUL Adapter (org.apache.logging.log4j:log4j-jul:2.20.0 - https://logging.apache.org/log4j/2.x/log4j-jul/) + * Apache Log4j Layout for JSON template (org.apache.logging.log4j:log4j-layout-template-json:2.17.1 - https://logging.apache.org/log4j/2.x/log4j-layout-template-json/) + * Apache Log4j SLF4J Binding (org.apache.logging.log4j:log4j-slf4j-impl:2.20.0 - https://logging.apache.org/log4j/2.x/log4j-slf4j-impl/) + * Apache Log4j Web (org.apache.logging.log4j:log4j-web:2.20.0 - https://logging.apache.org/log4j/2.x/log4j-web/) + * Lucene Common Analyzers (org.apache.lucene:lucene-analyzers-common:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-analyzers-common) + * Lucene ICU Analysis Components (org.apache.lucene:lucene-analyzers-icu:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-analyzers-icu) + * Lucene Kuromoji Japanese Morphological Analyzer (org.apache.lucene:lucene-analyzers-kuromoji:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-analyzers-kuromoji) + * Lucene Nori Korean Morphological Analyzer (org.apache.lucene:lucene-analyzers-nori:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-analyzers-nori) + * Lucene Phonetic Filters (org.apache.lucene:lucene-analyzers-phonetic:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-analyzers-phonetic) + * Lucene Smart Chinese Analyzer (org.apache.lucene:lucene-analyzers-smartcn:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-analyzers-smartcn) + * Lucene Stempel Analyzer (org.apache.lucene:lucene-analyzers-stempel:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-analyzers-stempel) + * Lucene Memory (org.apache.lucene:lucene-backward-codecs:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-backward-codecs) + * Lucene Classification (org.apache.lucene:lucene-classification:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-classification) + * Lucene codecs (org.apache.lucene:lucene-codecs:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-codecs) + * Lucene Core (org.apache.lucene:lucene-core:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-core) + * Lucene Expressions (org.apache.lucene:lucene-expressions:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-expressions) + * Lucene Grouping (org.apache.lucene:lucene-grouping:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-grouping) + * Lucene Highlighter (org.apache.lucene:lucene-highlighter:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-highlighter) + * Lucene Join (org.apache.lucene:lucene-join:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-join) + * Lucene Memory (org.apache.lucene:lucene-memory:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-memory) + * Lucene Miscellaneous (org.apache.lucene:lucene-misc:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-misc) + * Lucene Queries (org.apache.lucene:lucene-queries:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-queries) + * Lucene QueryParsers (org.apache.lucene:lucene-queryparser:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-queryparser) + * Lucene Sandbox (org.apache.lucene:lucene-sandbox:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-sandbox) + * Lucene Spatial Extras (org.apache.lucene:lucene-spatial-extras:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-spatial-extras) + * Lucene Spatial 3D (org.apache.lucene:lucene-spatial3d:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-spatial3d) + * Lucene Suggest (org.apache.lucene:lucene-suggest:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-suggest) + * Apache FontBox (org.apache.pdfbox:fontbox:2.0.28 - http://pdfbox.apache.org/) + * PDFBox JBIG2 ImageIO plugin (org.apache.pdfbox:jbig2-imageio:3.0.4 - https://www.apache.org/jbig2-imageio/) + * Apache JempBox (org.apache.pdfbox:jempbox:1.8.17 - http://www.apache.org/pdfbox-parent/jempbox/) + * Apache PDFBox (org.apache.pdfbox:pdfbox:2.0.28 - https://www.apache.org/pdfbox-parent/pdfbox/) + * Apache PDFBox tools (org.apache.pdfbox:pdfbox-tools:2.0.27 - https://www.apache.org/pdfbox-parent/pdfbox-tools/) + * Apache XmpBox (org.apache.pdfbox:xmpbox:2.0.27 - https://www.apache.org/pdfbox-parent/xmpbox/) + * Apache POI - Common (org.apache.poi:poi:5.2.3 - https://poi.apache.org/) + * Apache POI - API based on OPC and OOXML schemas (org.apache.poi:poi-ooxml:5.2.3 - https://poi.apache.org/) + * Apache POI (org.apache.poi:poi-ooxml-lite:5.2.3 - https://poi.apache.org/) + * Apache POI (org.apache.poi:poi-scratchpad:5.2.3 - https://poi.apache.org/) + * Apache Solr Core (org.apache.solr:solr-core:8.11.2 - https://lucene.apache.org/solr-parent/solr-core) + * Apache Solr Solrj (org.apache.solr:solr-solrj:8.11.2 - https://lucene.apache.org/solr-parent/solr-solrj) * Apache 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.3.0 - https://tika.apache.org/) - * Apache Tika Apple parser module (org.apache.tika:tika-parser-apple-module:2.3.0 - https://tika.apache.org/tika-parser-apple-module/) - * Apache Tika audiovideo parser module (org.apache.tika:tika-parser-audiovideo-module:2.3.0 - https://tika.apache.org/tika-parser-audiovideo-module/) - * Apache Tika cad parser module (org.apache.tika:tika-parser-cad-module:2.3.0 - https://tika.apache.org/tika-parser-cad-module/) - * Apache Tika code parser module (org.apache.tika:tika-parser-code-module:2.3.0 - https://tika.apache.org/tika-parser-code-module/) - * Apache Tika crypto parser module (org.apache.tika:tika-parser-crypto-module:2.3.0 - https://tika.apache.org/tika-parser-crypto-module/) - * Apache Tika digest commons (org.apache.tika:tika-parser-digest-commons:2.3.0 - https://tika.apache.org/tika-parser-digest-commons/) - * Apache Tika font parser module (org.apache.tika:tika-parser-font-module:2.3.0 - https://tika.apache.org/tika-parser-font-module/) - * Apache Tika html commons (org.apache.tika:tika-parser-html-commons:2.3.0 - https://tika.apache.org/tika-parser-html-commons/) - * Apache Tika html parser module (org.apache.tika:tika-parser-html-module:2.3.0 - https://tika.apache.org/tika-parser-html-module/) - * Apache Tika image parser module (org.apache.tika:tika-parser-image-module:2.3.0 - https://tika.apache.org/tika-parser-image-module/) - * Apache Tika mail commons (org.apache.tika:tika-parser-mail-commons:2.3.0 - https://tika.apache.org/tika-parser-mail-commons/) - * Apache Tika mail parser module (org.apache.tika:tika-parser-mail-module:2.3.0 - https://tika.apache.org/tika-parser-mail-module/) - * Apache Tika Microsoft parser module (org.apache.tika:tika-parser-microsoft-module:2.3.0 - https://tika.apache.org/tika-parser-microsoft-module/) - * Apache Tika miscellaneous office format parser module (org.apache.tika:tika-parser-miscoffice-module:2.3.0 - https://tika.apache.org/tika-parser-miscoffice-module/) - * Apache Tika news parser module (org.apache.tika:tika-parser-news-module:2.3.0 - https://tika.apache.org/tika-parser-news-module/) - * Apache Tika OCR parser module (org.apache.tika:tika-parser-ocr-module:2.3.0 - https://tika.apache.org/tika-parser-ocr-module/) - * Apache Tika PDF parser module (org.apache.tika:tika-parser-pdf-module:2.3.0 - https://tika.apache.org/tika-parser-pdf-module/) - * Apache Tika package parser module (org.apache.tika:tika-parser-pkg-module:2.3.0 - https://tika.apache.org/tika-parser-pkg-module/) - * Apache Tika text parser module (org.apache.tika:tika-parser-text-module:2.3.0 - https://tika.apache.org/tika-parser-text-module/) - * Apache Tika XML parser module (org.apache.tika:tika-parser-xml-module:2.3.0 - https://tika.apache.org/tika-parser-xml-module/) - * Apache Tika XMP commons (org.apache.tika:tika-parser-xmp-commons:2.3.0 - https://tika.apache.org/tika-parser-xmp-commons/) - * Apache Tika ZIP commons (org.apache.tika:tika-parser-zip-commons:2.3.0 - https://tika.apache.org/tika-parser-zip-commons/) - * Apache Tika standard parser package (org.apache.tika:tika-parsers-standard-package:2.3.0 - https://tika.apache.org/tika-parsers/tika-parsers-standard/tika-parsers-standard-package/) - * tomcat-embed-core (org.apache.tomcat.embed:tomcat-embed-core:9.0.63 - https://tomcat.apache.org/) - * tomcat-embed-el (org.apache.tomcat.embed:tomcat-embed-el:9.0.63 - https://tomcat.apache.org/) - * tomcat-embed-websocket (org.apache.tomcat.embed:tomcat-embed-websocket:9.0.63 - https://tomcat.apache.org/) + * Apache Tika core (org.apache.tika:tika-core:2.5.0 - https://tika.apache.org/) + * Apache Tika Apple parser module (org.apache.tika:tika-parser-apple-module:2.5.0 - https://tika.apache.org/tika-parser-apple-module/) + * Apache Tika audiovideo parser module (org.apache.tika:tika-parser-audiovideo-module:2.5.0 - https://tika.apache.org/tika-parser-audiovideo-module/) + * Apache Tika cad parser module (org.apache.tika:tika-parser-cad-module:2.5.0 - https://tika.apache.org/tika-parser-cad-module/) + * Apache Tika code parser module (org.apache.tika:tika-parser-code-module:2.5.0 - https://tika.apache.org/tika-parser-code-module/) + * Apache Tika crypto parser module (org.apache.tika:tika-parser-crypto-module:2.5.0 - https://tika.apache.org/tika-parser-crypto-module/) + * Apache Tika digest commons (org.apache.tika:tika-parser-digest-commons:2.5.0 - https://tika.apache.org/tika-parser-digest-commons/) + * Apache Tika font parser module (org.apache.tika:tika-parser-font-module:2.5.0 - https://tika.apache.org/tika-parser-font-module/) + * Apache Tika html parser module (org.apache.tika:tika-parser-html-module:2.5.0 - https://tika.apache.org/tika-parser-html-module/) + * Apache Tika image parser module (org.apache.tika:tika-parser-image-module:2.5.0 - https://tika.apache.org/tika-parser-image-module/) + * Apache Tika mail commons (org.apache.tika:tika-parser-mail-commons:2.5.0 - https://tika.apache.org/tika-parser-mail-commons/) + * Apache Tika mail parser module (org.apache.tika:tika-parser-mail-module:2.5.0 - https://tika.apache.org/tika-parser-mail-module/) + * Apache Tika Microsoft parser module (org.apache.tika:tika-parser-microsoft-module:2.5.0 - https://tika.apache.org/tika-parser-microsoft-module/) + * Apache Tika miscellaneous office format parser module (org.apache.tika:tika-parser-miscoffice-module:2.5.0 - https://tika.apache.org/tika-parser-miscoffice-module/) + * Apache Tika news parser module (org.apache.tika:tika-parser-news-module:2.5.0 - https://tika.apache.org/tika-parser-news-module/) + * Apache Tika OCR parser module (org.apache.tika:tika-parser-ocr-module:2.5.0 - https://tika.apache.org/tika-parser-ocr-module/) + * Apache Tika PDF parser module (org.apache.tika:tika-parser-pdf-module:2.5.0 - https://tika.apache.org/tika-parser-pdf-module/) + * Apache Tika package parser module (org.apache.tika:tika-parser-pkg-module:2.5.0 - https://tika.apache.org/tika-parser-pkg-module/) + * Apache Tika text parser module (org.apache.tika:tika-parser-text-module:2.5.0 - https://tika.apache.org/tika-parser-text-module/) + * Apache Tika WARC parser module (org.apache.tika:tika-parser-webarchive-module:2.5.0 - https://tika.apache.org/tika-parser-webarchive-module/) + * Apache Tika XML parser module (org.apache.tika:tika-parser-xml-module:2.5.0 - https://tika.apache.org/tika-parser-xml-module/) + * Apache Tika XMP commons (org.apache.tika:tika-parser-xmp-commons:2.5.0 - https://tika.apache.org/tika-parser-xmp-commons/) + * Apache Tika ZIP commons (org.apache.tika:tika-parser-zip-commons:2.5.0 - https://tika.apache.org/tika-parser-zip-commons/) + * Apache Tika standard parser package (org.apache.tika:tika-parsers-standard-package:2.5.0 - https://tika.apache.org/tika-parsers/tika-parsers-standard/tika-parsers-standard-package/) + * tomcat-embed-core (org.apache.tomcat.embed:tomcat-embed-core:9.0.75 - https://tomcat.apache.org/) + * tomcat-embed-el (org.apache.tomcat.embed:tomcat-embed-el:9.0.75 - https://tomcat.apache.org/) + * tomcat-embed-websocket (org.apache.tomcat.embed:tomcat-embed-websocket:9.0.75 - https://tomcat.apache.org/) * Apache Velocity - Engine (org.apache.velocity:velocity-engine-core:2.3 - http://velocity.apache.org/engine/devel/velocity-engine-core/) * Apache Velocity - JSR 223 Scripting (org.apache.velocity:velocity-engine-scripting:2.2 - http://velocity.apache.org/engine/devel/velocity-engine-scripting/) * Axiom API (org.apache.ws.commons.axiom:axiom-api:1.2.22 - http://ws.apache.org/axiom/) * Abdera Model (FOM) Implementation (org.apache.ws.commons.axiom:fom-impl:1.2.22 - http://ws.apache.org/axiom/implementations/fom-impl/) - * XmlBeans (org.apache.xmlbeans:xmlbeans:5.0.3 - https://xmlbeans.apache.org/) + * XmlBeans (org.apache.xmlbeans:xmlbeans:5.1.1 - https://xmlbeans.apache.org/) * Apache ZooKeeper - Server (org.apache.zookeeper:zookeeper:3.6.2 - http://zookeeper.apache.org/zookeeper) * Apache ZooKeeper - Jute (org.apache.zookeeper:zookeeper-jute:3.6.2 - http://zookeeper.apache.org/zookeeper-jute) * org.apiguardian:apiguardian-api (org.apiguardian:apiguardian-api:1.1.0 - https://github.com/apiguardian-team/apiguardian) - * AssertJ fluent assertions (org.assertj:assertj-core:3.21.0 - https://assertj.github.io/doc/assertj-core/) + * AssertJ fluent assertions (org.assertj:assertj-core:3.22.0 - https://assertj.github.io/doc/assertj-core/) * Evo Inflector (org.atteo:evo-inflector:1.3 - http://atteo.org/static/evo-inflector) * jose4j (org.bitbucket.b_c:jose4j:0.6.5 - https://bitbucket.org/b_c/jose4j/) * TagSoup (org.ccil.cowan.tagsoup:tagsoup:1.2.1 - http://home.ccil.org/~cowan/XML/tagsoup/) @@ -279,34 +286,34 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * Apache :: JSTL module (org.eclipse.jetty:apache-jstl:9.4.15.v20190215 - http://tomcat.apache.org/taglibs/standard/) * Jetty :: ALPN :: Client (org.eclipse.jetty:jetty-alpn-client:9.4.44.v20210927 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-client) * Jetty :: ALPN :: JDK9 Client Implementation (org.eclipse.jetty:jetty-alpn-java-client:9.4.44.v20210927 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-java-client) - * Jetty :: ALPN :: JDK9 Server Implementation (org.eclipse.jetty:jetty-alpn-java-server:9.4.48.v20220622 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-java-server) + * Jetty :: ALPN :: JDK9 Server Implementation (org.eclipse.jetty:jetty-alpn-java-server:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-java-server) * Jetty :: ALPN :: Server (org.eclipse.jetty:jetty-alpn-server:9.4.44.v20210927 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-server) - * Jetty :: ALPN :: Server (org.eclipse.jetty:jetty-alpn-server:9.4.48.v20220622 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-server) + * Jetty :: ALPN :: Server (org.eclipse.jetty:jetty-alpn-server:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-server) * Jetty :: Servlet Annotations (org.eclipse.jetty:jetty-annotations:9.4.15.v20190215 - http://www.eclipse.org/jetty) * Jetty :: Asynchronous HTTP Client (org.eclipse.jetty:jetty-client:9.4.44.v20210927 - https://eclipse.org/jetty/jetty-client) * Jetty :: Continuation (org.eclipse.jetty:jetty-continuation:9.4.44.v20210927 - https://eclipse.org/jetty/jetty-continuation) - * Jetty :: Continuation (org.eclipse.jetty:jetty-continuation:9.4.48.v20220622 - https://eclipse.org/jetty/jetty-continuation) - * Jetty :: Deployers (org.eclipse.jetty:jetty-deploy:9.4.48.v20220622 - https://eclipse.org/jetty/jetty-deploy) - * Jetty :: Http Utility (org.eclipse.jetty:jetty-http:9.4.48.v20220622 - https://eclipse.org/jetty/jetty-http) - * Jetty :: IO Utility (org.eclipse.jetty:jetty-io:9.4.48.v20220622 - https://eclipse.org/jetty/jetty-io) + * Jetty :: Continuation (org.eclipse.jetty:jetty-continuation:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-continuation) + * Jetty :: Deployers (org.eclipse.jetty:jetty-deploy:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-deploy) + * Jetty :: Http Utility (org.eclipse.jetty:jetty-http:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-http) + * Jetty :: IO Utility (org.eclipse.jetty:jetty-io:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-io) * Jetty :: JMX Management (org.eclipse.jetty:jetty-jmx:9.4.44.v20210927 - https://eclipse.org/jetty/jetty-jmx) * Jetty :: JNDI Naming (org.eclipse.jetty:jetty-jndi:9.4.15.v20190215 - http://www.eclipse.org/jetty) * Jetty :: Plus (org.eclipse.jetty:jetty-plus:9.4.15.v20190215 - http://www.eclipse.org/jetty) * Jetty :: Rewrite Handler (org.eclipse.jetty:jetty-rewrite:9.4.44.v20210927 - https://eclipse.org/jetty/jetty-rewrite) * Jetty :: Security (org.eclipse.jetty:jetty-security:9.4.44.v20210927 - https://eclipse.org/jetty/jetty-security) - * Jetty :: Security (org.eclipse.jetty:jetty-security:9.4.48.v20220622 - https://eclipse.org/jetty/jetty-security) - * Jetty :: Server Core (org.eclipse.jetty:jetty-server:9.4.48.v20220622 - https://eclipse.org/jetty/jetty-server) - * Jetty :: Servlet Handling (org.eclipse.jetty:jetty-servlet:9.4.48.v20220622 - https://eclipse.org/jetty/jetty-servlet) - * Jetty :: Utility Servlets and Filters (org.eclipse.jetty:jetty-servlets:9.4.48.v20220622 - https://eclipse.org/jetty/jetty-servlets) - * Jetty :: Utilities (org.eclipse.jetty:jetty-util:9.4.48.v20220622 - https://eclipse.org/jetty/jetty-util) - * Jetty :: Utilities :: Ajax(JSON) (org.eclipse.jetty:jetty-util-ajax:9.4.48.v20220622 - https://eclipse.org/jetty/jetty-util-ajax) - * Jetty :: Webapp Application Support (org.eclipse.jetty:jetty-webapp:9.4.48.v20220622 - https://eclipse.org/jetty/jetty-webapp) - * Jetty :: XML utilities (org.eclipse.jetty:jetty-xml:9.4.48.v20220622 - https://eclipse.org/jetty/jetty-xml) + * Jetty :: Security (org.eclipse.jetty:jetty-security:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-security) + * Jetty :: Server Core (org.eclipse.jetty:jetty-server:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-server) + * Jetty :: Servlet Handling (org.eclipse.jetty:jetty-servlet:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-servlet) + * Jetty :: Utility Servlets and Filters (org.eclipse.jetty:jetty-servlets:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-servlets) + * Jetty :: Utilities (org.eclipse.jetty:jetty-util:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-util) + * Jetty :: Utilities :: Ajax(JSON) (org.eclipse.jetty:jetty-util-ajax:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-util-ajax) + * Jetty :: Webapp Application Support (org.eclipse.jetty:jetty-webapp:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-webapp) + * Jetty :: XML utilities (org.eclipse.jetty:jetty-xml:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-xml) * Jetty :: HTTP2 :: Client (org.eclipse.jetty.http2:http2-client:9.4.44.v20210927 - https://eclipse.org/jetty/http2-parent/http2-client) - * Jetty :: HTTP2 :: Common (org.eclipse.jetty.http2:http2-common:9.4.48.v20220622 - https://eclipse.org/jetty/http2-parent/http2-common) + * Jetty :: HTTP2 :: Common (org.eclipse.jetty.http2:http2-common:9.4.51.v20230217 - https://eclipse.org/jetty/http2-parent/http2-common) * Jetty :: HTTP2 :: HPACK (org.eclipse.jetty.http2:http2-hpack:9.4.44.v20210927 - https://eclipse.org/jetty/http2-parent/http2-hpack) * Jetty :: HTTP2 :: HTTP Client Transport (org.eclipse.jetty.http2:http2-http-client-transport:9.4.44.v20210927 - https://eclipse.org/jetty/http2-parent/http2-http-client-transport) - * Jetty :: HTTP2 :: Server (org.eclipse.jetty.http2:http2-server:9.4.48.v20220622 - https://eclipse.org/jetty/http2-parent/http2-server) + * Jetty :: HTTP2 :: Server (org.eclipse.jetty.http2:http2-server:9.4.51.v20230217 - https://eclipse.org/jetty/http2-parent/http2-server) * Jetty :: Schemas (org.eclipse.jetty.toolchain:jetty-schemas:3.1.2 - https://eclipse.org/jetty/jetty-schemas) * Ehcache (org.ehcache:ehcache:3.4.0 - http://ehcache.org) * flyway-core (org.flywaydb:flyway-core:8.4.4 - https://flywaydb.org/flyway-core) @@ -315,8 +322,10 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.35 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) * jersey-core-common (org.glassfish.jersey.core:jersey-common:2.35 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-common) * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.35 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) - * Hibernate Validator Engine (org.hibernate.validator:hibernate-validator:6.0.23.Final - http://hibernate.org/validator/hibernate-validator) - * Hibernate Validator Portable Extension (org.hibernate.validator:hibernate-validator-cdi:6.0.23.Final - http://hibernate.org/validator/hibernate-validator-cdi) + * 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) + * leveldb (org.iq80.leveldb:leveldb:0.12 - http://github.com/dain/leveldb/leveldb) + * leveldb-api (org.iq80.leveldb:leveldb-api:0.12 - http://github.com/dain/leveldb/leveldb-api) * Javassist (org.javassist:javassist:3.25.0-GA - http://www.javassist.org/) * Java Annotation Indexer (org.jboss:jandex:2.4.2.Final - http://www.jboss.org/jandex) * JBoss Logging 3 (org.jboss.logging:jboss-logging:3.4.3.Final - http://www.jboss.org) @@ -337,59 +346,67 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * Jetty Servlet Tester (org.mortbay.jetty:jetty-servlet-tester:6.1.26 - http://www.eclipse.org/jetty/jetty-parent/project/jetty-servlet-tester) * Jetty Utilities (org.mortbay.jetty:jetty-util:6.1.26 - http://www.eclipse.org/jetty/jetty-parent/project/jetty-util) * Servlet Specification API (org.mortbay.jetty:servlet-api:2.5-20081211 - http://jetty.mortbay.org/servlet-api) + * jwarc (org.netpreserve:jwarc:0.19.0 - https://github.com/iipc/jwarc) * Objenesis (org.objenesis:objenesis:3.2 - http://objenesis.org/objenesis) * parboiled-core (org.parboiled:parboiled-core:1.3.1 - http://parboiled.org) * parboiled-java (org.parboiled:parboiled-java:1.3.1 - http://parboiled.org) * RRD4J (org.rrd4j:rrd4j:3.5 - https://github.com/rrd4j/rrd4j/) - * JSONassert (org.skyscreamer:jsonassert:1.5.0 - https://github.com/skyscreamer/JSONassert) - * Spring AOP (org.springframework:spring-aop:5.3.20 - https://github.com/spring-projects/spring-framework) - * Spring Beans (org.springframework:spring-beans:5.3.20 - https://github.com/spring-projects/spring-framework) - * Spring Context (org.springframework:spring-context:5.3.20 - https://github.com/spring-projects/spring-framework) - * Spring Context Support (org.springframework:spring-context-support:5.3.20 - https://github.com/spring-projects/spring-framework) - * Spring Core (org.springframework:spring-core:5.3.20 - https://github.com/spring-projects/spring-framework) - * Spring Expression Language (SpEL) (org.springframework:spring-expression:5.3.20 - https://github.com/spring-projects/spring-framework) - * Spring Commons Logging Bridge (org.springframework:spring-jcl:5.3.20 - https://github.com/spring-projects/spring-framework) - * Spring JDBC (org.springframework:spring-jdbc:5.3.20 - https://github.com/spring-projects/spring-framework) - * Spring Object/Relational Mapping (org.springframework:spring-orm:5.3.20 - https://github.com/spring-projects/spring-framework) - * Spring TestContext Framework (org.springframework:spring-test:5.3.20 - https://github.com/spring-projects/spring-framework) - * Spring Transaction (org.springframework:spring-tx:5.3.20 - https://github.com/spring-projects/spring-framework) - * Spring Web (org.springframework:spring-web:5.3.20 - https://github.com/spring-projects/spring-framework) - * Spring Web MVC (org.springframework:spring-webmvc:5.3.20 - https://github.com/spring-projects/spring-framework) - * spring-boot (org.springframework.boot:spring-boot:2.6.8 - https://spring.io/projects/spring-boot) - * spring-boot-actuator (org.springframework.boot:spring-boot-actuator:2.6.8 - https://spring.io/projects/spring-boot) - * spring-boot-actuator-autoconfigure (org.springframework.boot:spring-boot-actuator-autoconfigure:2.6.8 - https://spring.io/projects/spring-boot) - * spring-boot-autoconfigure (org.springframework.boot:spring-boot-autoconfigure:2.6.8 - https://spring.io/projects/spring-boot) + * Scala Library (org.scala-lang:scala-library:2.13.9 - https://www.scala-lang.org/) + * Scala Compiler (org.scala-lang:scala-reflect:2.13.0 - https://www.scala-lang.org/) + * scala-collection-compat (org.scala-lang.modules:scala-collection-compat_2.13:2.1.6 - http://www.scala-lang.org/) + * scala-java8-compat (org.scala-lang.modules:scala-java8-compat_2.13:0.9.0 - http://www.scala-lang.org/) + * scala-parser-combinators (org.scala-lang.modules:scala-parser-combinators_2.13:1.1.2 - http://www.scala-lang.org/) + * scala-xml (org.scala-lang.modules:scala-xml_2.13:1.3.0 - http://www.scala-lang.org/) + * JSONassert (org.skyscreamer:jsonassert:1.5.1 - https://github.com/skyscreamer/JSONassert) + * JCL 1.2 implemented over SLF4J (org.slf4j:jcl-over-slf4j:1.7.36 - http://www.slf4j.org) + * Spring AOP (org.springframework:spring-aop:5.3.27 - https://github.com/spring-projects/spring-framework) + * Spring Beans (org.springframework:spring-beans:5.3.27 - https://github.com/spring-projects/spring-framework) + * Spring Context (org.springframework:spring-context:5.3.27 - https://github.com/spring-projects/spring-framework) + * Spring Context Support (org.springframework:spring-context-support:5.3.27 - https://github.com/spring-projects/spring-framework) + * Spring Core (org.springframework:spring-core:5.3.27 - https://github.com/spring-projects/spring-framework) + * Spring Expression Language (SpEL) (org.springframework:spring-expression:5.3.27 - https://github.com/spring-projects/spring-framework) + * Spring Commons Logging Bridge (org.springframework:spring-jcl:5.3.27 - https://github.com/spring-projects/spring-framework) + * Spring JDBC (org.springframework:spring-jdbc:5.3.27 - https://github.com/spring-projects/spring-framework) + * Spring Object/Relational Mapping (org.springframework:spring-orm:5.3.27 - https://github.com/spring-projects/spring-framework) + * Spring TestContext Framework (org.springframework:spring-test:5.3.27 - https://github.com/spring-projects/spring-framework) + * Spring Transaction (org.springframework:spring-tx:5.3.27 - https://github.com/spring-projects/spring-framework) + * Spring Web (org.springframework:spring-web:5.3.27 - https://github.com/spring-projects/spring-framework) + * Spring Web MVC (org.springframework:spring-webmvc:5.3.27 - https://github.com/spring-projects/spring-framework) + * spring-boot (org.springframework.boot:spring-boot:2.7.12 - https://spring.io/projects/spring-boot) + * spring-boot-actuator (org.springframework.boot:spring-boot-actuator:2.7.12 - https://spring.io/projects/spring-boot) + * spring-boot-actuator-autoconfigure (org.springframework.boot:spring-boot-actuator-autoconfigure:2.7.12 - https://spring.io/projects/spring-boot) + * spring-boot-autoconfigure (org.springframework.boot:spring-boot-autoconfigure:2.7.12 - https://spring.io/projects/spring-boot) * Spring Boot Configuration Processor (org.springframework.boot:spring-boot-configuration-processor:2.0.0.RELEASE - https://projects.spring.io/spring-boot/#/spring-boot-parent/spring-boot-tools/spring-boot-configuration-processor) - * spring-boot-starter (org.springframework.boot:spring-boot-starter:2.6.8 - https://spring.io/projects/spring-boot) - * spring-boot-starter-actuator (org.springframework.boot:spring-boot-starter-actuator:2.6.8 - https://spring.io/projects/spring-boot) - * spring-boot-starter-aop (org.springframework.boot:spring-boot-starter-aop:2.6.8 - https://spring.io/projects/spring-boot) - * spring-boot-starter-cache (org.springframework.boot:spring-boot-starter-cache:2.6.8 - https://spring.io/projects/spring-boot) - * spring-boot-starter-data-rest (org.springframework.boot:spring-boot-starter-data-rest:2.6.8 - https://spring.io/projects/spring-boot) - * spring-boot-starter-json (org.springframework.boot:spring-boot-starter-json:2.6.8 - https://spring.io/projects/spring-boot) - * spring-boot-starter-log4j2 (org.springframework.boot:spring-boot-starter-log4j2:2.6.8 - https://spring.io/projects/spring-boot) - * spring-boot-starter-security (org.springframework.boot:spring-boot-starter-security:2.6.8 - https://spring.io/projects/spring-boot) - * spring-boot-starter-test (org.springframework.boot:spring-boot-starter-test:2.6.8 - https://spring.io/projects/spring-boot) - * spring-boot-starter-tomcat (org.springframework.boot:spring-boot-starter-tomcat:2.6.8 - https://spring.io/projects/spring-boot) - * spring-boot-starter-web (org.springframework.boot:spring-boot-starter-web:2.6.8 - https://spring.io/projects/spring-boot) - * spring-boot-test (org.springframework.boot:spring-boot-test:2.6.8 - https://spring.io/projects/spring-boot) - * spring-boot-test-autoconfigure (org.springframework.boot:spring-boot-test-autoconfigure:2.6.8 - https://spring.io/projects/spring-boot) - * Spring Data Core (org.springframework.data:spring-data-commons:2.6.4 - https://www.spring.io/spring-data/spring-data-commons) - * Spring Data REST - Core (org.springframework.data:spring-data-rest-core:3.6.4 - https://www.spring.io/spring-data/spring-data-rest-parent/spring-data-rest-core) - * Spring Data REST - WebMVC (org.springframework.data:spring-data-rest-webmvc:3.6.4 - https://www.spring.io/spring-data/spring-data-rest-parent/spring-data-rest-webmvc) - * Spring HATEOAS (org.springframework.hateoas:spring-hateoas:1.4.2 - https://github.com/spring-projects/spring-hateoas) + * spring-boot-starter (org.springframework.boot:spring-boot-starter:2.7.12 - https://spring.io/projects/spring-boot) + * spring-boot-starter-actuator (org.springframework.boot:spring-boot-starter-actuator:2.7.12 - https://spring.io/projects/spring-boot) + * spring-boot-starter-aop (org.springframework.boot:spring-boot-starter-aop:2.7.12 - https://spring.io/projects/spring-boot) + * spring-boot-starter-cache (org.springframework.boot:spring-boot-starter-cache:2.7.12 - https://spring.io/projects/spring-boot) + * spring-boot-starter-data-rest (org.springframework.boot:spring-boot-starter-data-rest:2.7.12 - https://spring.io/projects/spring-boot) + * spring-boot-starter-json (org.springframework.boot:spring-boot-starter-json:2.7.12 - https://spring.io/projects/spring-boot) + * spring-boot-starter-log4j2 (org.springframework.boot:spring-boot-starter-log4j2:2.7.12 - https://spring.io/projects/spring-boot) + * spring-boot-starter-security (org.springframework.boot:spring-boot-starter-security:2.7.12 - https://spring.io/projects/spring-boot) + * spring-boot-starter-test (org.springframework.boot:spring-boot-starter-test:2.7.12 - https://spring.io/projects/spring-boot) + * spring-boot-starter-tomcat (org.springframework.boot:spring-boot-starter-tomcat:2.7.12 - https://spring.io/projects/spring-boot) + * spring-boot-starter-web (org.springframework.boot:spring-boot-starter-web:2.7.12 - https://spring.io/projects/spring-boot) + * spring-boot-test (org.springframework.boot:spring-boot-test:2.7.12 - https://spring.io/projects/spring-boot) + * spring-boot-test-autoconfigure (org.springframework.boot:spring-boot-test-autoconfigure:2.7.12 - https://spring.io/projects/spring-boot) + * Spring Data Core (org.springframework.data:spring-data-commons:2.7.12 - https://www.spring.io/spring-data/spring-data-commons) + * Spring Data REST - Core (org.springframework.data:spring-data-rest-core:3.7.12 - https://www.spring.io/spring-data/spring-data-rest-parent/spring-data-rest-core) + * Spring Data REST - WebMVC (org.springframework.data:spring-data-rest-webmvc:3.7.12 - https://www.spring.io/spring-data/spring-data-rest-parent/spring-data-rest-webmvc) + * Spring HATEOAS (org.springframework.hateoas:spring-hateoas:1.5.4 - https://github.com/spring-projects/spring-hateoas) * Spring Plugin - Core (org.springframework.plugin:spring-plugin-core:2.0.0.RELEASE - https://github.com/spring-projects/spring-plugin/spring-plugin-core) - * spring-security-config (org.springframework.security:spring-security-config:5.6.5 - https://spring.io/projects/spring-security) - * spring-security-core (org.springframework.security:spring-security-core:5.6.5 - https://spring.io/projects/spring-security) - * spring-security-crypto (org.springframework.security:spring-security-crypto:5.6.5 - https://spring.io/projects/spring-security) - * spring-security-test (org.springframework.security:spring-security-test:5.6.5 - https://spring.io/projects/spring-security) - * spring-security-web (org.springframework.security:spring-security-web:5.6.5 - https://spring.io/projects/spring-security) + * spring-security-config (org.springframework.security:spring-security-config:5.7.8 - https://spring.io/projects/spring-security) + * spring-security-core (org.springframework.security:spring-security-core:5.7.8 - https://spring.io/projects/spring-security) + * spring-security-crypto (org.springframework.security:spring-security-crypto:5.7.8 - https://spring.io/projects/spring-security) + * spring-security-test (org.springframework.security:spring-security-test:5.7.8 - https://spring.io/projects/spring-security) + * spring-security-web (org.springframework.security:spring-security-web:5.7.8 - https://spring.io/projects/spring-security) * SWORD v2 :: Common Server Library (org.swordapp:sword2-server:1.0 - http://www.swordapp.org/) * snappy-java (org.xerial.snappy:snappy-java:1.1.7.6 - https://github.com/xerial/snappy-java) * xml-matchers (org.xmlmatchers:xml-matchers:0.10 - http://code.google.com/p/xml-matchers/) * org.xmlunit:xmlunit-core (org.xmlunit:xmlunit-core:2.8.0 - https://www.xmlunit.org/) - * org.xmlunit:xmlunit-core (org.xmlunit:xmlunit-core:2.8.4 - https://www.xmlunit.org/) + * org.xmlunit:xmlunit-core (org.xmlunit:xmlunit-core:2.9.1 - https://www.xmlunit.org/) * org.xmlunit:xmlunit-placeholders (org.xmlunit:xmlunit-placeholders:2.8.0 - https://www.xmlunit.org/xmlunit-placeholders/) - * SnakeYAML (org.yaml:snakeyaml:1.29 - http://www.snakeyaml.org) + * SnakeYAML (org.yaml:snakeyaml:1.30 - https://bitbucket.org/snakeyaml/snakeyaml) * software.amazon.ion:ion-java (software.amazon.ion:ion-java:1.0.2 - https://github.com/amznlabs/ion-java/) * Xalan Java Serializer (xalan:serializer:2.7.2 - http://xml.apache.org/xalan-j/) * xalan (xalan:xalan:2.7.0 - no url defined) @@ -404,7 +421,7 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * coverity-escapers (com.coverity.security:coverity-escapers:1.1.1 - http://coverity.com/security) * Java Advanced Imaging Image I/O Tools API core (standalone) (com.github.jai-imageio:jai-imageio-core:1.4.0 - https://github.com/jai-imageio/jai-imageio-core) * JSONLD Java :: Core (com.github.jsonld-java:jsonld-java:0.5.1 - http://github.com/jsonld-java/jsonld-java/jsonld-java/) - * curvesapi (com.github.virtuald:curvesapi:1.06 - https://github.com/virtuald/curvesapi) + * curvesapi (com.github.virtuald:curvesapi:1.07 - https://github.com/virtuald/curvesapi) * Protocol Buffers [Core] (com.google.protobuf:protobuf-java:3.11.0 - https://developers.google.com/protocol-buffers/protobuf-java/) * JZlib (com.jcraft:jzlib:1.1.3 - http://www.jcraft.com/jzlib/) * dnsjava (dnsjava:dnsjava:2.1.7 - http://www.dnsjava.org) @@ -426,11 +443,15 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * asm-commons (org.ow2.asm:asm-commons:8.0.1 - http://asm.ow2.io/) * asm-tree (org.ow2.asm:asm-tree:7.1 - http://asm.ow2.org/) * asm-util (org.ow2.asm:asm-util:7.1 - http://asm.ow2.org/) - * PostgreSQL JDBC Driver (org.postgresql:postgresql:42.4.3 - https://jdbc.postgresql.org) + * PostgreSQL JDBC Driver (org.postgresql:postgresql:42.6.0 - https://jdbc.postgresql.org) * Reflections (org.reflections:reflections:0.9.12 - http://github.com/ronmamo/reflections) * JMatIO (org.tallison:jmatio:1.5 - https://github.com/tballison/jmatio) * XMLUnit for Java (xmlunit:xmlunit:1.3 - http://xmlunit.sourceforge.net/) + CC0: + + * reactive-streams (org.reactivestreams:reactive-streams:1.0.2 - http://www.reactive-streams.org/) + Common Development and Distribution License (CDDL): * istack common utility code runtime (com.sun.istack:istack-commons-runtime:3.0.7 - http://java.net/istack-commons/istack-commons-runtime/) @@ -446,7 +467,7 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * Java Servlet API (javax.servlet:javax.servlet-api:3.1.0 - http://servlet-spec.java.net) * javax.transaction API (javax.transaction:javax.transaction-api:1.3 - http://jta-spec.java.net) * jaxb-api (javax.xml.bind:jaxb-api:2.3.1 - https://github.com/javaee/jaxb-spec/jaxb-api) - * JHighlight (org.codelibs:jhighlight:1.0.3 - https://github.com/codelibs/jhighlight) + * JHighlight (org.codelibs:jhighlight:1.1.0 - https://github.com/codelibs/jhighlight) * HK2 API module (org.glassfish.hk2:hk2-api:2.6.1 - https://github.com/eclipse-ee4j/glassfish-hk2/hk2-api) * ServiceLocator Default Implementation (org.glassfish.hk2:hk2-locator:2.6.1 - https://github.com/eclipse-ee4j/glassfish-hk2/hk2-locator) * HK2 Implementation Utilities (org.glassfish.hk2:hk2-utils:2.6.1 - https://github.com/eclipse-ee4j/glassfish-hk2/hk2-utils) @@ -489,34 +510,34 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * Apache :: JSTL module (org.eclipse.jetty:apache-jstl:9.4.15.v20190215 - http://tomcat.apache.org/taglibs/standard/) * Jetty :: ALPN :: Client (org.eclipse.jetty:jetty-alpn-client:9.4.44.v20210927 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-client) * Jetty :: ALPN :: JDK9 Client Implementation (org.eclipse.jetty:jetty-alpn-java-client:9.4.44.v20210927 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-java-client) - * Jetty :: ALPN :: JDK9 Server Implementation (org.eclipse.jetty:jetty-alpn-java-server:9.4.48.v20220622 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-java-server) + * Jetty :: ALPN :: JDK9 Server Implementation (org.eclipse.jetty:jetty-alpn-java-server:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-java-server) * Jetty :: ALPN :: Server (org.eclipse.jetty:jetty-alpn-server:9.4.44.v20210927 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-server) - * Jetty :: ALPN :: Server (org.eclipse.jetty:jetty-alpn-server:9.4.48.v20220622 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-server) + * Jetty :: ALPN :: Server (org.eclipse.jetty:jetty-alpn-server:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-server) * Jetty :: Servlet Annotations (org.eclipse.jetty:jetty-annotations:9.4.15.v20190215 - http://www.eclipse.org/jetty) * Jetty :: Asynchronous HTTP Client (org.eclipse.jetty:jetty-client:9.4.44.v20210927 - https://eclipse.org/jetty/jetty-client) * Jetty :: Continuation (org.eclipse.jetty:jetty-continuation:9.4.44.v20210927 - https://eclipse.org/jetty/jetty-continuation) - * Jetty :: Continuation (org.eclipse.jetty:jetty-continuation:9.4.48.v20220622 - https://eclipse.org/jetty/jetty-continuation) - * Jetty :: Deployers (org.eclipse.jetty:jetty-deploy:9.4.48.v20220622 - https://eclipse.org/jetty/jetty-deploy) - * Jetty :: Http Utility (org.eclipse.jetty:jetty-http:9.4.48.v20220622 - https://eclipse.org/jetty/jetty-http) - * Jetty :: IO Utility (org.eclipse.jetty:jetty-io:9.4.48.v20220622 - https://eclipse.org/jetty/jetty-io) + * Jetty :: Continuation (org.eclipse.jetty:jetty-continuation:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-continuation) + * Jetty :: Deployers (org.eclipse.jetty:jetty-deploy:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-deploy) + * Jetty :: Http Utility (org.eclipse.jetty:jetty-http:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-http) + * Jetty :: IO Utility (org.eclipse.jetty:jetty-io:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-io) * Jetty :: JMX Management (org.eclipse.jetty:jetty-jmx:9.4.44.v20210927 - https://eclipse.org/jetty/jetty-jmx) * Jetty :: JNDI Naming (org.eclipse.jetty:jetty-jndi:9.4.15.v20190215 - http://www.eclipse.org/jetty) * Jetty :: Plus (org.eclipse.jetty:jetty-plus:9.4.15.v20190215 - http://www.eclipse.org/jetty) * Jetty :: Rewrite Handler (org.eclipse.jetty:jetty-rewrite:9.4.44.v20210927 - https://eclipse.org/jetty/jetty-rewrite) * Jetty :: Security (org.eclipse.jetty:jetty-security:9.4.44.v20210927 - https://eclipse.org/jetty/jetty-security) - * Jetty :: Security (org.eclipse.jetty:jetty-security:9.4.48.v20220622 - https://eclipse.org/jetty/jetty-security) - * Jetty :: Server Core (org.eclipse.jetty:jetty-server:9.4.48.v20220622 - https://eclipse.org/jetty/jetty-server) - * Jetty :: Servlet Handling (org.eclipse.jetty:jetty-servlet:9.4.48.v20220622 - https://eclipse.org/jetty/jetty-servlet) - * Jetty :: Utility Servlets and Filters (org.eclipse.jetty:jetty-servlets:9.4.48.v20220622 - https://eclipse.org/jetty/jetty-servlets) - * Jetty :: Utilities (org.eclipse.jetty:jetty-util:9.4.48.v20220622 - https://eclipse.org/jetty/jetty-util) - * Jetty :: Utilities :: Ajax(JSON) (org.eclipse.jetty:jetty-util-ajax:9.4.48.v20220622 - https://eclipse.org/jetty/jetty-util-ajax) - * Jetty :: Webapp Application Support (org.eclipse.jetty:jetty-webapp:9.4.48.v20220622 - https://eclipse.org/jetty/jetty-webapp) - * Jetty :: XML utilities (org.eclipse.jetty:jetty-xml:9.4.48.v20220622 - https://eclipse.org/jetty/jetty-xml) + * Jetty :: Security (org.eclipse.jetty:jetty-security:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-security) + * Jetty :: Server Core (org.eclipse.jetty:jetty-server:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-server) + * Jetty :: Servlet Handling (org.eclipse.jetty:jetty-servlet:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-servlet) + * Jetty :: Utility Servlets and Filters (org.eclipse.jetty:jetty-servlets:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-servlets) + * Jetty :: Utilities (org.eclipse.jetty:jetty-util:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-util) + * Jetty :: Utilities :: Ajax(JSON) (org.eclipse.jetty:jetty-util-ajax:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-util-ajax) + * Jetty :: Webapp Application Support (org.eclipse.jetty:jetty-webapp:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-webapp) + * Jetty :: XML utilities (org.eclipse.jetty:jetty-xml:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-xml) * Jetty :: HTTP2 :: Client (org.eclipse.jetty.http2:http2-client:9.4.44.v20210927 - https://eclipse.org/jetty/http2-parent/http2-client) - * Jetty :: HTTP2 :: Common (org.eclipse.jetty.http2:http2-common:9.4.48.v20220622 - https://eclipse.org/jetty/http2-parent/http2-common) + * Jetty :: HTTP2 :: Common (org.eclipse.jetty.http2:http2-common:9.4.51.v20230217 - https://eclipse.org/jetty/http2-parent/http2-common) * Jetty :: HTTP2 :: HPACK (org.eclipse.jetty.http2:http2-hpack:9.4.44.v20210927 - https://eclipse.org/jetty/http2-parent/http2-hpack) * Jetty :: HTTP2 :: HTTP Client Transport (org.eclipse.jetty.http2:http2-http-client-transport:9.4.44.v20210927 - https://eclipse.org/jetty/http2-parent/http2-http-client-transport) - * Jetty :: HTTP2 :: Server (org.eclipse.jetty.http2:http2-server:9.4.48.v20220622 - https://eclipse.org/jetty/http2-parent/http2-server) + * Jetty :: HTTP2 :: Server (org.eclipse.jetty.http2:http2-server:9.4.51.v20230217 - https://eclipse.org/jetty/http2-parent/http2-server) * Jetty :: Schemas (org.eclipse.jetty.toolchain:jetty-schemas:3.1.2 - https://eclipse.org/jetty/jetty-schemas) * HK2 API module (org.glassfish.hk2:hk2-api:2.6.1 - https://github.com/eclipse-ee4j/glassfish-hk2/hk2-api) * ServiceLocator Default Implementation (org.glassfish.hk2:hk2-locator:2.6.1 - https://github.com/eclipse-ee4j/glassfish-hk2/hk2-locator) @@ -542,10 +563,10 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * msg-simple (com.github.java-json-tools:msg-simple:1.2 - https://github.com/java-json-tools/msg-simple) * uri-template (com.github.java-json-tools:uri-template:0.10 - https://github.com/java-json-tools/uri-template) * FindBugs-Annotations (com.google.code.findbugs:annotations:3.0.1u2 - http://findbugs.sourceforge.net/) - * JHighlight (org.codelibs:jhighlight:1.0.3 - https://github.com/codelibs/jhighlight) - * Hibernate ORM - hibernate-core (org.hibernate:hibernate-core:5.6.5.Final - https://hibernate.org/orm) - * Hibernate ORM - hibernate-jcache (org.hibernate:hibernate-jcache:5.6.5.Final - https://hibernate.org/orm) - * Hibernate ORM - hibernate-jpamodelgen (org.hibernate:hibernate-jpamodelgen:5.6.5.Final - https://hibernate.org/orm) + * JHighlight (org.codelibs:jhighlight:1.1.0 - https://github.com/codelibs/jhighlight) + * Hibernate ORM - hibernate-core (org.hibernate:hibernate-core:5.6.15.Final - https://hibernate.org/orm) + * Hibernate ORM - hibernate-jcache (org.hibernate:hibernate-jcache:5.6.15.Final - https://hibernate.org/orm) + * Hibernate ORM - hibernate-jpamodelgen (org.hibernate:hibernate-jpamodelgen:5.6.15.Final - https://hibernate.org/orm) * Hibernate Commons Annotations (org.hibernate.common:hibernate-commons-annotations:5.1.2.Final - http://hibernate.org) * im4java (org.im4java:im4java:1.4.0 - http://sourceforge.net/projects/im4java/) * Javassist (org.javassist:javassist:3.25.0-GA - http://www.javassist.org/) @@ -562,9 +583,11 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines MIT License: + * better-files (com.github.pathikrit:better-files_2.13:3.9.1 - https://github.com/pathikrit/better-files) * Java SemVer (com.github.zafarkhaja:java-semver:0.9.0 - https://github.com/zafarkhaja/jsemver) - * dd-plist (com.googlecode.plist:dd-plist:1.23 - http://www.github.com/3breadt/dd-plist) + * dd-plist (com.googlecode.plist:dd-plist:1.25 - http://www.github.com/3breadt/dd-plist) * DigitalCollections: IIIF API Library (de.digitalcollections.iiif:iiif-apis:0.3.9 - https://github.com/dbmdz/iiif-apis) + * s3mock (io.findify:s3mock_2.13:0.2.6 - https://github.com/findify/s3mock) * JOpt Simple (net.sf.jopt-simple:jopt-simple:5.0.4 - http://jopt-simple.github.io/jopt-simple) * Bouncy Castle S/MIME API (org.bouncycastle:bcmail-jdk15on:1.70 - https://www.bouncycastle.org/java.html) * Bouncy Castle PKIX, CMS, EAC, TSP, PKCS, OCSP, CMP, and CRMF APIs (org.bouncycastle:bcpkix-jdk15on:1.70 - https://www.bouncycastle.org/java.html) @@ -572,15 +595,14 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * Bouncy Castle ASN.1 Extension and Utility APIs (org.bouncycastle:bcutil-jdk15on:1.70 - https://www.bouncycastle.org/java.html) * org.brotli:dec (org.brotli:dec:0.1.2 - http://brotli.org/dec) * Checker Qual (org.checkerframework:checker-qual:3.10.0 - https://checkerframework.org) - * Checker Qual (org.checkerframework:checker-qual:3.5.0 - https://checkerframework.org) + * Checker Qual (org.checkerframework:checker-qual:3.31.0 - https://checkerframework.org) * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.35 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.35 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) * 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) - * JCL 1.2 implemented over SLF4J (org.slf4j:jcl-over-slf4j:1.7.25 - http://www.slf4j.org) - * JUL to SLF4J bridge (org.slf4j:jul-to-slf4j:1.7.25 - http://www.slf4j.org) - * SLF4J API Module (org.slf4j:slf4j-api:1.7.25 - http://www.slf4j.org) + * JUL to SLF4J bridge (org.slf4j:jul-to-slf4j:1.7.36 - http://www.slf4j.org) + * SLF4J API Module (org.slf4j:slf4j-api:1.7.36 - http://www.slf4j.org) * SLF4J Extensions Module (org.slf4j:slf4j-ext:1.7.28 - http://www.slf4j.org) * HAL Browser (org.webjars:hal-browser:ad9b865 - http://webjars.org) * toastr (org.webjars.bowergithub.codeseven:toastr:2.1.4 - http://webjars.org) @@ -589,7 +611,7 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * jquery (org.webjars.bowergithub.jquery:jquery-dist:3.6.0 - https://www.webjars.org) * urijs (org.webjars.bowergithub.medialize:uri.js:1.19.10 - https://www.webjars.org) * bootstrap (org.webjars.bowergithub.twbs:bootstrap:4.6.1 - https://www.webjars.org) - * core-js (org.webjars.npm:core-js:3.28.0 - https://www.webjars.org) + * core-js (org.webjars.npm:core-js:3.30.1 - https://www.webjars.org) * @json-editor/json-editor (org.webjars.npm:json-editor__json-editor:2.6.1 - https://www.webjars.org) Mozilla Public License: @@ -606,17 +628,14 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * jersey-core-common (org.glassfish.jersey.core:jersey-common:2.35 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-common) * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.35 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) * HdrHistogram (org.hdrhistogram:HdrHistogram:2.1.12 - http://hdrhistogram.github.io/HdrHistogram/) + * JSON in Java (org.json:json:20230227 - https://github.com/douglascrockford/JSON-java) * 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) - The JSON License: - - * JSON in Java (org.json:json:20180130 - https://github.com/douglascrockford/JSON-java) - UnRar License: - * Java Unrar (com.github.junrar:junrar:7.4.1 - https://github.com/junrar/junrar) + * Java Unrar (com.github.junrar:junrar:7.5.3 - https://github.com/junrar/junrar) Unicode/ICU License: From 5a43e6bcf1adb1e053be4dd46b566c604923c8a9 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 23 Jun 2023 12:10:53 -0500 Subject: [PATCH 280/510] [maven-release-plugin] prepare release dspace-7.6 --- dspace-api/pom.xml | 2 +- dspace-iiif/pom.xml | 2 +- dspace-oai/pom.xml | 2 +- dspace-rdf/pom.xml | 2 +- dspace-rest/pom.xml | 4 ++-- dspace-server-webapp/pom.xml | 2 +- dspace-services/pom.xml | 2 +- dspace-sword/pom.xml | 2 +- dspace-swordv2/pom.xml | 2 +- dspace/modules/additions/pom.xml | 2 +- dspace/modules/pom.xml | 2 +- dspace/modules/rest/pom.xml | 2 +- dspace/modules/server/pom.xml | 2 +- dspace/pom.xml | 2 +- pom.xml | 32 ++++++++++++++++---------------- 15 files changed, 31 insertions(+), 31 deletions(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index d41c51638c3a..d0d2bf8608ae 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -12,7 +12,7 @@ org.dspace dspace-parent - 7.6-SNAPSHOT + 7.6 .. diff --git a/dspace-iiif/pom.xml b/dspace-iiif/pom.xml index 7e26e22fa2ea..8ffa26e96751 100644 --- a/dspace-iiif/pom.xml +++ b/dspace-iiif/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.6-SNAPSHOT + 7.6 .. diff --git a/dspace-oai/pom.xml b/dspace-oai/pom.xml index 8692482d7889..8b6a3ee41486 100644 --- a/dspace-oai/pom.xml +++ b/dspace-oai/pom.xml @@ -8,7 +8,7 @@ dspace-parent org.dspace - 7.6-SNAPSHOT + 7.6 .. diff --git a/dspace-rdf/pom.xml b/dspace-rdf/pom.xml index 95354621aa13..80c0614781c9 100644 --- a/dspace-rdf/pom.xml +++ b/dspace-rdf/pom.xml @@ -9,7 +9,7 @@ org.dspace dspace-parent - 7.6-SNAPSHOT + 7.6 .. diff --git a/dspace-rest/pom.xml b/dspace-rest/pom.xml index 7fdf21ef4ce6..dcc47b175369 100644 --- a/dspace-rest/pom.xml +++ b/dspace-rest/pom.xml @@ -3,7 +3,7 @@ org.dspace dspace-rest war - 7.6-SNAPSHOT + 7.6 DSpace (Deprecated) REST Webapp DSpace RESTful Web Services API. NOTE: this REST API is DEPRECATED. Please consider using the REST API in the dspace-server-webapp instead! @@ -12,7 +12,7 @@ org.dspace dspace-parent - 7.6-SNAPSHOT + 7.6 .. diff --git a/dspace-server-webapp/pom.xml b/dspace-server-webapp/pom.xml index 99aa88bebf0c..6f4b2871df9c 100644 --- a/dspace-server-webapp/pom.xml +++ b/dspace-server-webapp/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.6-SNAPSHOT + 7.6 .. diff --git a/dspace-services/pom.xml b/dspace-services/pom.xml index f3112b049bdd..488fa05c9633 100644 --- a/dspace-services/pom.xml +++ b/dspace-services/pom.xml @@ -9,7 +9,7 @@ org.dspace dspace-parent - 7.6-SNAPSHOT + 7.6 diff --git a/dspace-sword/pom.xml b/dspace-sword/pom.xml index fd68337bbc3a..02f293dbbb9d 100644 --- a/dspace-sword/pom.xml +++ b/dspace-sword/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.6-SNAPSHOT + 7.6 .. diff --git a/dspace-swordv2/pom.xml b/dspace-swordv2/pom.xml index 9badeb2fe80a..3bc838d3ea22 100644 --- a/dspace-swordv2/pom.xml +++ b/dspace-swordv2/pom.xml @@ -13,7 +13,7 @@ org.dspace dspace-parent - 7.6-SNAPSHOT + 7.6 .. diff --git a/dspace/modules/additions/pom.xml b/dspace/modules/additions/pom.xml index dd98bf0cbdd1..100561849da1 100644 --- a/dspace/modules/additions/pom.xml +++ b/dspace/modules/additions/pom.xml @@ -17,7 +17,7 @@ org.dspace modules - 7.6-SNAPSHOT + 7.6 .. diff --git a/dspace/modules/pom.xml b/dspace/modules/pom.xml index b60246ba6cc9..c12b13a38df9 100644 --- a/dspace/modules/pom.xml +++ b/dspace/modules/pom.xml @@ -11,7 +11,7 @@ org.dspace dspace-parent - 7.6-SNAPSHOT + 7.6 ../../pom.xml diff --git a/dspace/modules/rest/pom.xml b/dspace/modules/rest/pom.xml index 115393b7db20..4f3e7cc1c6b2 100644 --- a/dspace/modules/rest/pom.xml +++ b/dspace/modules/rest/pom.xml @@ -13,7 +13,7 @@ org.dspace modules - 7.6-SNAPSHOT + 7.6 .. diff --git a/dspace/modules/server/pom.xml b/dspace/modules/server/pom.xml index 41ddb94be5a9..c3921bfa2704 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-SNAPSHOT + 7.6 .. diff --git a/dspace/pom.xml b/dspace/pom.xml index 7916648e4784..75f16a420597 100644 --- a/dspace/pom.xml +++ b/dspace/pom.xml @@ -16,7 +16,7 @@ org.dspace dspace-parent - 7.6-SNAPSHOT + 7.6 ../pom.xml diff --git a/pom.xml b/pom.xml index f8e873c8d4d5..432a928b5b96 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ org.dspace dspace-parent pom - 7.6-SNAPSHOT + 7.6 DSpace Parent Project DSpace open source software is a turnkey institutional repository application. @@ -872,14 +872,14 @@ org.dspace dspace-rest - 7.6-SNAPSHOT + 7.6 jar classes org.dspace dspace-rest - 7.6-SNAPSHOT + 7.6 war @@ -1030,69 +1030,69 @@ org.dspace dspace-api - 7.6-SNAPSHOT + 7.6 org.dspace dspace-api test-jar - 7.6-SNAPSHOT + 7.6 test org.dspace.modules additions - 7.6-SNAPSHOT + 7.6 org.dspace dspace-sword - 7.6-SNAPSHOT + 7.6 org.dspace dspace-swordv2 - 7.6-SNAPSHOT + 7.6 org.dspace dspace-oai - 7.6-SNAPSHOT + 7.6 org.dspace dspace-services - 7.6-SNAPSHOT + 7.6 org.dspace dspace-server-webapp test-jar - 7.6-SNAPSHOT + 7.6 test org.dspace dspace-rdf - 7.6-SNAPSHOT + 7.6 org.dspace dspace-iiif - 7.6-SNAPSHOT + 7.6 org.dspace dspace-server-webapp - 7.6-SNAPSHOT + 7.6 jar classes org.dspace dspace-server-webapp - 7.6-SNAPSHOT + 7.6 war @@ -1927,7 +1927,7 @@ scm:git:git@github.com:DSpace/DSpace.git scm:git:git@github.com:DSpace/DSpace.git git@github.com:DSpace/DSpace.git - HEAD + dspace-7.6 From a533704a27ed97f16125590c4569589991119356 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 23 Jun 2023 12:10:56 -0500 Subject: [PATCH 281/510] [maven-release-plugin] prepare for next development iteration --- dspace-api/pom.xml | 2 +- dspace-iiif/pom.xml | 2 +- dspace-oai/pom.xml | 2 +- dspace-rdf/pom.xml | 2 +- dspace-rest/pom.xml | 4 ++-- dspace-server-webapp/pom.xml | 2 +- dspace-services/pom.xml | 2 +- dspace-sword/pom.xml | 2 +- dspace-swordv2/pom.xml | 2 +- dspace/modules/additions/pom.xml | 2 +- dspace/modules/pom.xml | 2 +- dspace/modules/rest/pom.xml | 2 +- dspace/modules/server/pom.xml | 2 +- dspace/pom.xml | 2 +- pom.xml | 32 ++++++++++++++++---------------- 15 files changed, 31 insertions(+), 31 deletions(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index d0d2bf8608ae..0e70fc52e085 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -12,7 +12,7 @@ org.dspace dspace-parent - 7.6 + 7.6.1-SNAPSHOT .. diff --git a/dspace-iiif/pom.xml b/dspace-iiif/pom.xml index 8ffa26e96751..30e20489b82c 100644 --- a/dspace-iiif/pom.xml +++ b/dspace-iiif/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.6 + 7.6.1-SNAPSHOT .. diff --git a/dspace-oai/pom.xml b/dspace-oai/pom.xml index 8b6a3ee41486..08e732d45733 100644 --- a/dspace-oai/pom.xml +++ b/dspace-oai/pom.xml @@ -8,7 +8,7 @@ dspace-parent org.dspace - 7.6 + 7.6.1-SNAPSHOT .. diff --git a/dspace-rdf/pom.xml b/dspace-rdf/pom.xml index 80c0614781c9..09c3e704de42 100644 --- a/dspace-rdf/pom.xml +++ b/dspace-rdf/pom.xml @@ -9,7 +9,7 @@ org.dspace dspace-parent - 7.6 + 7.6.1-SNAPSHOT .. diff --git a/dspace-rest/pom.xml b/dspace-rest/pom.xml index dcc47b175369..320567f3c104 100644 --- a/dspace-rest/pom.xml +++ b/dspace-rest/pom.xml @@ -3,7 +3,7 @@ org.dspace dspace-rest war - 7.6 + 7.6.1-SNAPSHOT DSpace (Deprecated) REST Webapp DSpace RESTful Web Services API. NOTE: this REST API is DEPRECATED. Please consider using the REST API in the dspace-server-webapp instead! @@ -12,7 +12,7 @@ org.dspace dspace-parent - 7.6 + 7.6.1-SNAPSHOT .. diff --git a/dspace-server-webapp/pom.xml b/dspace-server-webapp/pom.xml index 6f4b2871df9c..2221f9ca0e62 100644 --- a/dspace-server-webapp/pom.xml +++ b/dspace-server-webapp/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.6 + 7.6.1-SNAPSHOT .. diff --git a/dspace-services/pom.xml b/dspace-services/pom.xml index 488fa05c9633..fe7c6ab8b69d 100644 --- a/dspace-services/pom.xml +++ b/dspace-services/pom.xml @@ -9,7 +9,7 @@ org.dspace dspace-parent - 7.6 + 7.6.1-SNAPSHOT diff --git a/dspace-sword/pom.xml b/dspace-sword/pom.xml index 02f293dbbb9d..777041775d46 100644 --- a/dspace-sword/pom.xml +++ b/dspace-sword/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.6 + 7.6.1-SNAPSHOT .. diff --git a/dspace-swordv2/pom.xml b/dspace-swordv2/pom.xml index 3bc838d3ea22..c3fbdecb79d4 100644 --- a/dspace-swordv2/pom.xml +++ b/dspace-swordv2/pom.xml @@ -13,7 +13,7 @@ org.dspace dspace-parent - 7.6 + 7.6.1-SNAPSHOT .. diff --git a/dspace/modules/additions/pom.xml b/dspace/modules/additions/pom.xml index 100561849da1..dd6dbcb1ffd7 100644 --- a/dspace/modules/additions/pom.xml +++ b/dspace/modules/additions/pom.xml @@ -17,7 +17,7 @@ org.dspace modules - 7.6 + 7.6.1-SNAPSHOT .. diff --git a/dspace/modules/pom.xml b/dspace/modules/pom.xml index c12b13a38df9..e66622ce6b34 100644 --- a/dspace/modules/pom.xml +++ b/dspace/modules/pom.xml @@ -11,7 +11,7 @@ org.dspace dspace-parent - 7.6 + 7.6.1-SNAPSHOT ../../pom.xml diff --git a/dspace/modules/rest/pom.xml b/dspace/modules/rest/pom.xml index 4f3e7cc1c6b2..14c6ff93f761 100644 --- a/dspace/modules/rest/pom.xml +++ b/dspace/modules/rest/pom.xml @@ -13,7 +13,7 @@ org.dspace modules - 7.6 + 7.6.1-SNAPSHOT .. diff --git a/dspace/modules/server/pom.xml b/dspace/modules/server/pom.xml index c3921bfa2704..bebb6d183d7e 100644 --- a/dspace/modules/server/pom.xml +++ b/dspace/modules/server/pom.xml @@ -13,7 +13,7 @@ just adding new jar in the classloader modules org.dspace - 7.6 + 7.6.1-SNAPSHOT .. diff --git a/dspace/pom.xml b/dspace/pom.xml index 75f16a420597..bfa21f5d664f 100644 --- a/dspace/pom.xml +++ b/dspace/pom.xml @@ -16,7 +16,7 @@ org.dspace dspace-parent - 7.6 + 7.6.1-SNAPSHOT ../pom.xml diff --git a/pom.xml b/pom.xml index 432a928b5b96..4c9799243f8c 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ org.dspace dspace-parent pom - 7.6 + 7.6.1-SNAPSHOT DSpace Parent Project DSpace open source software is a turnkey institutional repository application. @@ -872,14 +872,14 @@ org.dspace dspace-rest - 7.6 + 7.6.1-SNAPSHOT jar classes org.dspace dspace-rest - 7.6 + 7.6.1-SNAPSHOT war @@ -1030,69 +1030,69 @@ org.dspace dspace-api - 7.6 + 7.6.1-SNAPSHOT org.dspace dspace-api test-jar - 7.6 + 7.6.1-SNAPSHOT test org.dspace.modules additions - 7.6 + 7.6.1-SNAPSHOT org.dspace dspace-sword - 7.6 + 7.6.1-SNAPSHOT org.dspace dspace-swordv2 - 7.6 + 7.6.1-SNAPSHOT org.dspace dspace-oai - 7.6 + 7.6.1-SNAPSHOT org.dspace dspace-services - 7.6 + 7.6.1-SNAPSHOT org.dspace dspace-server-webapp test-jar - 7.6 + 7.6.1-SNAPSHOT test org.dspace dspace-rdf - 7.6 + 7.6.1-SNAPSHOT org.dspace dspace-iiif - 7.6 + 7.6.1-SNAPSHOT org.dspace dspace-server-webapp - 7.6 + 7.6.1-SNAPSHOT jar classes org.dspace dspace-server-webapp - 7.6 + 7.6.1-SNAPSHOT war @@ -1927,7 +1927,7 @@ scm:git:git@github.com:DSpace/DSpace.git scm:git:git@github.com:DSpace/DSpace.git git@github.com:DSpace/DSpace.git - dspace-7.6 + HEAD From 6f2d5cab0517c0c4096db79c6745118647069c0b Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Tue, 6 Jun 2023 15:46:52 -0400 Subject: [PATCH 282/510] Replace Joda Time classes with java.time. --- .../status/AccessStatusServiceImpl.java | 8 ++- .../org/dspace/authority/AuthorityValue.java | 65 ++++++++++++------- .../CrossRefDateMetadataProcessor.java | 27 ++++---- .../status/DefaultAccessStatusHelperTest.java | 28 ++++++-- .../dspace/app/util/GoogleMetadataTest.java | 22 ++++--- .../dspace/authority/AuthorityValueTest.java | 44 +++++++++++++ .../builder/AbstractDSpaceObjectBuilder.java | 55 +++++++++------- .../org/dspace/builder/BitstreamBuilder.java | 3 +- .../java/org/dspace/builder/ItemBuilder.java | 3 +- .../CrossRefDateMetadataProcessorTest.java | 32 +++++++++ .../app/rest/BitstreamRestControllerIT.java | 9 +-- .../app/rest/BitstreamRestRepositoryIT.java | 21 +++--- .../app/rest/BrowsesResourceControllerIT.java | 6 +- .../app/rest/DiscoveryRestControllerIT.java | 10 ++- .../dspace/app/rest/ItemRestRepositoryIT.java | 48 +++++++------- 15 files changed, 260 insertions(+), 121 deletions(-) create mode 100644 dspace-api/src/test/java/org/dspace/authority/AuthorityValueTest.java create mode 100644 dspace-api/src/test/java/org/dspace/importer/external/crossref/CrossRefDateMetadataProcessorTest.java diff --git a/dspace-api/src/main/java/org/dspace/access/status/AccessStatusServiceImpl.java b/dspace-api/src/main/java/org/dspace/access/status/AccessStatusServiceImpl.java index 544dc99cb4dd..1ce3673452bb 100644 --- a/dspace-api/src/main/java/org/dspace/access/status/AccessStatusServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/access/status/AccessStatusServiceImpl.java @@ -8,6 +8,8 @@ package org.dspace.access.status; import java.sql.SQLException; +import java.time.LocalDate; +import java.time.ZoneId; import java.util.Date; import org.dspace.access.status.service.AccessStatusService; @@ -15,7 +17,6 @@ import org.dspace.core.Context; import org.dspace.core.service.PluginService; import org.dspace.services.ConfigurationService; -import org.joda.time.LocalDate; import org.springframework.beans.factory.annotation.Autowired; /** @@ -55,7 +56,10 @@ public void init() throws Exception { int month = configurationService.getIntProperty("access.status.embargo.forever.month"); int day = configurationService.getIntProperty("access.status.embargo.forever.day"); - forever_date = new LocalDate(year, month, day).toDate(); + forever_date = Date.from(LocalDate.of(year, month, day) + .atStartOfDay() + .atZone(ZoneId.systemDefault()) + .toInstant()); } } diff --git a/dspace-api/src/main/java/org/dspace/authority/AuthorityValue.java b/dspace-api/src/main/java/org/dspace/authority/AuthorityValue.java index 10a608bb7660..6ca0292fdb1b 100644 --- a/dspace-api/src/main/java/org/dspace/authority/AuthorityValue.java +++ b/dspace-api/src/main/java/org/dspace/authority/AuthorityValue.java @@ -9,6 +9,10 @@ import java.sql.SQLException; import java.text.DateFormat; +import java.time.DateTimeException; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; @@ -16,6 +20,7 @@ import java.util.Map; import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.solr.common.SolrDocument; import org.apache.solr.common.SolrInputDocument; @@ -25,9 +30,6 @@ import org.dspace.content.factory.ContentServiceFactory; import org.dspace.core.Context; import org.dspace.util.SolrUtils; -import org.joda.time.DateTime; -import org.joda.time.format.DateTimeFormatter; -import org.joda.time.format.ISODateTimeFormat; /** * @author Antoine Snyers (antoine at atmire.com) @@ -192,7 +194,7 @@ public void updateItem(Context context, Item currentItem, MetadataValue value) } /** - * Information that can be used the choice ui + * Information that can be used the choice ui. * * @return map */ @@ -200,42 +202,51 @@ public Map choiceSelectMap() { return new HashMap<>(); } - - public List getDateFormatters() { - List list = new ArrayList<>(); - list.add(ISODateTimeFormat.dateTime()); - list.add(ISODateTimeFormat.dateTimeNoMillis()); + /** + * Build a list of ISO date formatters to parse various forms. + * + *

Note: any formatter which does not parse a zone or + * offset must have a default zone set. See {@link stringToDate}. + * + * @return the formatters. + */ + static private List getDateFormatters() { + List list = new ArrayList<>(); + list.add(java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss[.SSS]X")); + list.add(java.time.format.DateTimeFormatter.ISO_LOCAL_DATE_TIME + .withZone(ZoneId.systemDefault().normalized())); return list; } - public Date stringToDate(String date) { + /** + * Convert a date string to internal form, trying several parsers. + * + * @param date serialized date to be converted. + * @return converted date, or null if no parser accepted the input. + */ + static public Date stringToDate(String date) { Date result = null; if (StringUtils.isNotBlank(date)) { - List dateFormatters = getDateFormatters(); - boolean converted = false; - int formatter = 0; - while (!converted) { + for (DateTimeFormatter formatter : getDateFormatters()) { try { - DateTimeFormatter dateTimeFormatter = dateFormatters.get(formatter); - DateTime dateTime = dateTimeFormatter.parseDateTime(date); - result = dateTime.toDate(); - converted = true; - } catch (IllegalArgumentException e) { - formatter++; - if (formatter > dateFormatters.size()) { - converted = true; - } - log.error("Could not find a valid date format for: \"" + date + "\"", e); + ZonedDateTime dateTime = ZonedDateTime.parse(date, formatter); + result = Date.from(dateTime.toInstant()); + break; + } catch (DateTimeException e) { + log.debug("Input '{}' did not match {}", date, formatter); } } } + if (null == result) { + log.error("Could not find a valid date format for: \"{}\"", date); + } return result; } /** * log4j logger */ - private static Logger log = org.apache.logging.log4j.LogManager.getLogger(AuthorityValue.class); + private static Logger log = LogManager.getLogger(); @Override public String toString() { @@ -272,6 +283,10 @@ public AuthorityValue newInstance(String info) { return new AuthorityValue(); } + /** + * Get the type of authority which created this value. + * @return type name. + */ public String getAuthorityType() { return "internal"; } diff --git a/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefDateMetadataProcessor.java b/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefDateMetadataProcessor.java index dec0b050f396..c83abbf2b285 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefDateMetadataProcessor.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefDateMetadataProcessor.java @@ -7,7 +7,8 @@ */ package org.dspace.importer.external.crossref; -import java.text.SimpleDateFormat; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; @@ -18,12 +19,11 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.dspace.importer.external.metadatamapping.contributor.JsonPathMetadataProcessor; -import org.joda.time.LocalDate; /** * This class is used for CrossRef's Live-Import to extract * issued attribute. - * Beans are configured in the crossref-integration.xml file. + * Beans are configured in the {@code crossref-integration.xml} file. * * @author Francesco Pio Scognamiglio (francescopio.scognamiglio at 4science.com) */ @@ -41,22 +41,25 @@ public Collection processMetadata(String json) { while (dates.hasNext()) { JsonNode date = dates.next(); LocalDate issuedDate = null; - SimpleDateFormat issuedDateFormat = null; + DateTimeFormatter issuedDateFormat = null; if (date.has(0) && date.has(1) && date.has(2)) { - issuedDate = new LocalDate( + issuedDate = LocalDate.of( date.get(0).numberValue().intValue(), date.get(1).numberValue().intValue(), date.get(2).numberValue().intValue()); - issuedDateFormat = new SimpleDateFormat("yyyy-MM-dd"); + issuedDateFormat = DateTimeFormatter.ISO_LOCAL_DATE; } else if (date.has(0) && date.has(1)) { - issuedDate = new LocalDate().withYear(date.get(0).numberValue().intValue()) - .withMonthOfYear(date.get(1).numberValue().intValue()); - issuedDateFormat = new SimpleDateFormat("yyyy-MM"); + issuedDate = LocalDate.of(date.get(0).numberValue().intValue(), + date.get(1).numberValue().intValue(), + 1); + issuedDateFormat = DateTimeFormatter.ofPattern("yyyy-MM"); } else if (date.has(0)) { - issuedDate = new LocalDate().withYear(date.get(0).numberValue().intValue()); - issuedDateFormat = new SimpleDateFormat("yyyy"); + issuedDate = LocalDate.of(date.get(0).numberValue().intValue(), + 1, + 1); + issuedDateFormat = DateTimeFormatter.ofPattern("yyyy"); } - values.add(issuedDateFormat.format(issuedDate.toDate())); + values.add(issuedDate.format(issuedDateFormat)); } return values; } diff --git a/dspace-api/src/test/java/org/dspace/access/status/DefaultAccessStatusHelperTest.java b/dspace-api/src/test/java/org/dspace/access/status/DefaultAccessStatusHelperTest.java index a41e985deb32..b9bca5e91391 100644 --- a/dspace-api/src/test/java/org/dspace/access/status/DefaultAccessStatusHelperTest.java +++ b/dspace-api/src/test/java/org/dspace/access/status/DefaultAccessStatusHelperTest.java @@ -14,6 +14,8 @@ import java.io.ByteArrayInputStream; import java.nio.charset.StandardCharsets; import java.sql.SQLException; +import java.time.LocalDate; +import java.time.ZoneId; import java.util.ArrayList; import java.util.Date; import java.util.List; @@ -42,7 +44,6 @@ import org.dspace.eperson.Group; import org.dspace.eperson.factory.EPersonServiceFactory; import org.dspace.eperson.service.GroupService; -import org.joda.time.LocalDate; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -129,7 +130,7 @@ public void init() { fail("SQL Error in init: " + ex.getMessage()); } helper = new DefaultAccessStatusHelper(); - threshold = new LocalDate(10000, 1, 1).toDate(); + threshold = dateFrom(10000, 1, 1); } /** @@ -266,7 +267,7 @@ public void testWithEmbargo() throws Exception { Group group = groupService.findByName(context, Group.ANONYMOUS); policy.setGroup(group); policy.setAction(Constants.READ); - policy.setStartDate(new LocalDate(9999, 12, 31).toDate()); + policy.setStartDate(dateFrom(9999, 12, 31)); policies.add(policy); authorizeService.removeAllPolicies(context, bitstream); authorizeService.addPolicies(context, policies, bitstream); @@ -293,7 +294,7 @@ public void testWithDateRestriction() throws Exception { Group group = groupService.findByName(context, Group.ANONYMOUS); policy.setGroup(group); policy.setAction(Constants.READ); - policy.setStartDate(new LocalDate(10000, 1, 1).toDate()); + policy.setStartDate(dateFrom(10000, 1, 1)); policies.add(policy); authorizeService.removeAllPolicies(context, bitstream); authorizeService.addPolicies(context, policies, bitstream); @@ -383,7 +384,7 @@ public void testWithPrimaryAndMultipleBitstreams() throws Exception { Group group = groupService.findByName(context, Group.ANONYMOUS); policy.setGroup(group); policy.setAction(Constants.READ); - policy.setStartDate(new LocalDate(9999, 12, 31).toDate()); + policy.setStartDate(dateFrom(9999, 12, 31)); policies.add(policy); authorizeService.removeAllPolicies(context, primaryBitstream); authorizeService.addPolicies(context, policies, primaryBitstream); @@ -412,7 +413,7 @@ public void testWithNoPrimaryAndMultipleBitstreams() throws Exception { Group group = groupService.findByName(context, Group.ANONYMOUS); policy.setGroup(group); policy.setAction(Constants.READ); - policy.setStartDate(new LocalDate(9999, 12, 31).toDate()); + policy.setStartDate(dateFrom(9999, 12, 31)); policies.add(policy); authorizeService.removeAllPolicies(context, anotherBitstream); authorizeService.addPolicies(context, policies, anotherBitstream); @@ -420,4 +421,19 @@ public void testWithNoPrimaryAndMultipleBitstreams() throws Exception { String status = helper.getAccessStatusFromItem(context, itemWithoutPrimaryAndMultipleBitstreams, threshold); assertThat("testWithNoPrimaryAndMultipleBitstreams 0", status, equalTo(DefaultAccessStatusHelper.OPEN_ACCESS)); } + + /** + * Create a Date from local year, month, day. + * + * @param year the year. + * @param month the month. + * @param day the day. + * @return the assembled date. + */ + private Date dateFrom(int year, int month, int day) { + return Date.from(LocalDate.of(year, month, day) + .atStartOfDay() + .atZone(ZoneId.systemDefault()) + .toInstant()); + } } diff --git a/dspace-api/src/test/java/org/dspace/app/util/GoogleMetadataTest.java b/dspace-api/src/test/java/org/dspace/app/util/GoogleMetadataTest.java index ee6723480e35..c2543ca17b8c 100644 --- a/dspace-api/src/test/java/org/dspace/app/util/GoogleMetadataTest.java +++ b/dspace-api/src/test/java/org/dspace/app/util/GoogleMetadataTest.java @@ -16,11 +16,15 @@ import java.io.IOException; import java.nio.charset.StandardCharsets; import java.sql.SQLException; +import java.time.Period; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; import java.util.Date; import java.util.List; import java.util.Map; import com.google.common.base.Splitter; +import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.dspace.AbstractUnitTest; import org.dspace.authorize.AuthorizeException; @@ -41,10 +45,6 @@ import org.dspace.eperson.Group; import org.dspace.eperson.factory.EPersonServiceFactory; import org.dspace.eperson.service.GroupService; -import org.joda.time.DateTime; -import org.joda.time.DateTimeZone; -import org.joda.time.MutablePeriod; -import org.joda.time.format.PeriodFormat; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -54,7 +54,7 @@ public class GoogleMetadataTest extends AbstractUnitTest { /** * log4j category */ - private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(GoogleMetadataTest.class); + private static final Logger log = LogManager.getLogger(); /** * Item instance for the tests @@ -319,6 +319,7 @@ public void testGetPDFURLWithNoBitstreams() throws Exception { /** * Test empty bitstreams + * @throws java.lang.Exception passed through. */ @Test public void testGetPDFURLWithEmptyBitstreams() throws Exception { @@ -348,8 +349,9 @@ public void testGetPDFURLWithEmptyBitstreams() throws Exception { } /** - * Verify there is no mapping for {@link GoogleMetadata#PDF} if there are only embargoed (non-publically accessible - * bitstream) files + * Verify there is no mapping for {@link GoogleMetadata#PDF} if there are + * only embargoed (non-publicly accessible bitstream) files. + * @throws java.lang.Exception passed through. */ @Test public void testGetPdfUrlOfEmbargoed() throws Exception { @@ -363,8 +365,10 @@ public void testGetPdfUrlOfEmbargoed() throws Exception { b.getFormat(context).setMIMEType("unknown"); bundleService.addBitstream(context, bundle, b); // Set 3 month embargo on pdf - MutablePeriod period = PeriodFormat.getDefault().parseMutablePeriod("3 months"); - Date embargoDate = DateTime.now(DateTimeZone.UTC).plus(period).toDate(); + Period period = Period.ofMonths(3); + Date embargoDate = Date.from(ZonedDateTime.now(ZoneOffset.UTC) + .plus(period) + .toInstant()); Group anonGroup = groupService.findByName(context, Group.ANONYMOUS); authorizeService.removeAllPolicies(context, b); resourcePolicyService.removeAllPolicies(context, b); diff --git a/dspace-api/src/test/java/org/dspace/authority/AuthorityValueTest.java b/dspace-api/src/test/java/org/dspace/authority/AuthorityValueTest.java new file mode 100644 index 000000000000..74b1e7776643 --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/authority/AuthorityValueTest.java @@ -0,0 +1,44 @@ +package org.dspace.authority; + +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import static org.junit.Assert.assertNull; + +import java.util.Date; + +import static org.junit.Assert.assertEquals; +import org.junit.Test; + +/** + * + * @author mwood + */ +public class AuthorityValueTest { + /** + * Test of stringToDate method, of class AuthorityValue. + */ + @Test + public void testStringToDate() { + Date expected; + Date actual; + + // Test an invalid date. + actual = AuthorityValue.stringToDate("not a date"); + assertNull("Unparseable date should return null", actual); + + // Test a date-time without zone or offset. + expected = Date.from(LocalDateTime.of(1957, 01, 27, 01, 23, 45) + .atZone(ZoneOffset.of("-05")) + .toInstant()); + actual = AuthorityValue.stringToDate("1957-01-27T01:23:45"); + assertEquals("Local date-time should convert", expected, actual); + + // Test a date-time with milliseconds and offset from UTC. + expected = Date.from(LocalDateTime.of(1957, 01, 27, 01, 23, 45, 678_000_000) + .atZone(ZoneOffset.of("-05")) + .toInstant()); + actual = AuthorityValue.stringToDate("1957-01-27T01:23:45.678-05"); + assertEquals("Zoned date-time with milliseconds should convert", + expected, actual); + } +} diff --git a/dspace-api/src/test/java/org/dspace/builder/AbstractDSpaceObjectBuilder.java b/dspace-api/src/test/java/org/dspace/builder/AbstractDSpaceObjectBuilder.java index b20515017af0..4455668146e6 100644 --- a/dspace-api/src/test/java/org/dspace/builder/AbstractDSpaceObjectBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/AbstractDSpaceObjectBuilder.java @@ -8,6 +8,8 @@ package org.dspace.builder; import java.sql.SQLException; +import java.time.Instant; +import java.time.Period; import java.util.Date; import org.apache.logging.log4j.Logger; @@ -20,11 +22,6 @@ import org.dspace.core.Context; import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; -import org.joda.time.DateTime; -import org.joda.time.DateTimeZone; -import org.joda.time.MutablePeriod; -import org.joda.time.format.PeriodFormat; -import org.joda.time.format.PeriodFormatter; /** * Abstract builder to construct DSpace Objects @@ -112,21 +109,22 @@ protected > B setMetadataSingleValue(fi } /** - * Support method to grant the {@link Constants#READ} permission over an object only to the {@link Group#ANONYMOUS} - * after the specified embargoPeriod. Any other READ permissions will be removed + * Support method to grant the {@link Constants#READ} permission over an + * object only to the {@link Group#ANONYMOUS} after the specified + * embargoPeriod. Any other READ permissions will be removed. * + * @param type of this Builder. * @param embargoPeriod - * the embargo period after which the READ permission will be active. It is parsed using the - * {@link PeriodFormatter#parseMutablePeriod(String)} method of the joda library - * @param dso - * the DSpaceObject on which grant the permission - * @return the builder properly configured to retain read permission on the object only for the specified group + * the embargo period after which the READ permission will be + * active. + * @param dso the DSpaceObject on which to grant the permission. + * @return the builder properly configured to retain read permission on the + * object only for the specified group. */ - protected > B setEmbargo(String embargoPeriod, DSpaceObject dso) { + protected > B setEmbargo(Period embargoPeriod, DSpaceObject dso) { // add policy just for anonymous try { - MutablePeriod period = PeriodFormat.getDefault().parseMutablePeriod(embargoPeriod); - Date embargoDate = DateTime.now(DateTimeZone.UTC).plus(period).toDate(); + Date embargoDate = Date.from(Instant.now().plus(embargoPeriod)); return setOnlyReadPermission(dso, groupService.findByName(context, Group.ANONYMOUS), embargoDate); } catch (Exception e) { @@ -135,14 +133,19 @@ protected > B setEmbargo(String embargo } /** - * Support method to grant the {@link Constants#READ} permission over an object only to a specific group. Any other - * READ permissions will be removed + * Support method to grant the {@link Constants#READ} permission over an + * object only to a specific group.Any other READ permissions will be + * removed. * + * @param type of this Builder. * @param dso * the DSpaceObject on which grant the permission * @param group * the EPersonGroup that will be granted of the permission - * @return the builder properly configured to retain read permission on the object only for the specified group + * @param startDate + * the date on which access begins. + * @return the builder properly configured to retain read permission on the + * object only for the specified group. */ protected > B setOnlyReadPermission(DSpaceObject dso, Group group, Date startDate) { @@ -161,15 +164,20 @@ protected > B setOnlyReadPermission(DSp } return (B) this; } + /** - * Support method to grant the {@link Constants#ADMIN} permission over an object only to a specific eperson. - * If another ADMIN policy is in place for an eperson it will be replaced + * Support method to grant the {@link Constants#READ} permission over an + * object only to a specific EPerson. Any other READ permissions will be + * removed. * + * @param type of this Builder. * @param dso * the DSpaceObject on which grant the permission * @param eperson - * the eperson that will be granted of the permission - * @return the builder properly configured to build the object with the additional admin permission + * the EPerson that will be granted of the permission + * @param startDate the date on which access begins. + * @return the builder properly configured to build the object with the + * additional admin permission. */ protected > B setAdminPermission(DSpaceObject dso, EPerson eperson, Date startDate) { @@ -191,6 +199,7 @@ protected > B setAdminPermission(DSpace /** * Support method to grant {@link Constants#REMOVE} permission to a specific eperson * + * @param type of this Builder. * @param dso * the DSpaceObject on which grant the permission * @param eperson @@ -220,6 +229,7 @@ protected > B setRemovePermissionForEpe /** * Support method to grant {@link Constants#ADD} permission to a specific eperson * + * @param type of this Builder. * @param dso * the DSpaceObject on which grant the permission * @param eperson @@ -249,6 +259,7 @@ protected > B setAddPermissionForEperso /** * Support method to grant {@link Constants#WRITE} permission to a specific eperson * + * @param type of this Builder. * @param dso * the DSpaceObject on which grant the permission * @param eperson diff --git a/dspace-api/src/test/java/org/dspace/builder/BitstreamBuilder.java b/dspace-api/src/test/java/org/dspace/builder/BitstreamBuilder.java index 424833e5cc65..2822d3624e99 100644 --- a/dspace-api/src/test/java/org/dspace/builder/BitstreamBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/BitstreamBuilder.java @@ -10,6 +10,7 @@ import java.io.IOException; import java.io.InputStream; import java.sql.SQLException; +import java.time.Period; import java.util.List; import org.dspace.authorize.AuthorizeException; @@ -171,7 +172,7 @@ private Bundle getOriginalBundle(Item item) throws SQLException, AuthorizeExcept return targetBundle; } - public BitstreamBuilder withEmbargoPeriod(String embargoPeriod) { + public BitstreamBuilder withEmbargoPeriod(Period embargoPeriod) { return setEmbargo(embargoPeriod, bitstream); } diff --git a/dspace-api/src/test/java/org/dspace/builder/ItemBuilder.java b/dspace-api/src/test/java/org/dspace/builder/ItemBuilder.java index 3e5ab0f38f5b..f6190c5751df 100644 --- a/dspace-api/src/test/java/org/dspace/builder/ItemBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/ItemBuilder.java @@ -13,6 +13,7 @@ import java.io.IOException; import java.sql.SQLException; +import java.time.Period; import java.util.UUID; import org.dspace.authorize.AuthorizeException; @@ -291,7 +292,7 @@ public ItemBuilder withdrawn() { return this; } - public ItemBuilder withEmbargoPeriod(String embargoPeriod) { + public ItemBuilder withEmbargoPeriod(Period embargoPeriod) { return setEmbargo(embargoPeriod, item); } diff --git a/dspace-api/src/test/java/org/dspace/importer/external/crossref/CrossRefDateMetadataProcessorTest.java b/dspace-api/src/test/java/org/dspace/importer/external/crossref/CrossRefDateMetadataProcessorTest.java new file mode 100644 index 000000000000..89b860d36f10 --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/importer/external/crossref/CrossRefDateMetadataProcessorTest.java @@ -0,0 +1,32 @@ +package org.dspace.importer.external.crossref; + +import java.util.Collection; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import static org.junit.Assert.*; + +/** + * + * @author mwood + */ +public class CrossRefDateMetadataProcessorTest { + /** + * Test of processMetadata method, of class CrossRefDateMetadataProcessor. + */ + @Test + public void testProcessMetadata() { + CrossRefDateMetadataProcessor unit = new CrossRefDateMetadataProcessor(); + unit.setPathToArray("/dates"); + Collection metadata = unit.processMetadata("{\"dates\": [" + + "[1957, 1, 27]," + + "[1957, 1]," + + "[1957]" + + "]}"); + String[] metadataValues = (String[]) metadata.toArray(new String[3]); + assertEquals("[yyyy, MM, dd] should parse", "1957-01-27", metadataValues[0]); + assertEquals("[yyyy, MM] should parse", "1957-01", metadataValues[1]); + assertEquals("[yyyy] should parse", "1957", metadataValues[2]); + } +} 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 4813cc659694..5fbf669baee3 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 @@ -49,6 +49,7 @@ import java.io.InputStream; import java.io.StringWriter; import java.io.Writer; +import java.time.Period; import java.util.UUID; import org.apache.commons.io.IOUtils; @@ -393,7 +394,7 @@ public void testEmbargoedBitstream() throws Exception { .withName("Test Embargoed Bitstream") .withDescription("This bitstream is embargoed") .withMimeType("text/plain") - .withEmbargoPeriod("6 months") + .withEmbargoPeriod(Period.ofMonths(6)) .build(); } context.restoreAuthSystemState(); @@ -437,7 +438,7 @@ public void embargoedBitstreamForbiddenTest() throws Exception { .withName("Test Embargoed Bitstream") .withDescription("This bitstream is embargoed") .withMimeType("text/plain") - .withEmbargoPeriod("3 months") + .withEmbargoPeriod(Period.ofMonths(3)) .build(); } context.restoreAuthSystemState(); @@ -480,7 +481,7 @@ public void expiredEmbargoedBitstreamTest() throws Exception { .withName("Test Embargoed Bitstream") .withDescription("This bitstream is embargoed") .withMimeType("text/plain") - .withEmbargoPeriod("-3 months") + .withEmbargoPeriod(Period.ofMonths(-3)) .build(); } context.restoreAuthSystemState(); @@ -558,7 +559,7 @@ public void embargoedBitstreamAccessGrantByAdminsTest() throws Exception { .withName("Bitstream") .withDescription("Description") .withMimeType("text/plain") - .withEmbargoPeriod("2 week") + .withEmbargoPeriod(Period.ofWeeks(2)) .build(); } context.restoreAuthSystemState(); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java index 8b34edb938a6..bc276557dfb3 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java @@ -25,6 +25,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import java.io.InputStream; +import java.time.Period; import java.util.ArrayList; import java.util.Comparator; import java.util.List; @@ -310,7 +311,7 @@ public void findOneBitstreamTest_EmbargoedBitstream_Anon() throws Exception { .withName("Test Embargoed Bitstream") .withDescription("This bitstream is embargoed") .withMimeType("text/plain") - .withEmbargoPeriod("3 months") + .withEmbargoPeriod(Period.ofMonths(3)) .build(); } context.restoreAuthSystemState(); @@ -363,7 +364,7 @@ public void findOneBitstreamFormatTest_EmbargoedBitstream_Anon() throws Exceptio .withName("Test Embargoed Bitstream") .withDescription("This bitstream is embargoed") .withMimeType(bitstreamFormat.getMIMEType()) - .withEmbargoPeriod("3 months") + .withEmbargoPeriod(Period.ofMonths(3)) .build(); } context.restoreAuthSystemState(); @@ -517,7 +518,7 @@ public void findOneBitstreamTest_EmbargoedBitstream_NoREADRightsOnBundle() throw .withName("Test Embargoed Bitstream") .withDescription("This bitstream is embargoed") .withMimeType("text/plain") - .withEmbargoPeriod("3 months") + .withEmbargoPeriod(Period.ofMonths(3)) .build(); } @@ -577,7 +578,7 @@ public void findOneBitstreamFormatTest_EmbargoedBitstream_NoREADRightsOnBundle() .withName("Test Embargoed Bitstream") .withDescription("This bitstream is embargoed") .withMimeType(bitstreamFormat.getMIMEType()) - .withEmbargoPeriod("3 months") + .withEmbargoPeriod(Period.ofMonths(3)) .build(); } @@ -638,7 +639,7 @@ public void findOneBitstreamTest_EmbargoedBitstream_ePersonREADRightsOnBundle() .withName("Test Embargoed Bitstream") .withDescription("This bitstream is embargoed") .withMimeType("text/plain") - .withEmbargoPeriod("3 months") + .withEmbargoPeriod(Period.ofMonths(3)) .build(); } @@ -701,7 +702,7 @@ public void findOneBitstreamFormatTest_EmbargoedBitstream_ePersonREADRightsOnBun .withName("Test Embargoed Bitstream") .withDescription("This bitstream is embargoed") .withMimeType(bitstreamFormat.getMIMEType()) - .withEmbargoPeriod("3 months") + .withEmbargoPeriod(Period.ofMonths(3)) .build(); } @@ -768,7 +769,7 @@ public void findOneBitstreamTest_EmbargoedBitstream_NoREADRightsOnItem() throws .withName("Test Embargoed Bitstream") .withDescription("This bitstream is embargoed") .withMimeType("text/plain") - .withEmbargoPeriod("3 months") + .withEmbargoPeriod(Period.ofMonths(3)) .build(); } @@ -826,7 +827,7 @@ public void findOneBitstreamTest_EmbargoedBitstream_ePersonREADRightsOnItem() th .withName("Test Embargoed Bitstream") .withDescription("This bitstream is embargoed") .withMimeType("text/plain") - .withEmbargoPeriod("3 months") + .withEmbargoPeriod(Period.ofMonths(3)) .build(); } @@ -1899,7 +1900,7 @@ public void findByHandleAndFileNameForEmbargoItem() throws Exception { .withTitle("Test") .withIssueDate("2010-10-17") .withAuthor("Smith, Donald") - .withEmbargoPeriod("6 months") + .withEmbargoPeriod(Period.ofMonths(6)) .build(); String bitstreamContent = "This is an archived bitstream"; @@ -2372,7 +2373,7 @@ public void findByHandleAndFileNameForPublicItemWithEmbargoOnFile() throws Excep .withName("Test Embargoed Bitstream") .withDescription("This bitstream is embargoed") .withMimeType("text/plain") - .withEmbargoPeriod("3 months") + .withEmbargoPeriod(Period.ofMonths(3)) .build(); } 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 d1791ab872bb..75df0feb34a3 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 @@ -21,6 +21,8 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import java.time.Period; + import org.dspace.app.rest.matcher.BrowseEntryResourceMatcher; import org.dspace.app.rest.matcher.BrowseIndexMatcher; import org.dspace.app.rest.matcher.ItemMatcher; @@ -776,7 +778,7 @@ public void findBrowseByTitleItems() throws Exception { .withIssueDate("2017-08-10") .withAuthor("Mouse, Mickey") .withSubject("Cartoons").withSubject("Mice") - .withEmbargoPeriod("12 months") + .withEmbargoPeriod(Period.ofMonths(12)) .build(); //5. An item that is only readable for an internal groups @@ -909,7 +911,7 @@ public void findBrowseByTitleItemsWithScope() throws Exception { .withIssueDate("2017-08-10") .withAuthor("Mouse, Mickey") .withSubject("Cartoons").withSubject("Mice") - .withEmbargoPeriod("12 months") + .withEmbargoPeriod(Period.ofMonths(12)) .build(); //5. An item that is only readable for an internal groups diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryRestControllerIT.java index a115c8aa2f15..6095c20c9e33 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryRestControllerIT.java @@ -26,6 +26,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import java.io.InputStream; +import java.time.Period; import java.util.UUID; import com.jayway.jsonpath.matchers.JsonPathMatchers; @@ -2413,7 +2414,7 @@ public void discoverSearchObjectsTestForEmbargoedItemsAndPrivateItems() throws E .withAuthor("test2, test2").withAuthor("Maybe, Maybe") .withSubject("AnotherTest").withSubject("TestingForMore") .withSubject("ExtraEntry") - .withEmbargoPeriod("12 months") + .withEmbargoPeriod(Period.ofMonths(12)) .build(); //Turn on the authorization again @@ -2714,7 +2715,9 @@ public void discoverSearchObjectsTestForScopeWithPrivateItem() throws Exception /** * This test verifies that * {@link org.dspace.discovery.indexobject.InprogressSubmissionIndexFactoryImpl#storeInprogressItemFields} - * indexes the owning collection of workspace items + * indexes the owning collection of workspace items. + * + * @throws java.lang.Exception passed through. */ @Test public void discoverSearchObjectsTestForWorkspaceItemInCollectionScope() throws Exception { @@ -2765,7 +2768,8 @@ public void discoverSearchObjectsTestForWorkspaceItemInCollectionScope() throws /** * This test verifies that * {@link org.dspace.discovery.indexobject.InprogressSubmissionIndexFactoryImpl#storeInprogressItemFields} - * indexes the owning collection of workflow items + * indexes the owning collection of workflow items. + * @throws java.lang.Exception passed through. */ @Test public void discoverSearchObjectsTestForWorkflowItemInCollectionScope() throws Exception { 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 801976be9f0d..08463fb22280 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 @@ -36,6 +36,7 @@ import java.io.InputStream; import java.sql.SQLException; +import java.time.Period; import java.util.ArrayList; import java.util.Comparator; import java.util.HashMap; @@ -313,7 +314,6 @@ public void findAllWithPaginationTest() throws Exception { .andExpect(jsonPath("$.page.totalPages", is(2))) .andExpect(jsonPath("$.page.number", is(0))) .andExpect(jsonPath("$.page.totalElements", is(3))); - ; getClient(token).perform(get("/api/core/items") .param("size", "2") @@ -596,7 +596,7 @@ public void withdrawPatchTest() throws Exception { // is used in the provenance note. String token = getAuthToken(admin.getEmail(), password); - List ops = new ArrayList(); + List ops = new ArrayList<>(); ReplaceOperation replaceOperation = new ReplaceOperation("/withdrawn", true); ops.add(replaceOperation); String patchBody = getPatchContent(ops); @@ -651,7 +651,7 @@ public void withdrawPatchUnauthorizedTest() throws Exception { .build(); context.restoreAuthSystemState(); - List ops = new ArrayList(); + List ops = new ArrayList<>(); ReplaceOperation replaceOperation = new ReplaceOperation("/withdrawn", true); ops.add(replaceOperation); String patchBody = getPatchContent(ops); @@ -700,7 +700,7 @@ public void withdrawPatchForbiddenTest() throws Exception { // try to use an unauthorized user String token = getAuthToken(eperson.getEmail(), password); - List ops = new ArrayList(); + List ops = new ArrayList<>(); ReplaceOperation replaceOperation = new ReplaceOperation("/withdrawn", true); ops.add(replaceOperation); String patchBody = getPatchContent(ops); @@ -760,7 +760,7 @@ public void valueMissingForWithdrawalOperation() throws Exception { context.restoreAuthSystemState(); String token = getAuthToken(admin.getEmail(), password); - List ops = new ArrayList(); + List ops = new ArrayList<>(); ReplaceOperation replaceOperation = new ReplaceOperation("/withdrawn", null); ops.add(replaceOperation); String patchBody = getPatchContent(ops); @@ -822,7 +822,7 @@ public void reinstatePatchTest() throws Exception { // is used in the provenance note. String token = getAuthToken(admin.getEmail(), password); - List ops = new ArrayList(); + List ops = new ArrayList<>(); ReplaceOperation replaceOperation = new ReplaceOperation("/withdrawn", false); ops.add(replaceOperation); String patchBody = getPatchContent(ops); @@ -882,7 +882,7 @@ public void reinstatePatchUnauthorizedTest() throws Exception { context.restoreAuthSystemState(); String tokenAdmin = getAuthToken(admin.getEmail(), password); - List ops = new ArrayList(); + List ops = new ArrayList<>(); ReplaceOperation replaceOperation = new ReplaceOperation("/withdrawn", false); ops.add(replaceOperation); String patchBody = getPatchContent(ops); @@ -932,7 +932,7 @@ public void reinstatePatchForbiddenTest() throws Exception { String token = getAuthToken(eperson.getEmail(), password); String tokenAdmin = getAuthToken(admin.getEmail(), password); - List ops = new ArrayList(); + List ops = new ArrayList<>(); ReplaceOperation replaceOperation = new ReplaceOperation("/withdrawn", false); ops.add(replaceOperation); String patchBody = getPatchContent(ops); @@ -977,7 +977,7 @@ public void makeDiscoverablePatchTest() throws Exception { context.restoreAuthSystemState(); String token = getAuthToken(admin.getEmail(), password); - List ops = new ArrayList(); + List ops = new ArrayList<>(); ReplaceOperation replaceOperation = new ReplaceOperation("/discoverable", true); ops.add(replaceOperation); String patchBody = getPatchContent(ops); @@ -1031,7 +1031,7 @@ public void makeDiscoverablePatchUnauthorizedTest() throws Exception { context.restoreAuthSystemState(); String token = getAuthToken(admin.getEmail(), password); - List ops = new ArrayList(); + List ops = new ArrayList<>(); ReplaceOperation replaceOperation = new ReplaceOperation("/discoverable", true); ops.add(replaceOperation); String patchBody = getPatchContent(ops); @@ -1076,7 +1076,7 @@ public void makeDiscoverablePatchForbiddenTest() throws Exception { String token = getAuthToken(eperson.getEmail(), password); String tokenAdmin = getAuthToken(admin.getEmail(), password); - List ops = new ArrayList(); + List ops = new ArrayList<>(); ReplaceOperation replaceOperation = new ReplaceOperation("/discoverable", true); ops.add(replaceOperation); String patchBody = getPatchContent(ops); @@ -1119,7 +1119,7 @@ public void makeUnDiscoverablePatchTest() throws Exception { context.restoreAuthSystemState(); String token = getAuthToken(admin.getEmail(), password); - List ops = new ArrayList(); + List ops = new ArrayList<>(); ReplaceOperation replaceOperation = new ReplaceOperation("/discoverable", false); ops.add(replaceOperation); String patchBody = getPatchContent(ops); @@ -1165,7 +1165,7 @@ public void useStringForBooleanTest() throws Exception { context.restoreAuthSystemState(); String token = getAuthToken(admin.getEmail(), password); - List ops = new ArrayList(); + List ops = new ArrayList<>(); // String value should work. ReplaceOperation replaceOperation = new ReplaceOperation("/discoverable", "false"); ops.add(replaceOperation); @@ -1212,7 +1212,7 @@ public void makeUnDiscoverablePatchUnauthorizedTest() throws Exception { context.restoreAuthSystemState(); String token = getAuthToken(admin.getEmail(), password); - List ops = new ArrayList(); + List ops = new ArrayList<>(); ReplaceOperation replaceOperation = new ReplaceOperation("/discoverable", false); ops.add(replaceOperation); String patchBody = getPatchContent(ops); @@ -1257,7 +1257,7 @@ public void makeUnDiscoverablePatchForbiddenTest() throws Exception { String token = getAuthToken(eperson.getEmail(), password); String tokenAdmin = getAuthToken(admin.getEmail(), password); - List ops = new ArrayList(); + List ops = new ArrayList<>(); ReplaceOperation replaceOperation = new ReplaceOperation("/discoverable", false); ops.add(replaceOperation); String patchBody = getPatchContent(ops); @@ -1311,7 +1311,7 @@ public void valueMissingForDiscoverableOperation() throws Exception { context.restoreAuthSystemState(); String token = getAuthToken(admin.getEmail(), password); - List ops = new ArrayList(); + List ops = new ArrayList<>(); ReplaceOperation replaceOperation = new ReplaceOperation("/discoverable", null); ops.add(replaceOperation); String patchBody = getPatchContent(ops); @@ -1638,7 +1638,7 @@ public void embargoAccessTest() throws Exception { .withIssueDate("2017-12-18") .withAuthor("Smith, Donald").withAuthor("Doe, John") .withSubject("ExtraEntry") - .withEmbargoPeriod("6 months") + .withEmbargoPeriod(Period.ofMonths(6)) .build(); //3. a public item @@ -1729,7 +1729,7 @@ public void embargoAccessGrantAdminsTest() throws Exception { .withIssueDate("2015-10-21") .withAuthor("Smith, Donald") .withSubject("ExtraEntry") - .withEmbargoPeriod("1 week") + .withEmbargoPeriod(Period.ofWeeks(1)) .build(); context.restoreAuthSystemState(); @@ -1778,7 +1778,7 @@ public void expiredEmbargoTest() throws Exception { .withTitle("embargoed item 1") .withIssueDate("2017-11-18") .withAuthor("Smith, Donald") - .withEmbargoPeriod("-2 week") + .withEmbargoPeriod(Period.ofWeeks(-2)) .build(); context.restoreAuthSystemState(); @@ -2069,7 +2069,7 @@ public void testCreateItem() throws Exception { context.restoreAuthSystemState(); UUID idRef = null; - AtomicReference idRefNoEmbeds = new AtomicReference(); + AtomicReference idRefNoEmbeds = new AtomicReference<>(); try { ObjectMapper mapper = new ObjectMapper(); ItemRest itemRest = new ItemRest(); @@ -3895,7 +3895,7 @@ public void testDeletionOfPublicationToBeSynchronizedWithOrcid() throws Exceptio createOrcidQueue(context, firstProfile, publication).build(); createOrcidQueue(context, secondProfile, publication).build(); - List historyRecords = new ArrayList(); + List historyRecords = new ArrayList<>(); historyRecords.add(createOrcidHistory(context, firstProfile, publication).build()); historyRecords.add(createOrcidHistory(context, firstProfile, publication).withPutCode("12345").build()); historyRecords.add(createOrcidHistory(context, secondProfile, publication).build()); @@ -3982,7 +3982,7 @@ public void testDeletionOfFundingToBeSynchronizedWithOrcid() throws Exception { createOrcidQueue(context, firstProfile, funding).build(); createOrcidQueue(context, secondProfile, funding).build(); - List historyRecords = new ArrayList(); + List historyRecords = new ArrayList<>(); historyRecords.add(createOrcidHistory(context, firstProfile, funding).build()); historyRecords.add(createOrcidHistory(context, firstProfile, funding).withPutCode("12345").build()); historyRecords.add(createOrcidHistory(context, secondProfile, funding).build()); @@ -4081,7 +4081,7 @@ public void findWithdrawnItemTest() throws Exception { String tokenAdmin = getAuthToken(admin.getEmail(), password); String tokenEperson = getAuthToken(eperson.getEmail(), password); - List ops = new ArrayList(); + List ops = new ArrayList<>(); ReplaceOperation replaceOperation = new ReplaceOperation("/withdrawn", true); ops.add(replaceOperation); String patchBody = getPatchContent(ops); @@ -4131,7 +4131,7 @@ public void findWithdrawnItemTest() throws Exception { .param("projection", "full")) .andExpect(status().isOk()) .andExpect(jsonPath("$", CollectionMatcher.matchCollectionEntryFullProjection( - col1.getName(), col1.getID(), col1.getHandle())));; + col1.getName(), col1.getID(), col1.getHandle()))); // try to spoof information as a logged in eperson using embedding, verify that no embedds are included getClient(tokenEperson).perform(get("/api/core/items/" + item.getID()) From 3c8409522e38c75ac59093f827b5cc035802e595 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Tue, 6 Jun 2023 16:30:14 -0400 Subject: [PATCH 283/510] Remove unused dependency on Joda Time --- dspace-api/pom.xml | 4 ---- dspace-server-webapp/pom.xml | 4 ---- 2 files changed, 8 deletions(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index 0e70fc52e085..3a4b673f0b87 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -703,10 +703,6 @@ annotations - - joda-time - joda-time - javax.inject javax.inject diff --git a/dspace-server-webapp/pom.xml b/dspace-server-webapp/pom.xml index 2221f9ca0e62..b65e3bc238dc 100644 --- a/dspace-server-webapp/pom.xml +++ b/dspace-server-webapp/pom.xml @@ -433,10 +433,6 @@ commons-validator commons-validator - - joda-time - joda-time - com.fasterxml.jackson.core jackson-databind From d52eb41383078f278b04482661cb85a295f0437e Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Tue, 6 Jun 2023 16:47:47 -0400 Subject: [PATCH 284/510] Expected time should be in the local zone, not EST. --- .../test/java/org/dspace/authority/AuthorityValueTest.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/dspace-api/src/test/java/org/dspace/authority/AuthorityValueTest.java b/dspace-api/src/test/java/org/dspace/authority/AuthorityValueTest.java index 74b1e7776643..81873d5cdb65 100644 --- a/dspace-api/src/test/java/org/dspace/authority/AuthorityValueTest.java +++ b/dspace-api/src/test/java/org/dspace/authority/AuthorityValueTest.java @@ -1,7 +1,10 @@ package org.dspace.authority; +import java.time.Instant; import java.time.LocalDateTime; +import java.time.ZoneId; import java.time.ZoneOffset; +import java.time.ZonedDateTime; import static org.junit.Assert.assertNull; import java.util.Date; @@ -28,7 +31,7 @@ public void testStringToDate() { // Test a date-time without zone or offset. expected = Date.from(LocalDateTime.of(1957, 01, 27, 01, 23, 45) - .atZone(ZoneOffset.of("-05")) + .atZone(ZoneId.systemDefault()) .toInstant()); actual = AuthorityValue.stringToDate("1957-01-27T01:23:45"); assertEquals("Local date-time should convert", expected, actual); From 48b21728e7f1d42b966a823e9c05b3bff00c5c42 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Tue, 6 Jun 2023 17:14:41 -0400 Subject: [PATCH 285/510] I forgot the license headers on new tests. --- .../test/java/org/dspace/authority/AuthorityValueTest.java | 7 +++++++ .../crossref/CrossRefDateMetadataProcessorTest.java | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/dspace-api/src/test/java/org/dspace/authority/AuthorityValueTest.java b/dspace-api/src/test/java/org/dspace/authority/AuthorityValueTest.java index 81873d5cdb65..ac0d7880e1bb 100644 --- a/dspace-api/src/test/java/org/dspace/authority/AuthorityValueTest.java +++ b/dspace-api/src/test/java/org/dspace/authority/AuthorityValueTest.java @@ -1,3 +1,10 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ package org.dspace.authority; import java.time.Instant; diff --git a/dspace-api/src/test/java/org/dspace/importer/external/crossref/CrossRefDateMetadataProcessorTest.java b/dspace-api/src/test/java/org/dspace/importer/external/crossref/CrossRefDateMetadataProcessorTest.java index 89b860d36f10..fa9322d72d0d 100644 --- a/dspace-api/src/test/java/org/dspace/importer/external/crossref/CrossRefDateMetadataProcessorTest.java +++ b/dspace-api/src/test/java/org/dspace/importer/external/crossref/CrossRefDateMetadataProcessorTest.java @@ -1,3 +1,10 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ package org.dspace.importer.external.crossref; import java.util.Collection; From 05c349f0d5d745688a1021f2eb5143b3f67f4053 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Tue, 6 Jun 2023 17:30:44 -0400 Subject: [PATCH 286/510] Clean up mess left by fixing errors. --- .../java/org/dspace/authority/AuthorityValueTest.java | 8 +++----- .../crossref/CrossRefDateMetadataProcessorTest.java | 7 +++---- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/dspace-api/src/test/java/org/dspace/authority/AuthorityValueTest.java b/dspace-api/src/test/java/org/dspace/authority/AuthorityValueTest.java index ac0d7880e1bb..07c4b65f40f2 100644 --- a/dspace-api/src/test/java/org/dspace/authority/AuthorityValueTest.java +++ b/dspace-api/src/test/java/org/dspace/authority/AuthorityValueTest.java @@ -7,16 +7,14 @@ */ package org.dspace.authority; -import java.time.Instant; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + import java.time.LocalDateTime; import java.time.ZoneId; import java.time.ZoneOffset; -import java.time.ZonedDateTime; -import static org.junit.Assert.assertNull; - import java.util.Date; -import static org.junit.Assert.assertEquals; import org.junit.Test; /** diff --git a/dspace-api/src/test/java/org/dspace/importer/external/crossref/CrossRefDateMetadataProcessorTest.java b/dspace-api/src/test/java/org/dspace/importer/external/crossref/CrossRefDateMetadataProcessorTest.java index fa9322d72d0d..323856cd0a7d 100644 --- a/dspace-api/src/test/java/org/dspace/importer/external/crossref/CrossRefDateMetadataProcessorTest.java +++ b/dspace-api/src/test/java/org/dspace/importer/external/crossref/CrossRefDateMetadataProcessorTest.java @@ -7,12 +7,11 @@ */ package org.dspace.importer.external.crossref; +import static org.junit.Assert.assertEquals; + import java.util.Collection; -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.BeforeClass; + import org.junit.Test; -import static org.junit.Assert.*; /** * From 0cd92cd3b4d786cd32595b5593adc53a72fae6b3 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Wed, 7 Jun 2023 10:39:59 -0400 Subject: [PATCH 287/510] Instant refuses to be adjusted by a Period. --- .../dspace/builder/AbstractDSpaceObjectBuilder.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/dspace-api/src/test/java/org/dspace/builder/AbstractDSpaceObjectBuilder.java b/dspace-api/src/test/java/org/dspace/builder/AbstractDSpaceObjectBuilder.java index 4455668146e6..e7ebd8768e7d 100644 --- a/dspace-api/src/test/java/org/dspace/builder/AbstractDSpaceObjectBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/AbstractDSpaceObjectBuilder.java @@ -9,7 +9,9 @@ import java.sql.SQLException; import java.time.Instant; +import java.time.LocalDate; import java.time.Period; +import java.time.ZoneId; import java.util.Date; import org.apache.logging.log4j.Logger; @@ -28,6 +30,7 @@ * * @author Tom Desair (tom dot desair at atmire dot com) * @author Raf Ponsaerts (raf dot ponsaerts at atmire dot com) + * @param concrete type of DSpaceObject */ public abstract class AbstractDSpaceObjectBuilder extends AbstractBuilder { @@ -124,7 +127,12 @@ protected > B setMetadataSingleValue(fi protected > B setEmbargo(Period embargoPeriod, DSpaceObject dso) { // add policy just for anonymous try { - Date embargoDate = Date.from(Instant.now().plus(embargoPeriod)); + Instant embargoInstant = LocalDate.now() + .plus(embargoPeriod) + .atStartOfDay() + .atZone(ZoneId.systemDefault()) + .toInstant(); + Date embargoDate = Date.from(embargoInstant); return setOnlyReadPermission(dso, groupService.findByName(context, Group.ANONYMOUS), embargoDate); } catch (Exception e) { From 289221622adc628c67e004b048326b507acadbd9 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Mon, 12 Jun 2023 09:46:02 -0400 Subject: [PATCH 288/510] Fix Joda-style Period calculation in a newly-added class. --- .../src/test/java/org/dspace/builder/ItemBuilder.java | 10 ++++++++-- .../controller/LinksetRestControllerIT.java | 7 ++++--- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/dspace-api/src/test/java/org/dspace/builder/ItemBuilder.java b/dspace-api/src/test/java/org/dspace/builder/ItemBuilder.java index f6190c5751df..f4f504e60fad 100644 --- a/dspace-api/src/test/java/org/dspace/builder/ItemBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/ItemBuilder.java @@ -282,8 +282,8 @@ public ItemBuilder withHandle(String handle) { } /** - * Withdrawn the item under build. Please note that an user need to be loggedin the context to avoid NPE during the - * creation of the provenance metadata + * Withdraw the item under build. Please note that the Context must be + * logged in to avoid NPE during the creation of the provenance metadata. * * @return the ItemBuilder */ @@ -292,6 +292,12 @@ public ItemBuilder withdrawn() { return this; } + /** + * Set an embargo to end after some time from "now". + * + * @param embargoPeriod embargo starting "now", for this long. + * @return the ItemBuilder. + */ public ItemBuilder withEmbargoPeriod(Period embargoPeriod) { return setEmbargo(embargoPeriod, item); } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/signposting/controller/LinksetRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/signposting/controller/LinksetRestControllerIT.java index 6d1d242cad7f..0bd80a3bf107 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/signposting/controller/LinksetRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/signposting/controller/LinksetRestControllerIT.java @@ -19,6 +19,7 @@ import java.text.MessageFormat; import java.text.SimpleDateFormat; import java.util.Date; +import java.time.Period; import org.apache.commons.codec.CharEncoding; import org.apache.commons.io.IOUtils; @@ -466,7 +467,7 @@ public void findOneEmbargoItemJsonLinksets() throws Exception { .withTitle("Withdrawn Item") .withMetadata("dc", "identifier", "doi", doi) .withIssueDate("2017-11-18") - .withEmbargoPeriod("2 week") + .withEmbargoPeriod(Period.ofWeeks(2)) .build(); context.restoreAuthSystemState(); @@ -837,7 +838,7 @@ public void findTypedLinkForBitstreamUnderEmbargo() throws Exception { .withName("Bitstream") .withDescription("description") .withMimeType(bitstreamMimeType) - .withEmbargoPeriod("6 months") + .withEmbargoPeriod(Period.ofMonths(6)) .build(); } context.restoreAuthSystemState(); @@ -949,7 +950,7 @@ public void getDescribedByEmbargoItem() throws Exception { .withTitle("Withdrawn Item") .withMetadata("dc", "identifier", "doi", doi) .withIssueDate("2017-11-18") - .withEmbargoPeriod("2 week") + .withEmbargoPeriod(Period.ofWeeks(2)) .build(); context.restoreAuthSystemState(); From 17c410ec2748181964bc4888086b1ec47114ef36 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Mon, 26 Jun 2023 11:16:13 -0400 Subject: [PATCH 289/510] Rebase messed up the order of imports. --- .../rest/signposting/controller/LinksetRestControllerIT.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/signposting/controller/LinksetRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/signposting/controller/LinksetRestControllerIT.java index 0bd80a3bf107..b363e4885ed5 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/signposting/controller/LinksetRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/signposting/controller/LinksetRestControllerIT.java @@ -18,8 +18,8 @@ import java.text.DateFormat; import java.text.MessageFormat; import java.text.SimpleDateFormat; -import java.util.Date; import java.time.Period; +import java.util.Date; import org.apache.commons.codec.CharEncoding; import org.apache.commons.io.IOUtils; From c4159cff0d31e4fb99d73914f6dd382abb6716e5 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Tue, 27 Jun 2023 17:01:32 -0400 Subject: [PATCH 290/510] More and better(?) documentation. --- .../src/main/java/org/dspace/core/Email.java | 116 +++++++++++++----- 1 file changed, 84 insertions(+), 32 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/core/Email.java b/dspace-api/src/main/java/org/dspace/core/Email.java index 998d934c9558..a95407876e11 100644 --- a/dspace-api/src/main/java/org/dspace/core/Email.java +++ b/dspace-api/src/main/java/org/dspace/core/Email.java @@ -57,26 +57,40 @@ import org.dspace.services.factory.DSpaceServicesFactory; /** - * Class representing an e-mail message, also used to send e-mails. + * Class representing an e-mail message. The {@link send} method causes the + * assembled message to be formatted and sent. *

* Typical use: - *

+ *
+ * Email email = Email.getEmail(path);
+ * email.addRecipient("foo@bar.com");
+ * email.addArgument("John");
+ * email.addArgument("On the Testing of DSpace");
+ * email.send();
+ * 
+ * {@code path} is the filesystem path of an email template, typically in + * {@code ${dspace.dir}/config/emails/} and can include the subject -- see + * below. Templates are processed by
+ * Apache Velocity. They may contain VTL directives and property + * placeholders. + *

+ * {@link addArgument(string)} adds a property to the {@code params} array + * in the Velocity context, which can be used to replace placeholder tokens + * in the message. These arguments are indexed by number in the order they were + * added to the message. *

- * Email email = new Email();
- * email.addRecipient("foo@bar.com");
- * email.addArgument("John");
- * email.addArgument("On the Testing of DSpace");
- * email.send();
- *

+ * The DSpace configuration properties are also available to templates as the + * array {@code config}, indexed by name. Example: {@code ${config.get('dspace.name')}} *

- * name is the name of an email template in - * dspace-dir/config/emails/ (which also includes the subject.) - * arg0 and arg1 are arguments to fill out the - * message with. - *

- * Emails are formatted using Apache Velocity. Headers such as Subject may be - * supplied by the template, by defining them using #set(). Example: - *

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

+ * Headers such as Subject may be supplied by the template, by defining them + * using the VTL directive {@code #set()}. Only headers named in the DSpace + * configuration array property {@code mail.message.headers} will be added. + *

+ * Example: * *

  *
@@ -91,12 +105,14 @@
  *
  *     Thank you for sending us your submission "${params[1]}".
  *
+ *     --
+ *     The ${config.get('dspace.name')} Team
+ *
  * 
* *

* If the example code above was used to send this mail, the resulting mail * would have the subject Example e-mail and the body would be: - *

* *
  *
@@ -105,7 +121,16 @@
  *
  *     Thank you for sending us your submission "On the Testing of DSpace".
  *
+ *     --
+ *     The DSpace Team
+ *
  * 
+ *

+ * There are two ways to load a message body. One can create an instance of + * {@link Email} and call {@link setContent} on it, passing the body as a String. Or + * one can use the static factory method {@link getEmail} to load a file by its + * complete filesystem path. In either case the text will be loaded into a + * Velocity template. * * @author Robert Tansley * @author Jim Downing - added attachment handling code @@ -182,7 +207,7 @@ public Email() { } /** - * Add a recipient + * Add a recipient. * * @param email the recipient's email address */ @@ -205,7 +230,7 @@ public void setContent(String name, String cnt) { } /** - * Set the subject of the message + * Set the subject of the message. * * @param s the subject of the message */ @@ -214,7 +239,7 @@ public void setSubject(String s) { } /** - * Set the reply-to email address + * Set the reply-to email address. * * @param email the reply-to email address */ @@ -223,7 +248,7 @@ public void setReplyTo(String email) { } /** - * Fill out the next argument in the template + * Fill out the next argument in the template. * * @param arg the value for the next argument */ @@ -231,6 +256,13 @@ public void addArgument(Object arg) { arguments.add(arg); } + /** + * Add an attachment bodypart to the message from an external file. + * + * @param f reference to a file to be attached. + * @param name a name for the resulting bodypart in the message's MIME + * structure. + */ public void addAttachment(File f, String name) { attachments.add(new FileAttachment(f, name)); } @@ -238,6 +270,17 @@ public void addAttachment(File f, String name) { /** When given a bad MIME type for an attachment, use this instead. */ private static final String DEFAULT_ATTACHMENT_TYPE = "application/octet-stream"; + /** + * Add an attachment bodypart to the message from a byte stream. + * + * @param is the content of this stream will become the content of the + * bodypart. + * @param name a name for the resulting bodypart in the message's MIME + * structure. + * @param mimetype the MIME type of the resulting bodypart, such as + * "text/pdf". If {@code null} it will default to + * "application/octet-stream", which is MIME for "unknown format". + */ public void addAttachment(InputStream is, String name, String mimetype) { if (null == mimetype) { LOG.error("Null MIME type replaced with '" + DEFAULT_ATTACHMENT_TYPE @@ -257,6 +300,11 @@ public void addAttachment(InputStream is, String name, String mimetype) { moreAttachments.add(new InputStreamAttachment(is, name, mimetype)); } + /** + * Set the character set of the message. + * + * @param cs the name of a character set, such as "UTF-8" or "EUC-JP". + */ public void setCharset(String cs) { charset = cs; } @@ -447,6 +495,9 @@ public void send() throws MessagingException, IOException { /** * Get the VTL template for an email message. The message is suitable * for inserting values using Apache Velocity. + *

+ * Note that everything is stored here, so that only send() throws a + * MessagingException. * * @param emailFile * full name for the email template, for example "/dspace/config/emails/register". @@ -484,15 +535,6 @@ public static Email getEmail(String emailFile) } return email; } - /* - * Implementation note: It might be necessary to add a quick utility method - * like "send(to, subject, message)". We'll see how far we get without it - - * having all emails as templates in the config allows customisation and - * internationalisation. - * - * Note that everything is stored and the run in send() so that only send() - * throws a MessagingException. - */ /** * Test method to send an email to check email server settings @@ -547,7 +589,7 @@ public static void main(String[] args) { } /** - * Utility struct class for handling file attachments. + * Utility record class for handling file attachments. * * @author ojd20 */ @@ -563,7 +605,7 @@ public FileAttachment(File f, String n) { } /** - * Utility struct class for handling file attachments. + * Utility record class for handling file attachments. * * @author Adán Román Ruiz at arvo.es */ @@ -580,6 +622,8 @@ public InputStreamAttachment(InputStream is, String name, String mimetype) { } /** + * Wrap an {@link InputStream} in a {@link DataSource}. + * * @author arnaldo */ public static class InputStreamDataSource implements DataSource { @@ -587,6 +631,14 @@ public static class InputStreamDataSource implements DataSource { private final String contentType; private final ByteArrayOutputStream baos; + /** + * Consume the content of an InputStream and store it in a local buffer. + * + * @param name give the DataSource a name. + * @param contentType the DataSource contains this type of data. + * @param inputStream content to be buffered in the DataSource. + * @throws IOException if the stream cannot be read. + */ InputStreamDataSource(String name, String contentType, InputStream inputStream) throws IOException { this.name = name; this.contentType = contentType; From a30454ca5a523c532e353a63270bb5a32fbd3e70 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Tue, 27 Jun 2023 17:05:21 -0400 Subject: [PATCH 291/510] Don't clear the list of accepted embedded message header properties. Why was this cleared? --- dspace-api/src/main/java/org/dspace/core/Email.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/core/Email.java b/dspace-api/src/main/java/org/dspace/core/Email.java index a95407876e11..a64a85a07351 100644 --- a/dspace-api/src/main/java/org/dspace/core/Email.java +++ b/dspace-api/src/main/java/org/dspace/core/Email.java @@ -378,13 +378,12 @@ public void send() throws MessagingException, IOException { // No template and no content -- PANIC!!! throw new MessagingException("Email has no body"); } - // No template, so use a String of content. + // No existing template, so use a String of content. StringResourceRepository repo = (StringResourceRepository) templateEngine.getApplicationAttribute(RESOURCE_REPOSITORY_NAME); repo.putStringResource(contentName, content); // Turn content into a template. template = templateEngine.getTemplate(contentName); - templateHeaders = new String[] {}; } StringWriter writer = new StringWriter(); From d939786a46ed7bc141ea240a86c8a509161ca4b5 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Tue, 27 Jun 2023 17:23:26 -0400 Subject: [PATCH 292/510] Simplify and modernize the code. Store content directly in the template. --- .../src/main/java/org/dspace/core/Email.java | 58 ++++++++----------- 1 file changed, 25 insertions(+), 33 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/core/Email.java b/dspace-api/src/main/java/org/dspace/core/Email.java index a64a85a07351..f6df740a53ef 100644 --- a/dspace-api/src/main/java/org/dspace/core/Email.java +++ b/dspace-api/src/main/java/org/dspace/core/Email.java @@ -21,7 +21,6 @@ import java.util.Collections; import java.util.Date; import java.util.Enumeration; -import java.util.Iterator; import java.util.List; import java.util.Properties; import javax.activation.DataHandler; @@ -41,7 +40,6 @@ import javax.mail.internet.MimeMultipart; import javax.mail.internet.ParseException; -import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.velocity.Template; @@ -140,7 +138,6 @@ public class Email { /** * The content of the message */ - private String content; private String contentName; /** @@ -201,7 +198,6 @@ public Email() { moreAttachments = new ArrayList<>(10); subject = ""; template = null; - content = ""; replyTo = null; charset = null; } @@ -221,12 +217,20 @@ public void addRecipient(String email) { * "Subject:" line must be stripped. * * @param name a name for this message body - * @param cnt the content of the message + * @param content the content of the message */ - public void setContent(String name, String cnt) { - content = cnt; + public void setContent(String name, String content) { contentName = name; arguments.clear(); + + VelocityEngine templateEngine = new VelocityEngine(); + templateEngine.init(VELOCITY_PROPERTIES); + + StringResourceRepository repo = (StringResourceRepository) + templateEngine.getApplicationAttribute(RESOURCE_REPOSITORY_NAME); + repo.putStringResource(contentName, content); + // Turn content into a template. + template = templateEngine.getTemplate(contentName); } /** @@ -328,15 +332,20 @@ public void reset() { * {@code mail.message.headers} then that name and its value will be added * to the message's headers. * - *

"subject" is treated specially: if {@link setSubject()} has not been called, - * the value of any "subject" property will be used as if setSubject had - * been called with that value. Thus a template may define its subject, but - * the caller may override it. + *

"subject" is treated specially: if {@link setSubject()} has not been + * called, the value of any "subject" property will be used as if setSubject + * had been called with that value. Thus a template may define its subject, + * but the caller may override it. * * @throws MessagingException if there was a problem sending the mail. * @throws IOException if IO error */ public void send() throws MessagingException, IOException { + if (null == template) { + // No template -- no content -- PANIC!!! + throw new MessagingException("Email has no body"); + } + ConfigurationService config = DSpaceServicesFactory.getInstance().getConfigurationService(); @@ -356,36 +365,18 @@ public void send() throws MessagingException, IOException { MimeMessage message = new MimeMessage(session); // Set the recipients of the message - Iterator i = recipients.iterator(); - - while (i.hasNext()) { - message.addRecipient(Message.RecipientType.TO, new InternetAddress( - i.next())); + for (String recipient : recipients) { + message.addRecipient(Message.RecipientType.TO, + new InternetAddress(recipient)); } // Get headers defined by the template. String[] templateHeaders = config.getArrayProperty("mail.message.headers"); // Format the mail message body - VelocityEngine templateEngine = new VelocityEngine(); - templateEngine.init(VELOCITY_PROPERTIES); - VelocityContext vctx = new VelocityContext(); vctx.put("config", new UnmodifiableConfigurationService(config)); vctx.put("params", Collections.unmodifiableList(arguments)); - if (null == template) { - if (StringUtils.isBlank(content)) { - // No template and no content -- PANIC!!! - throw new MessagingException("Email has no body"); - } - // No existing template, so use a String of content. - StringResourceRepository repo = (StringResourceRepository) - templateEngine.getApplicationAttribute(RESOURCE_REPOSITORY_NAME); - repo.putStringResource(contentName, content); - // Turn content into a template. - template = templateEngine.getTemplate(contentName); - } - StringWriter writer = new StringWriter(); try { template.merge(vctx, writer); @@ -452,7 +443,8 @@ public void send() throws MessagingException, IOException { // add the stream messageBodyPart = new MimeBodyPart(); messageBodyPart.setDataHandler(new DataHandler( - new InputStreamDataSource(attachment.name,attachment.mimetype,attachment.is))); + new InputStreamDataSource(attachment.name, + attachment.mimetype, attachment.is))); messageBodyPart.setFileName(attachment.name); multipart.addBodyPart(messageBodyPart); } From f66ca33b0627c1b0789c9c3ce407463f5dc3356e Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Wed, 28 Jun 2023 10:36:30 -0400 Subject: [PATCH 293/510] Rename test suites that are really integration testing. --- ...sswordValidatorTest.java => RegexPasswordValidatorIT.java} | 2 +- ...ningTest.java => RelationshipServiceImplVersioningIT.java} | 2 +- ...ationshipsTest.java => VersioningWithRelationshipsIT.java} | 2 +- ...elationshipDAOImplTest.java => RelationshipDAOImplIT.java} | 4 ++-- ...hipTypeDAOImplTest.java => RelationshipTypeDAOImplIT.java} | 4 ++-- .../service/{ItemServiceTest.java => ItemServiceIT.java} | 4 ++-- ...iderTest.java => VersionedHandleIdentifierProviderIT.java} | 2 +- 7 files changed, 10 insertions(+), 10 deletions(-) rename dspace-api/src/test/java/org/dspace/authorize/{RegexPasswordValidatorTest.java => RegexPasswordValidatorIT.java} (97%) rename dspace-api/src/test/java/org/dspace/content/{RelationshipServiceImplVersioningTest.java => RelationshipServiceImplVersioningIT.java} (99%) rename dspace-api/src/test/java/org/dspace/content/{VersioningWithRelationshipsTest.java => VersioningWithRelationshipsIT.java} (99%) rename dspace-api/src/test/java/org/dspace/content/dao/{RelationshipDAOImplTest.java => RelationshipDAOImplIT.java} (98%) rename dspace-api/src/test/java/org/dspace/content/dao/{RelationshipTypeDAOImplTest.java => RelationshipTypeDAOImplIT.java} (98%) rename dspace-api/src/test/java/org/dspace/content/service/{ItemServiceTest.java => ItemServiceIT.java} (99%) rename dspace-api/src/test/java/org/dspace/identifier/{VersionedHandleIdentifierProviderTest.java => VersionedHandleIdentifierProviderIT.java} (97%) diff --git a/dspace-api/src/test/java/org/dspace/authorize/RegexPasswordValidatorTest.java b/dspace-api/src/test/java/org/dspace/authorize/RegexPasswordValidatorIT.java similarity index 97% rename from dspace-api/src/test/java/org/dspace/authorize/RegexPasswordValidatorTest.java rename to dspace-api/src/test/java/org/dspace/authorize/RegexPasswordValidatorIT.java index df333fa500c9..7286fb8e8374 100644 --- a/dspace-api/src/test/java/org/dspace/authorize/RegexPasswordValidatorTest.java +++ b/dspace-api/src/test/java/org/dspace/authorize/RegexPasswordValidatorIT.java @@ -26,7 +26,7 @@ * @author Luca Giamminonni (luca.giamminonni at 4science.it) */ @RunWith(MockitoJUnitRunner.class) -public class RegexPasswordValidatorTest extends AbstractIntegrationTest { +public class RegexPasswordValidatorIT extends AbstractIntegrationTest { @Mock private ConfigurationService configurationService; diff --git a/dspace-api/src/test/java/org/dspace/content/RelationshipServiceImplVersioningTest.java b/dspace-api/src/test/java/org/dspace/content/RelationshipServiceImplVersioningIT.java similarity index 99% rename from dspace-api/src/test/java/org/dspace/content/RelationshipServiceImplVersioningTest.java rename to dspace-api/src/test/java/org/dspace/content/RelationshipServiceImplVersioningIT.java index d42213da2cf8..1b6f23032d57 100644 --- a/dspace-api/src/test/java/org/dspace/content/RelationshipServiceImplVersioningTest.java +++ b/dspace-api/src/test/java/org/dspace/content/RelationshipServiceImplVersioningIT.java @@ -26,7 +26,7 @@ import org.junit.Before; import org.junit.Test; -public class RelationshipServiceImplVersioningTest extends AbstractIntegrationTestWithDatabase { +public class RelationshipServiceImplVersioningIT extends AbstractIntegrationTestWithDatabase { private RelationshipService relationshipService; private RelationshipDAO relationshipDAO; diff --git a/dspace-api/src/test/java/org/dspace/content/VersioningWithRelationshipsTest.java b/dspace-api/src/test/java/org/dspace/content/VersioningWithRelationshipsIT.java similarity index 99% rename from dspace-api/src/test/java/org/dspace/content/VersioningWithRelationshipsTest.java rename to dspace-api/src/test/java/org/dspace/content/VersioningWithRelationshipsIT.java index 528568c4e5fb..44653300e0de 100644 --- a/dspace-api/src/test/java/org/dspace/content/VersioningWithRelationshipsTest.java +++ b/dspace-api/src/test/java/org/dspace/content/VersioningWithRelationshipsIT.java @@ -70,7 +70,7 @@ import org.junit.Test; import org.springframework.beans.factory.config.AutowireCapableBeanFactory; -public class VersioningWithRelationshipsTest extends AbstractIntegrationTestWithDatabase { +public class VersioningWithRelationshipsIT extends AbstractIntegrationTestWithDatabase { private final RelationshipService relationshipService = ContentServiceFactory.getInstance().getRelationshipService(); diff --git a/dspace-api/src/test/java/org/dspace/content/dao/RelationshipDAOImplTest.java b/dspace-api/src/test/java/org/dspace/content/dao/RelationshipDAOImplIT.java similarity index 98% rename from dspace-api/src/test/java/org/dspace/content/dao/RelationshipDAOImplTest.java rename to dspace-api/src/test/java/org/dspace/content/dao/RelationshipDAOImplIT.java index b6f5da6be065..2d08223b2e3e 100644 --- a/dspace-api/src/test/java/org/dspace/content/dao/RelationshipDAOImplTest.java +++ b/dspace-api/src/test/java/org/dspace/content/dao/RelationshipDAOImplIT.java @@ -39,9 +39,9 @@ * Created by: Andrew Wood * Date: 20 Sep 2019 */ -public class RelationshipDAOImplTest extends AbstractIntegrationTest { +public class RelationshipDAOImplIT extends AbstractIntegrationTest { - private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(RelationshipDAOImplTest.class); + private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(RelationshipDAOImplIT.class); private Relationship relationship; diff --git a/dspace-api/src/test/java/org/dspace/content/dao/RelationshipTypeDAOImplTest.java b/dspace-api/src/test/java/org/dspace/content/dao/RelationshipTypeDAOImplIT.java similarity index 98% rename from dspace-api/src/test/java/org/dspace/content/dao/RelationshipTypeDAOImplTest.java rename to dspace-api/src/test/java/org/dspace/content/dao/RelationshipTypeDAOImplIT.java index 3fff6fec4762..ff7d03b49f6d 100644 --- a/dspace-api/src/test/java/org/dspace/content/dao/RelationshipTypeDAOImplTest.java +++ b/dspace-api/src/test/java/org/dspace/content/dao/RelationshipTypeDAOImplIT.java @@ -35,9 +35,9 @@ import org.junit.Before; import org.junit.Test; -public class RelationshipTypeDAOImplTest extends AbstractIntegrationTest { +public class RelationshipTypeDAOImplIT extends AbstractIntegrationTest { - private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(RelationshipTypeDAOImplTest.class); + private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(RelationshipTypeDAOImplIT.class); private Relationship relationship; diff --git a/dspace-api/src/test/java/org/dspace/content/service/ItemServiceTest.java b/dspace-api/src/test/java/org/dspace/content/service/ItemServiceIT.java similarity index 99% rename from dspace-api/src/test/java/org/dspace/content/service/ItemServiceTest.java rename to dspace-api/src/test/java/org/dspace/content/service/ItemServiceIT.java index 50b4d3f3b48e..e40577ef36ea 100644 --- a/dspace-api/src/test/java/org/dspace/content/service/ItemServiceTest.java +++ b/dspace-api/src/test/java/org/dspace/content/service/ItemServiceIT.java @@ -54,8 +54,8 @@ import org.junit.Before; import org.junit.Test; -public class ItemServiceTest extends AbstractIntegrationTestWithDatabase { - private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(ItemServiceTest.class); +public class ItemServiceIT extends AbstractIntegrationTestWithDatabase { + private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(ItemServiceIT.class); protected RelationshipService relationshipService = ContentServiceFactory.getInstance().getRelationshipService(); protected RelationshipTypeService relationshipTypeService = ContentServiceFactory.getInstance() diff --git a/dspace-api/src/test/java/org/dspace/identifier/VersionedHandleIdentifierProviderTest.java b/dspace-api/src/test/java/org/dspace/identifier/VersionedHandleIdentifierProviderIT.java similarity index 97% rename from dspace-api/src/test/java/org/dspace/identifier/VersionedHandleIdentifierProviderTest.java rename to dspace-api/src/test/java/org/dspace/identifier/VersionedHandleIdentifierProviderIT.java index 1bc6bf140832..7e549f6cae33 100644 --- a/dspace-api/src/test/java/org/dspace/identifier/VersionedHandleIdentifierProviderTest.java +++ b/dspace-api/src/test/java/org/dspace/identifier/VersionedHandleIdentifierProviderIT.java @@ -27,7 +27,7 @@ import org.junit.Before; import org.junit.Test; -public class VersionedHandleIdentifierProviderTest extends AbstractIntegrationTestWithDatabase { +public class VersionedHandleIdentifierProviderIT extends AbstractIntegrationTestWithDatabase { private ServiceManager serviceManager; private IdentifierServiceImpl identifierService; From db81d758a947a9bdbb63fea9e872bc9b52a377ff Mon Sep 17 00:00:00 2001 From: Agustina Martinez Date: Wed, 28 Jun 2023 19:10:37 +0100 Subject: [PATCH 294/510] OAI: add support to extract embargo from bitstreams and expose it in OAI metadata --- .../java/org/dspace/xoai/util/ItemUtils.java | 32 +++++++++++++++++++ .../oai/metadataFormats/uketd_dc.xsl | 5 +++ 2 files changed, 37 insertions(+) diff --git a/dspace-oai/src/main/java/org/dspace/xoai/util/ItemUtils.java b/dspace-oai/src/main/java/org/dspace/xoai/util/ItemUtils.java index 955c3a78c392..f15e82ce52d0 100644 --- a/dspace-oai/src/main/java/org/dspace/xoai/util/ItemUtils.java +++ b/dspace-oai/src/main/java/org/dspace/xoai/util/ItemUtils.java @@ -11,6 +11,8 @@ import java.io.IOException; import java.io.InputStream; import java.sql.SQLException; +import java.text.SimpleDateFormat; +import java.util.Date; import java.util.List; import com.lyncode.xoai.dataprovider.xml.xoai.Element; @@ -21,6 +23,9 @@ import org.dspace.app.util.factory.UtilServiceFactory; import org.dspace.app.util.service.MetadataExposureService; import org.dspace.authorize.AuthorizeException; +import org.dspace.authorize.ResourcePolicy; +import org.dspace.authorize.factory.AuthorizeServiceFactory; +import org.dspace.authorize.service.AuthorizeService; import org.dspace.content.Bitstream; import org.dspace.content.Bundle; import org.dspace.content.Item; @@ -34,6 +39,9 @@ import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.core.Utils; +import org.dspace.eperson.Group; +import org.dspace.eperson.factory.EPersonServiceFactory; +import org.dspace.eperson.service.GroupService; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; import org.dspace.xoai.data.DSpaceItem; @@ -57,6 +65,9 @@ public class ItemUtils { private static final BitstreamService bitstreamService = ContentServiceFactory.getInstance().getBitstreamService(); + private static final AuthorizeService authorizeService = + AuthorizeServiceFactory.getInstance().getAuthorizeService(); + private static final ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); /** @@ -136,6 +147,9 @@ private static Element createBundlesElement(Context context, Item item) throws S if (description != null) { bitstream.getField().add(createValue("description", description)); } + // Add bitstream embargo information (READ policy present, for Anonymous group with a start date) + addEmbargoField(context, bit, bitstream); + bitstream.getField().add(createValue("format", bit.getFormat(context).getMIMEType())); bitstream.getField().add(createValue("size", "" + bit.getSizeBytes())); bitstream.getField().add(createValue("url", url)); @@ -148,6 +162,24 @@ private static Element createBundlesElement(Context context, Item item) throws S return bundles; } + private static void addEmbargoField(Context context, Bitstream bit, Element bitstream) throws SQLException { + GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); + Group anonymousGroup = groupService.findByName(context, Group.ANONYMOUS); + List policies = authorizeService.findPoliciesByDSOAndType(context, bit, ResourcePolicy.TYPE_CUSTOM); + + for (ResourcePolicy policy : policies) { + if (policy.getGroup() == anonymousGroup && policy.getAction() == Constants.READ) { + Date startDate = policies.get(0).getStartDate(); + + if (startDate != null && startDate.after(new Date())) { + SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd"); + bitstream.getField().add( + createValue("embargo", formatter.format(startDate))); + } + } + } + } + private static Element createLicenseElement(Context context, Item item) throws SQLException, AuthorizeException, IOException { Element license = create("license"); diff --git a/dspace/config/crosswalks/oai/metadataFormats/uketd_dc.xsl b/dspace/config/crosswalks/oai/metadataFormats/uketd_dc.xsl index a3a4e6667046..b9d81aef5da8 100644 --- a/dspace/config/crosswalks/oai/metadataFormats/uketd_dc.xsl +++ b/dspace/config/crosswalks/oai/metadataFormats/uketd_dc.xsl @@ -123,6 +123,11 @@ + + + + From 51e60fbcf92ea731c4e355c9cf080d251ffbf68f Mon Sep 17 00:00:00 2001 From: Agustina Martinez Date: Wed, 28 Jun 2023 19:27:35 +0100 Subject: [PATCH 295/510] ItemUtils.java: added method doc --- .../main/java/org/dspace/xoai/util/ItemUtils.java | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/dspace-oai/src/main/java/org/dspace/xoai/util/ItemUtils.java b/dspace-oai/src/main/java/org/dspace/xoai/util/ItemUtils.java index f15e82ce52d0..b1b949770dbd 100644 --- a/dspace-oai/src/main/java/org/dspace/xoai/util/ItemUtils.java +++ b/dspace-oai/src/main/java/org/dspace/xoai/util/ItemUtils.java @@ -162,10 +162,17 @@ private static Element createBundlesElement(Context context, Item item) throws S return bundles; } - private static void addEmbargoField(Context context, Bitstream bit, Element bitstream) throws SQLException { + /** + * This method will add embargo metadata for all bitstreams with an active embargo + * @param context + * @param bitstream the bitstream object + * @param bitstreamEl the bitstream metadata object to add embargo value to + * @throws SQLException + */ + private static void addEmbargoField(Context context, Bitstream bitstream, Element bitstreamEl) throws SQLException { GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); Group anonymousGroup = groupService.findByName(context, Group.ANONYMOUS); - List policies = authorizeService.findPoliciesByDSOAndType(context, bit, ResourcePolicy.TYPE_CUSTOM); + List policies = authorizeService.findPoliciesByDSOAndType(context, bitstream, ResourcePolicy.TYPE_CUSTOM); for (ResourcePolicy policy : policies) { if (policy.getGroup() == anonymousGroup && policy.getAction() == Constants.READ) { @@ -173,7 +180,7 @@ private static void addEmbargoField(Context context, Bitstream bit, Element bits if (startDate != null && startDate.after(new Date())) { SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd"); - bitstream.getField().add( + bitstreamEl.getField().add( createValue("embargo", formatter.format(startDate))); } } From 538be7f09ba790a4ab7099e7027e1e8f6a9c62ea Mon Sep 17 00:00:00 2001 From: Agustina Martinez Date: Thu, 29 Jun 2023 09:06:08 +0100 Subject: [PATCH 296/510] ItemUtils.java: improved method to account for multiple embargo policies and select the longest embargo --- .../java/org/dspace/xoai/util/ItemUtils.java | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/dspace-oai/src/main/java/org/dspace/xoai/util/ItemUtils.java b/dspace-oai/src/main/java/org/dspace/xoai/util/ItemUtils.java index b1b949770dbd..107454ecd082 100644 --- a/dspace-oai/src/main/java/org/dspace/xoai/util/ItemUtils.java +++ b/dspace-oai/src/main/java/org/dspace/xoai/util/ItemUtils.java @@ -14,6 +14,8 @@ import java.text.SimpleDateFormat; import java.util.Date; import java.util.List; +import java.util.ArrayList; +import java.util.Collections; import com.lyncode.xoai.dataprovider.xml.xoai.Element; import com.lyncode.xoai.dataprovider.xml.xoai.Metadata; @@ -163,7 +165,8 @@ private static Element createBundlesElement(Context context, Item item) throws S } /** - * This method will add embargo metadata for all bitstreams with an active embargo + * This method will add embargo metadata for a give bitstream with an active embargo. + * It will parse of relevant policies and select the longest active embargo * @param context * @param bitstream the bitstream object * @param bitstreamEl the bitstream metadata object to add embargo value to @@ -174,17 +177,23 @@ private static void addEmbargoField(Context context, Bitstream bitstream, Elemen Group anonymousGroup = groupService.findByName(context, Group.ANONYMOUS); List policies = authorizeService.findPoliciesByDSOAndType(context, bitstream, ResourcePolicy.TYPE_CUSTOM); + List embargoDates = new ArrayList<>(); + // Account for cases where there could be more than one embargo policy for (ResourcePolicy policy : policies) { if (policy.getGroup() == anonymousGroup && policy.getAction() == Constants.READ) { - Date startDate = policies.get(0).getStartDate(); - + Date startDate = policy.getStartDate(); if (startDate != null && startDate.after(new Date())) { - SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd"); - bitstreamEl.getField().add( - createValue("embargo", formatter.format(startDate))); + embargoDates.add(startDate); } } } + if (embargoDates.size() >= 1) { + // Sort array of dates to extract the longest embargo + SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd"); + Collections.sort(embargoDates, Date::compareTo); + bitstreamEl.getField().add( + createValue("embargo", formatter.format(embargoDates.get(embargoDates.size() - 1)))); + } } private static Element createLicenseElement(Context context, Item item) From 2b5c4a5f4a80fb8a311437676bc351dc75d257f0 Mon Sep 17 00:00:00 2001 From: Toni Prieto Date: Thu, 29 Jun 2023 13:00:59 +0200 Subject: [PATCH 297/510] Improve performance of automatic discovery re-index after database changes --- .../src/main/java/org/dspace/storage/rdbms/DatabaseUtils.java | 1 + 1 file changed, 1 insertion(+) diff --git a/dspace-api/src/main/java/org/dspace/storage/rdbms/DatabaseUtils.java b/dspace-api/src/main/java/org/dspace/storage/rdbms/DatabaseUtils.java index 89010a73087f..0732eea2a0b9 100644 --- a/dspace-api/src/main/java/org/dspace/storage/rdbms/DatabaseUtils.java +++ b/dspace-api/src/main/java/org/dspace/storage/rdbms/DatabaseUtils.java @@ -1465,6 +1465,7 @@ public void run() { Context context = null; try { context = new Context(); + context.setMode(Context.Mode.READ_ONLY); context.turnOffAuthorisationSystem(); log.info( "Post database migration, reindexing all content in Discovery search and browse engine"); From af55090245baf3313407ae1c103b2db53b97b9d4 Mon Sep 17 00:00:00 2001 From: Toni Prieto Date: Thu, 29 Jun 2023 14:31:39 +0200 Subject: [PATCH 298/510] Ignore vocabulary indexes without discovery facet to avoid NPE --- .../content/authority/ChoiceAuthorityServiceImpl.java | 6 ++++++ 1 file changed, 6 insertions(+) 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 ec8f8769be52..4cac1da31490 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 @@ -585,6 +585,12 @@ public DSpaceControlledVocabularyIndex getVocabularyIndex(String nameVocab) { break; } } + + // If there is no matching facet, return null to ignore this vocabulary index + if (matchingFacet == null) { + return null; + } + DSpaceControlledVocabularyIndex vocabularyIndex = new DSpaceControlledVocabularyIndex((DSpaceControlledVocabulary) source, metadataFields, matchingFacet); From ea4565bd6016d795b2cb89834c65acc8e0e0c977 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 30 Jun 2023 14:37:06 -0500 Subject: [PATCH 299/510] [maven-release-plugin] prepare branch dspace-7_x --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 4c9799243f8c..3873fa372c66 100644 --- a/pom.xml +++ b/pom.xml @@ -1927,7 +1927,7 @@ scm:git:git@github.com:DSpace/DSpace.git scm:git:git@github.com:DSpace/DSpace.git git@github.com:DSpace/DSpace.git - HEAD + dspace-7_x From 2b42811e971853c85caf8487411de79515e2bcf3 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 30 Jun 2023 14:37:09 -0500 Subject: [PATCH 300/510] [maven-release-plugin] prepare for next development iteration --- dspace-api/pom.xml | 2 +- dspace-iiif/pom.xml | 2 +- dspace-oai/pom.xml | 2 +- dspace-rdf/pom.xml | 2 +- dspace-rest/pom.xml | 4 ++-- dspace-server-webapp/pom.xml | 2 +- dspace-services/pom.xml | 2 +- dspace-sword/pom.xml | 2 +- dspace-swordv2/pom.xml | 2 +- dspace/modules/additions/pom.xml | 2 +- dspace/modules/pom.xml | 2 +- dspace/modules/rest/pom.xml | 2 +- dspace/modules/server/pom.xml | 2 +- dspace/pom.xml | 2 +- pom.xml | 32 ++++++++++++++++---------------- 15 files changed, 31 insertions(+), 31 deletions(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index c05546d56944..6379572ba488 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -12,7 +12,7 @@ org.dspace dspace-parent - 7.6.1-SNAPSHOT + 8.0-SNAPSHOT .. diff --git a/dspace-iiif/pom.xml b/dspace-iiif/pom.xml index 30e20489b82c..6e93a88e1351 100644 --- a/dspace-iiif/pom.xml +++ b/dspace-iiif/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.6.1-SNAPSHOT + 8.0-SNAPSHOT .. diff --git a/dspace-oai/pom.xml b/dspace-oai/pom.xml index 08e732d45733..808940eb7b88 100644 --- a/dspace-oai/pom.xml +++ b/dspace-oai/pom.xml @@ -8,7 +8,7 @@ dspace-parent org.dspace - 7.6.1-SNAPSHOT + 8.0-SNAPSHOT .. diff --git a/dspace-rdf/pom.xml b/dspace-rdf/pom.xml index 09c3e704de42..f21381eb4ea3 100644 --- a/dspace-rdf/pom.xml +++ b/dspace-rdf/pom.xml @@ -9,7 +9,7 @@ org.dspace dspace-parent - 7.6.1-SNAPSHOT + 8.0-SNAPSHOT .. diff --git a/dspace-rest/pom.xml b/dspace-rest/pom.xml index 320567f3c104..d7daf92aba57 100644 --- a/dspace-rest/pom.xml +++ b/dspace-rest/pom.xml @@ -3,7 +3,7 @@ org.dspace dspace-rest war - 7.6.1-SNAPSHOT + 8.0-SNAPSHOT DSpace (Deprecated) REST Webapp DSpace RESTful Web Services API. NOTE: this REST API is DEPRECATED. Please consider using the REST API in the dspace-server-webapp instead! @@ -12,7 +12,7 @@ org.dspace dspace-parent - 7.6.1-SNAPSHOT + 8.0-SNAPSHOT .. diff --git a/dspace-server-webapp/pom.xml b/dspace-server-webapp/pom.xml index 2221f9ca0e62..6e07365499d1 100644 --- a/dspace-server-webapp/pom.xml +++ b/dspace-server-webapp/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.6.1-SNAPSHOT + 8.0-SNAPSHOT .. diff --git a/dspace-services/pom.xml b/dspace-services/pom.xml index fe7c6ab8b69d..e3db95abf574 100644 --- a/dspace-services/pom.xml +++ b/dspace-services/pom.xml @@ -9,7 +9,7 @@ org.dspace dspace-parent - 7.6.1-SNAPSHOT + 8.0-SNAPSHOT diff --git a/dspace-sword/pom.xml b/dspace-sword/pom.xml index 777041775d46..349ac3abd0dd 100644 --- a/dspace-sword/pom.xml +++ b/dspace-sword/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.6.1-SNAPSHOT + 8.0-SNAPSHOT .. diff --git a/dspace-swordv2/pom.xml b/dspace-swordv2/pom.xml index c3fbdecb79d4..740351d5db69 100644 --- a/dspace-swordv2/pom.xml +++ b/dspace-swordv2/pom.xml @@ -13,7 +13,7 @@ org.dspace dspace-parent - 7.6.1-SNAPSHOT + 8.0-SNAPSHOT .. diff --git a/dspace/modules/additions/pom.xml b/dspace/modules/additions/pom.xml index dd6dbcb1ffd7..922e0f0fe51b 100644 --- a/dspace/modules/additions/pom.xml +++ b/dspace/modules/additions/pom.xml @@ -17,7 +17,7 @@ org.dspace modules - 7.6.1-SNAPSHOT + 8.0-SNAPSHOT .. diff --git a/dspace/modules/pom.xml b/dspace/modules/pom.xml index e66622ce6b34..3a9e5caa8ab3 100644 --- a/dspace/modules/pom.xml +++ b/dspace/modules/pom.xml @@ -11,7 +11,7 @@ org.dspace dspace-parent - 7.6.1-SNAPSHOT + 8.0-SNAPSHOT ../../pom.xml diff --git a/dspace/modules/rest/pom.xml b/dspace/modules/rest/pom.xml index 14c6ff93f761..d3713454f672 100644 --- a/dspace/modules/rest/pom.xml +++ b/dspace/modules/rest/pom.xml @@ -13,7 +13,7 @@ org.dspace modules - 7.6.1-SNAPSHOT + 8.0-SNAPSHOT .. diff --git a/dspace/modules/server/pom.xml b/dspace/modules/server/pom.xml index bebb6d183d7e..d0a25332aac8 100644 --- a/dspace/modules/server/pom.xml +++ b/dspace/modules/server/pom.xml @@ -13,7 +13,7 @@ just adding new jar in the classloader modules org.dspace - 7.6.1-SNAPSHOT + 8.0-SNAPSHOT .. diff --git a/dspace/pom.xml b/dspace/pom.xml index bfa21f5d664f..ac7af682da3e 100644 --- a/dspace/pom.xml +++ b/dspace/pom.xml @@ -16,7 +16,7 @@ org.dspace dspace-parent - 7.6.1-SNAPSHOT + 8.0-SNAPSHOT ../pom.xml diff --git a/pom.xml b/pom.xml index 3873fa372c66..a082bd0524f4 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ org.dspace dspace-parent pom - 7.6.1-SNAPSHOT + 8.0-SNAPSHOT DSpace Parent Project DSpace open source software is a turnkey institutional repository application. @@ -872,14 +872,14 @@ org.dspace dspace-rest - 7.6.1-SNAPSHOT + 8.0-SNAPSHOT jar classes org.dspace dspace-rest - 7.6.1-SNAPSHOT + 8.0-SNAPSHOT war @@ -1030,69 +1030,69 @@ org.dspace dspace-api - 7.6.1-SNAPSHOT + 8.0-SNAPSHOT org.dspace dspace-api test-jar - 7.6.1-SNAPSHOT + 8.0-SNAPSHOT test org.dspace.modules additions - 7.6.1-SNAPSHOT + 8.0-SNAPSHOT org.dspace dspace-sword - 7.6.1-SNAPSHOT + 8.0-SNAPSHOT org.dspace dspace-swordv2 - 7.6.1-SNAPSHOT + 8.0-SNAPSHOT org.dspace dspace-oai - 7.6.1-SNAPSHOT + 8.0-SNAPSHOT org.dspace dspace-services - 7.6.1-SNAPSHOT + 8.0-SNAPSHOT org.dspace dspace-server-webapp test-jar - 7.6.1-SNAPSHOT + 8.0-SNAPSHOT test org.dspace dspace-rdf - 7.6.1-SNAPSHOT + 8.0-SNAPSHOT org.dspace dspace-iiif - 7.6.1-SNAPSHOT + 8.0-SNAPSHOT org.dspace dspace-server-webapp - 7.6.1-SNAPSHOT + 8.0-SNAPSHOT jar classes org.dspace dspace-server-webapp - 7.6.1-SNAPSHOT + 8.0-SNAPSHOT war @@ -1927,7 +1927,7 @@ scm:git:git@github.com:DSpace/DSpace.git scm:git:git@github.com:DSpace/DSpace.git git@github.com:DSpace/DSpace.git - dspace-7_x + HEAD From 8633799b654e0c9e56a3f11411488d5fa3f9c267 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 31 May 2023 16:45:54 +0000 Subject: [PATCH 301/510] Update dependency com.flipkart.zjsonpatch:zjsonpatch to v0.4.14 --- dspace-server-webapp/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-server-webapp/pom.xml b/dspace-server-webapp/pom.xml index 6e07365499d1..6cec67baa397 100644 --- a/dspace-server-webapp/pom.xml +++ b/dspace-server-webapp/pom.xml @@ -288,7 +288,7 @@ com.flipkart.zjsonpatch zjsonpatch - 0.4.6 + 0.4.14 From 3613320e2bc0316f46af91b491c79a4e4b715a35 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 2 Jun 2023 10:08:29 +0000 Subject: [PATCH 302/510] Update dependency de.digitalcollections.iiif:iiif-apis to v0.3.10 --- dspace-iiif/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-iiif/pom.xml b/dspace-iiif/pom.xml index 6e93a88e1351..2f34671139c8 100644 --- a/dspace-iiif/pom.xml +++ b/dspace-iiif/pom.xml @@ -93,7 +93,7 @@ de.digitalcollections.iiif iiif-apis - 0.3.9 + 0.3.10 org.javassist From ad05c6a230b01f2a609a33a1f9a9607715fea2d2 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 2 Jun 2023 09:58:13 +0000 Subject: [PATCH 303/510] Update dependency com.h2database:h2 to v2.1.214 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index a082bd0524f4..65e54dc61a46 100644 --- a/pom.xml +++ b/pom.xml @@ -1694,7 +1694,7 @@ com.h2database h2 - 2.1.210 + 2.1.214 test From 2cc37373805436398586666c267bf06f1a77ccc7 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 2 Jun 2023 10:08:34 +0000 Subject: [PATCH 304/510] Update dependency dnsjava:dnsjava to v2.1.9 --- dspace-api/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index 6379572ba488..ea284c956c61 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -632,7 +632,7 @@ dnsjava dnsjava - 2.1.7 + 2.1.9 From 8d65ad4f3e2832647b9eb29469f863e8a1497bab Mon Sep 17 00:00:00 2001 From: Alan Orth Date: Fri, 2 Jun 2023 19:36:59 +0300 Subject: [PATCH 305/510] pom.xml: bump com.google.code.findbugs:jsr305 Closes: https://github.com/alanorth/DSpace/pull/12 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 65e54dc61a46..be5b6e2fb038 100644 --- a/pom.xml +++ b/pom.xml @@ -1738,7 +1738,7 @@ com.google.code.findbugs jsr305 - 3.0.1 + 3.0.2 provided From 193fdd5118da8e3cb367b983143d545ae0d327b0 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 2 Jun 2023 18:40:03 +0000 Subject: [PATCH 306/510] Update dependency javax.cache:cache-api to v1.1.1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index be5b6e2fb038..d6a9d8c4b2a7 100644 --- a/pom.xml +++ b/pom.xml @@ -35,7 +35,7 @@ 1.3.2 2.3.1 2.3.1 - 1.1.0 + 1.1.1 9.4.51.v20230217 2.20.0 From d8a4694210d2ce95c08cad227d362e64d938aeea Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 2 Jun 2023 16:54:30 +0000 Subject: [PATCH 307/510] Update dependency net.handle:handle to v9.3.1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index d6a9d8c4b2a7..4097404e0016 100644 --- a/pom.xml +++ b/pom.xml @@ -1358,7 +1358,7 @@ net.handle handle - 9.3.0 + 9.3.1 From aedf6e154691b967189ea457dee745204c5beba1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 2 Jun 2023 16:57:30 +0000 Subject: [PATCH 308/510] Update dependency org.apache.ant:ant to v1.10.13 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 4097404e0016..9c2021cb2dfa 100644 --- a/pom.xml +++ b/pom.xml @@ -1336,7 +1336,7 @@ org.apache.ant ant - 1.10.11 + 1.10.13 org.apache.jena From 0f6c3e7b81aa890bf1d5ac76722a2ea0ce34f7a6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 2 Jun 2023 15:00:31 +0000 Subject: [PATCH 309/510] Update dependency junit:junit to v4.13.2 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 9c2021cb2dfa..c2411d20c093 100644 --- a/pom.xml +++ b/pom.xml @@ -1669,7 +1669,7 @@ junit junit - 4.13.1 + 4.13.2 test From 25718ae351ad2b02115b44ec71c5bbe01e558c96 Mon Sep 17 00:00:00 2001 From: Alan Orth Date: Fri, 2 Jun 2023 22:25:29 +0300 Subject: [PATCH 310/510] pom.xml: bump org.apache.httpcomponents Closes: https://github.com/alanorth/DSpace/pull/21 Closes: https://github.com/alanorth/DSpace/pull/22 Closes: https://github.com/alanorth/DSpace/pull/23 --- pom.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index c2411d20c093..9737ca168009 100644 --- a/pom.xml +++ b/pom.xml @@ -1626,17 +1626,17 @@ org.apache.httpcomponents httpcore - 4.4.15 + 4.4.16 org.apache.httpcomponents httpclient - 4.5.13 + 4.5.14 org.apache.httpcomponents httpmime - 4.5.13 + 4.5.14 org.slf4j From b8308ef04924c5fad903949ee995b1d407103b59 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 2 Jun 2023 19:52:57 +0000 Subject: [PATCH 311/510] Update dependency org.ehcache:ehcache to v3.10.8 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 9737ca168009..e64b994ce818 100644 --- a/pom.xml +++ b/pom.xml @@ -27,7 +27,7 @@ 42.6.0 8.11.2 - 3.4.0 + 3.10.8 2.10.0 2.13.4 From 5b7ab0b0044228204bc8993ba75f98ec69ab9dea Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 2 Jun 2023 19:37:49 +0000 Subject: [PATCH 312/510] Update dependency org.flywaydb:flyway-core to v8.5.13 --- dspace-api/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index ea284c956c61..0ea86b6e10b4 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -668,7 +668,7 @@ org.flywaydb flyway-core - 8.4.4 + 8.5.13 From bdd9866cd3851390a00b0664203c605d1b943a2d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 2 Jun 2023 20:33:06 +0000 Subject: [PATCH 313/510] Update dependency com.opencsv:opencsv to v5.7.1 --- dspace-api/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index 0ea86b6e10b4..aab892ee0435 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -791,7 +791,7 @@ com.opencsv opencsv - 5.6 + 5.7.1 From 3cd5acc02744da9ba1108423b0949be6157f914d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 2 Jun 2023 20:32:55 +0000 Subject: [PATCH 314/510] Update dependency org.glassfish.jaxb:jaxb-runtime to v2.3.8 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e64b994ce818..191179a4a75e 100644 --- a/pom.xml +++ b/pom.xml @@ -34,7 +34,7 @@ 2.13.4.2 1.3.2 2.3.1 - 2.3.1 + 2.3.8 1.1.1 9.4.51.v20230217 From 706bf06a6e688ec91baa8912d4204eb83227d4d2 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 2 Jun 2023 19:32:15 +0000 Subject: [PATCH 315/510] Update dependency org.apache.james:apache-mime4j-core to v0.8.9 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 191179a4a75e..bdf7caad5103 100644 --- a/pom.xml +++ b/pom.xml @@ -1302,7 +1302,7 @@ org.apache.james apache-mime4j-core - 0.8.4 + 0.8.9 From 08a5c74848820223f6e9a2a8e7c10d44eae36f89 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 3 Jun 2023 14:47:00 +0000 Subject: [PATCH 316/510] Update dependency commons-io:commons-io to v2.12.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index bdf7caad5103..dde2af21aa9a 100644 --- a/pom.xml +++ b/pom.xml @@ -1489,7 +1489,7 @@ commons-io commons-io - 2.7 + 2.12.0 org.apache.commons From 021a39771c8508cb9b27878f0dfab51991dba0f7 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 3 Jun 2023 14:55:45 +0000 Subject: [PATCH 317/510] Update dependency commons-validator:commons-validator to v1.7 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index dde2af21aa9a..f7fd25bc649d 100644 --- a/pom.xml +++ b/pom.xml @@ -1516,7 +1516,7 @@ commons-validator commons-validator - 1.5.0 + 1.7 joda-time From 6f18a6b2f1f912474c1bd80f14ed9d01f32a7fe0 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 3 Jun 2023 14:55:40 +0000 Subject: [PATCH 318/510] Update dependency commons-codec:commons-codec to v1.15 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index f7fd25bc649d..f45ccb3c1841 100644 --- a/pom.xml +++ b/pom.xml @@ -1456,7 +1456,7 @@ commons-codec commons-codec - 1.10 + 1.15 org.apache.commons From 34ea02f29f5da493d797080bab39b1536bc64e64 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 3 Jun 2023 15:27:30 +0000 Subject: [PATCH 319/510] Update dependency commons-cli:commons-cli to v1.5.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index f45ccb3c1841..a226dd6c5780 100644 --- a/pom.xml +++ b/pom.xml @@ -1451,7 +1451,7 @@ commons-cli commons-cli - 1.4 + 1.5.0 commons-codec From 7627fe0223f085d510354a2ef63e972241c2dc12 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 3 Jun 2023 19:33:38 +0000 Subject: [PATCH 320/510] Update dependency joda-time:joda-time to v2.12.5 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index a226dd6c5780..08e6ceede7d4 100644 --- a/pom.xml +++ b/pom.xml @@ -1521,7 +1521,7 @@ joda-time joda-time - 2.9.2 + 2.12.5 com.sun.mail From 2225edd40fc97e2d52d09453fd3db79b66e01945 Mon Sep 17 00:00:00 2001 From: Alan Orth Date: Sun, 4 Jun 2023 22:25:40 +0300 Subject: [PATCH 321/510] pom.xml: bump Jersey Bump jersey due to jersey-media-json-jackson pulling in a conflicting jakarta.xml.bind-api via transitive dependency in dspace-rest, which is the legacy DSpace 6 REST API. --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 08e6ceede7d4..f29dd3c306ab 100644 --- a/pom.xml +++ b/pom.xml @@ -58,7 +58,7 @@ https://jena.apache.org/documentation/migrate_jena2_jena3.html --> 2.13.0 - 2.35 + 2.39.1 UTF-8 From 60886490037365f60621f3f5f5a898c832617814 Mon Sep 17 00:00:00 2001 From: Alan Orth Date: Sun, 4 Jun 2023 22:26:54 +0300 Subject: [PATCH 322/510] dspace-api/pom.xml: add exclusion for javassist Add an exclusion for org.javassist:javassist due to a dependency convergence error caused by eu.openaire:funders-model pulling in a version conflicting with Jersey's transitive dependency. --- dspace-api/pom.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index aab892ee0435..a32f3ddfc21b 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -818,6 +818,13 @@ eu.openaire funders-model 2.0.0 + + + + org.javassist + javassist + + From b1715b9b48dd500761c27c77b5e93d2b4cb3ead4 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 5 Jun 2023 06:03:24 +0000 Subject: [PATCH 323/510] Update dependency org.webjars.bowergithub.medialize:uri.js to v1.19.11 --- dspace-server-webapp/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-server-webapp/pom.xml b/dspace-server-webapp/pom.xml index 6cec67baa397..f4d47e88c2e6 100644 --- a/dspace-server-webapp/pom.xml +++ b/dspace-server-webapp/pom.xml @@ -322,7 +322,7 @@ org.webjars.bowergithub.medialize uri.js - 1.19.10 + 1.19.11 From ecd3604302e67a1df3f44fb0eeb704739ad6fdcb Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 5 Jun 2023 06:01:11 +0000 Subject: [PATCH 324/510] Update dependency com.fasterxml:classmate to v1.5.1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index f29dd3c306ab..d3f30a8d1b8a 100644 --- a/pom.xml +++ b/pom.xml @@ -1752,7 +1752,7 @@ com.fasterxml classmate - 1.3.0 + 1.5.1 com.fasterxml.jackson.core From 9188c838255c9dc75f581e9ff69e8443a2a4908e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 5 Jun 2023 06:03:29 +0000 Subject: [PATCH 325/510] Update dependency org.webjars.bowergithub.jquery:jquery-dist to v3.7.0 --- dspace-server-webapp/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-server-webapp/pom.xml b/dspace-server-webapp/pom.xml index f4d47e88c2e6..d7a8782b7378 100644 --- a/dspace-server-webapp/pom.xml +++ b/dspace-server-webapp/pom.xml @@ -308,7 +308,7 @@ org.webjars.bowergithub.jquery jquery-dist - 3.6.0 + 3.7.0 From f00c15f449b1e3ebc81ec644f22879046a94a15b Mon Sep 17 00:00:00 2001 From: Alan Orth Date: Mon, 5 Jun 2023 10:33:46 +0300 Subject: [PATCH 326/510] Bump xom:xom dependency No breaking changes, but some bug fixes, performance improvements, and compatibility fixes with Java 17+. See: https://xom.nu/history.html --- dspace-sword/pom.xml | 2 +- pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dspace-sword/pom.xml b/dspace-sword/pom.xml index 349ac3abd0dd..3ddf4d1a839c 100644 --- a/dspace-sword/pom.xml +++ b/dspace-sword/pom.xml @@ -104,7 +104,7 @@ xom xom - 1.3.7 + 1.3.9 commons-io diff --git a/pom.xml b/pom.xml index d3f30a8d1b8a..757c522aa3bd 100644 --- a/pom.xml +++ b/pom.xml @@ -1784,7 +1784,7 @@ xom xom - 1.2.5 + 1.3.9 From 171cd41f0fd060d0a31cbce0d1fddd12aedcd797 Mon Sep 17 00:00:00 2001 From: Alan Orth Date: Mon, 5 Jun 2023 10:36:44 +0300 Subject: [PATCH 327/510] Bump jaxen:jaxen dependency to 2.0.0 Should be mostly drop-in API compatible with Jaxen 1.1.x, but more importantly it makes the xom dependency optional so we can remove the exclusions in our various pom.xml files. See: http://cafeconleche.org/jaxen/releases.html --- dspace-api/pom.xml | 6 ------ pom.xml | 8 +------- 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index a32f3ddfc21b..1946eac9a561 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -492,12 +492,6 @@ jaxen jaxen - - - xom - xom - - org.jdom diff --git a/pom.xml b/pom.xml index 757c522aa3bd..930532b8b1c8 100644 --- a/pom.xml +++ b/pom.xml @@ -1538,13 +1538,7 @@ jaxen jaxen - 1.1.6 - - - xom - xom - - + 2.0.0 org.jdom From 7a74990894068ec8dad43c04925c79ddf3c72654 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 25 Jun 2023 16:37:20 +0000 Subject: [PATCH 328/510] Update dependency org.apache.bcel:bcel to v6.7.0 --- dspace-api/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index 1946eac9a561..4252a443986a 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -803,7 +803,7 @@ org.apache.bcel bcel - 6.6.0 + 6.7.0 test From 8185cd3ebdb7cd4b0d9f16b244dcff4ed7554d1b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 26 Jun 2023 04:40:30 +0000 Subject: [PATCH 329/510] Update dependency org.scala-lang:scala-library to v2.13.11 --- dspace-api/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index 4252a443986a..ee8c21cb64bd 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -923,7 +923,7 @@ org.scala-lang scala-library - 2.13.9 + 2.13.11 test From e3f7f7f30f21df2025647ac119a62d7e1c9138c9 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 26 Jun 2023 04:53:45 +0000 Subject: [PATCH 330/510] Update dependency commons-io:commons-io to v2.13.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 930532b8b1c8..4d01bca24044 100644 --- a/pom.xml +++ b/pom.xml @@ -1489,7 +1489,7 @@ commons-io commons-io - 2.12.0 + 2.13.0 org.apache.commons From 67298a290e1e95f22416964ce97690c6f25abd37 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 26 Jun 2023 04:56:57 +0000 Subject: [PATCH 331/510] Update dependency org.exparity:hamcrest-date to v2.0.8 --- dspace-server-webapp/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-server-webapp/pom.xml b/dspace-server-webapp/pom.xml index d7a8782b7378..42ed115d9174 100644 --- a/dspace-server-webapp/pom.xml +++ b/dspace-server-webapp/pom.xml @@ -541,7 +541,7 @@ org.exparity hamcrest-date - 2.0.7 + 2.0.8 test From c9197418e02c661ef24ac1f4e3e965c167a52917 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 27 Jun 2023 10:49:50 +0000 Subject: [PATCH 332/510] Update dependency commons-codec:commons-codec to v1.16.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 4d01bca24044..5853f1924684 100644 --- a/pom.xml +++ b/pom.xml @@ -1456,7 +1456,7 @@ commons-codec commons-codec - 1.15 + 1.16.0 org.apache.commons From 8ea07264cf2015fda9d52a2a18268af9ea839a56 Mon Sep 17 00:00:00 2001 From: Alan Orth Date: Fri, 30 Jun 2023 21:38:32 +0300 Subject: [PATCH 333/510] pom.xml: update spring boot to v2.7.13 Minor update. Also bump the spring security version to 5.7.9 as is used by spring boot. See: https://github.com/spring-projects/spring-boot/releases/tag/v2.7.13 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 5853f1924684..c43dc6c59782 100644 --- a/pom.xml +++ b/pom.xml @@ -20,8 +20,8 @@ 11 5.3.27 - 2.7.12 - 5.7.8 + 2.7.13 + 5.7.9 5.6.15.Final 6.2.5.Final 42.6.0 From cf87cbea8fc61037d647bf2d4aee1e99ec19f95f Mon Sep 17 00:00:00 2001 From: Alan Orth Date: Fri, 30 Jun 2023 21:41:41 +0300 Subject: [PATCH 334/510] pom.xml: bump spring core version to v5.3.28 Minor version bump with some bug fixes. See: https://github.com/spring-projects/spring-framework/releases/tag/v5.3.28 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index c43dc6c59782..00e1306d592d 100644 --- a/pom.xml +++ b/pom.xml @@ -19,7 +19,7 @@ 11 - 5.3.27 + 5.3.28 2.7.13 5.7.9 5.6.15.Final From 8006329514d1818268518d6deab57ecb80b237e2 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 2 Jul 2023 15:18:28 +0000 Subject: [PATCH 335/510] Update pdfbox-version to v2.0.29 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 00e1306d592d..5dc53194b335 100644 --- a/pom.xml +++ b/pom.xml @@ -39,7 +39,7 @@ 9.4.51.v20230217 2.20.0 - 2.0.28 + 2.0.29 1.19.0 1.7.36 2.5.0 From b846c53baaeae1e19fbbafa3dc7ca724bcaf32c1 Mon Sep 17 00:00:00 2001 From: "David P. Steelman" Date: Mon, 3 Jul 2023 14:09:15 -0400 Subject: [PATCH 336/510] DS-8935. webui.browse.link CrossLinks - Fix for multiple exact matches Fixes #8935 when multiple exact match "webui.browse.link" configuration entries are present that point to different indexes. Modified the code to return the index associated with the given metadata (which is used as the key in the hash map), instead of the key from the keySet (which may not actually be the metadata value being searched for). https://github.com/DSpace/DSpace/issues/8935 --- .../java/org/dspace/browse/CrossLinks.java | 2 +- .../org/dspace/browse/CrossLinksTest.java | 103 ++++++++++++++++++ 2 files changed, 104 insertions(+), 1 deletion(-) create mode 100644 dspace-api/src/test/java/org/dspace/browse/CrossLinksTest.java diff --git a/dspace-api/src/main/java/org/dspace/browse/CrossLinks.java b/dspace-api/src/main/java/org/dspace/browse/CrossLinks.java index 1ce2e558866d..ec4cb199ea1d 100644 --- a/dspace-api/src/main/java/org/dspace/browse/CrossLinks.java +++ b/dspace-api/src/main/java/org/dspace/browse/CrossLinks.java @@ -108,7 +108,7 @@ public String findLinkType(String metadata) { } else { // Exact match, if the key field has no .* wildcard if (links.containsKey(metadata)) { - return links.get(key); + return links.get(metadata); } } } diff --git a/dspace-api/src/test/java/org/dspace/browse/CrossLinksTest.java b/dspace-api/src/test/java/org/dspace/browse/CrossLinksTest.java new file mode 100644 index 000000000000..83aab72d904e --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/browse/CrossLinksTest.java @@ -0,0 +1,103 @@ +/** + * 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.browse; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import org.dspace.AbstractDSpaceTest; +import org.dspace.services.ConfigurationService; +import org.dspace.utils.DSpace; +import org.junit.Before; +import org.junit.Test; + +/** + * Test class for {@link CrossLinks} + */ +public class CrossLinksTest extends AbstractDSpaceTest { + protected ConfigurationService configurationService; + + + @Before + public void setUp() { + configurationService = new DSpace().getConfigurationService(); + } + + @Test + public void testFindLinkType_Null() throws Exception { + CrossLinks crossLinks = new CrossLinks(); + assertNull(crossLinks.findLinkType(null)); + } + + @Test + public void testFindLinkType_NoMatch() throws Exception { + CrossLinks crossLinks = new CrossLinks(); + String metadataField = "foo.bar.baz.does.not.exist"; + assertNull(crossLinks.findLinkType(metadataField)); + } + + @Test + public void testFindLinkType_WildcardMatch() throws Exception { + configurationService.setProperty("webui.browse.link.1", "author:dc.contributor.*"); + CrossLinks crossLinks = new CrossLinks(); + + String metadataField = "dc.contributor.author"; + assertEquals("author",crossLinks.findLinkType(metadataField)); + } + + @Test + public void testFindLinkType_SingleExactMatch_Author() throws Exception { + configurationService.setProperty("webui.browse.link.1", "author:dc.contributor.author"); + CrossLinks crossLinks = new CrossLinks(); + + assertEquals("type",crossLinks.findLinkType("dc.genre")); + assertEquals("author",crossLinks.findLinkType("dc.contributor.author")); + } + + @Test + public void testFindLinkType_SingleExactMatch_Type() throws Exception { + configurationService.setProperty("webui.browse.link.1", "type:dc.genre"); + CrossLinks crossLinks = new CrossLinks(); + + assertEquals("type",crossLinks.findLinkType("dc.genre")); + } + + @Test + public void testFindLinkType_MultipleExactMatches_DifferentIndexes() throws Exception { + configurationService.setProperty("webui.browse.link.1", "author:dc.contributor.author"); + configurationService.setProperty("webui.browse.link.2", "type:dc.genre"); + CrossLinks crossLinks = new CrossLinks(); + + assertEquals("author",crossLinks.findLinkType("dc.contributor.author")); + assertEquals("type",crossLinks.findLinkType("dc.genre")); + } + + @Test + public void testFindLinkType_MultipleWildcardMatches_DifferentIndexes() throws Exception { + configurationService.setProperty("webui.browse.link.1", "author:dc.contributor.*"); + configurationService.setProperty("webui.browse.link.2", "subject:dc.subject.*"); + CrossLinks crossLinks = new CrossLinks(); + + assertEquals("author",crossLinks.findLinkType("dc.contributor.author")); + assertEquals("subject",crossLinks.findLinkType("dc.subject.lcsh")); + } + + @Test + public void testFindLinkType_MultiplExactAndWildcardMatches_DifferentIndexes() throws Exception { + configurationService.setProperty("webui.browse.link.1", "author:dc.contributor.*"); + configurationService.setProperty("webui.browse.link.2", "subject:dc.subject.*"); + configurationService.setProperty("webui.browse.link.3", "type:dc.genre"); + configurationService.setProperty("webui.browse.link.4", "dateissued:dc.date.issued"); + CrossLinks crossLinks = new CrossLinks(); + + assertEquals("author",crossLinks.findLinkType("dc.contributor.author")); + assertEquals("subject",crossLinks.findLinkType("dc.subject.lcsh")); + assertEquals("type",crossLinks.findLinkType("dc.genre")); + assertEquals("dateissued",crossLinks.findLinkType("dc.date.issued")); + } +} From c72facbd74481af2656f1dd6a719b88da75e7419 Mon Sep 17 00:00:00 2001 From: "max.nuding" Date: Thu, 6 Jul 2023 09:17:59 +0200 Subject: [PATCH 337/510] Fix #8933: Only add the base statistic core if it hasn't already been added --- .../java/org/dspace/statistics/SolrLoggerServiceImpl.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/dspace-api/src/main/java/org/dspace/statistics/SolrLoggerServiceImpl.java b/dspace-api/src/main/java/org/dspace/statistics/SolrLoggerServiceImpl.java index 7853c3e11abf..9f34a4204721 100644 --- a/dspace-api/src/main/java/org/dspace/statistics/SolrLoggerServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/statistics/SolrLoggerServiceImpl.java @@ -1691,6 +1691,14 @@ protected synchronized void initSolrYearCores() { statisticYearCores .add(baseSolrUrl.replace("http://", "").replace("https://", "") + statCoreName); } + var baseCore = ((HttpSolrClient) solr) + .getBaseURL() + .replace("http://", "") + .replace("https://", ""); + if (!statisticYearCores.contains(baseCore)) { + //Also add the core containing the current year, if it hasn't been added already + statisticYearCores.add(baseCore); + } //Also add the core containing the current year ! statisticYearCores.add(((HttpSolrClient) solr) .getBaseURL() From 1b94aecb26059b35b762ff5285beecddd2e0d08f Mon Sep 17 00:00:00 2001 From: Nona Luypaert Date: Thu, 6 Jul 2023 16:48:10 +0200 Subject: [PATCH 338/510] 103837: Only send GA4 events for ORIGINAL bitstreams + ITs --- .../google/GoogleAsyncEventListener.java | 32 +++++- .../google/GoogleAsyncEventListenerIT.java | 105 +++++++++++++++++- dspace/config/dspace.cfg | 7 +- 3 files changed, 133 insertions(+), 11 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/google/GoogleAsyncEventListener.java b/dspace-api/src/main/java/org/dspace/google/GoogleAsyncEventListener.java index c169e4712f7f..e84d9f8591f2 100644 --- a/dspace-api/src/main/java/org/dspace/google/GoogleAsyncEventListener.java +++ b/dspace-api/src/main/java/org/dspace/google/GoogleAsyncEventListener.java @@ -22,6 +22,7 @@ import org.apache.commons.lang.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.dspace.content.Bitstream; import org.dspace.content.factory.ContentServiceFactory; import org.dspace.core.Constants; import org.dspace.core.Context; @@ -77,7 +78,7 @@ public void receiveEvent(Event event) { UsageEvent usageEvent = (UsageEvent) event; LOGGER.debug("Usage event received " + event.getName()); - if (isNotBitstreamViewEvent(usageEvent)) { + if (!isContentBitstream(usageEvent)) { return; } @@ -171,9 +172,32 @@ private String getDocumentPath(HttpServletRequest request) { return documentPath; } - private boolean isNotBitstreamViewEvent(UsageEvent usageEvent) { - return usageEvent.getAction() != UsageEvent.Action.VIEW - || usageEvent.getObject().getType() != Constants.BITSTREAM; + /** + * Verifies if the usage event is a content bitstream view event, by checking if:

    + *
  • the usage event is a view event
  • + *
  • the object of the usage event is a bitstream
  • + *
  • the bitstream belongs to the ORIGINAL bundle
+ * This last one can be skipped if 'google-analytics.exclude-non-content-bitstreams' is set to false. + * This will make it so the bundle name is completely ignored when sending events. + */ + private boolean isContentBitstream(UsageEvent usageEvent) { + // check if event is a VIEW event and object is a Bitstream + if (usageEvent.getAction() == UsageEvent.Action.VIEW + || usageEvent.getObject().getType() == Constants.BITSTREAM) { + // check if config is set to true + if (configurationService.getBooleanProperty("google-analytics.exclude-non-content-bitstreams")) { + try { + // check if bitstream belongs to the ORIGINAL bundle + return ((Bitstream) usageEvent.getObject()) + .getBundles().stream() + .anyMatch(bundle -> bundle.getName().equals(Constants.CONTENT_BUNDLE_NAME)); + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + return true; + } + return false; } private boolean isGoogleAnalyticsKeyNotConfigured() { diff --git a/dspace-server-webapp/src/test/java/org/dspace/google/GoogleAsyncEventListenerIT.java b/dspace-server-webapp/src/test/java/org/dspace/google/GoogleAsyncEventListenerIT.java index 866d0fafedb3..e43e9fd82035 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/google/GoogleAsyncEventListenerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/google/GoogleAsyncEventListenerIT.java @@ -29,12 +29,16 @@ import java.util.List; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.builder.BitstreamBuilder; +import org.dspace.builder.BundleBuilder; import org.dspace.builder.CollectionBuilder; import org.dspace.builder.CommunityBuilder; import org.dspace.builder.ItemBuilder; import org.dspace.content.Bitstream; +import org.dspace.content.Bundle; import org.dspace.content.Collection; import org.dspace.content.Item; +import org.dspace.core.Constants; import org.dspace.google.client.GoogleAnalyticsClient; import org.dspace.services.ConfigurationService; import org.junit.After; @@ -61,6 +65,8 @@ public class GoogleAsyncEventListenerIT extends AbstractControllerIntegrationTes private Bitstream bitstream; + private Item item; + private List originalGoogleAnalyticsClients; private GoogleAnalyticsClient firstGaClientMock = mock(GoogleAnalyticsClient.class); @@ -80,7 +86,7 @@ public void setup() throws Exception { .withName("Test collection") .build(); - Item item = ItemBuilder.createItem(context, collection) + item = ItemBuilder.createItem(context, collection) .withTitle("Test item") .build(); @@ -238,6 +244,88 @@ public void testOnBitstreamContentDownloadWithTooManyEvents() throws Exception { } + @Test + public void testOnBitstreamContentDownloadExcludeNonContentBitstreams() throws Exception { + configurationService.setProperty("google-analytics.exclude-non-content-bitstreams", true); + + context.turnOffAuthorisationSystem(); + Bundle licenseBundle = BundleBuilder.createBundle(context, item) + .withName(Constants.LICENSE_BUNDLE_NAME).build(); + Bitstream license = BitstreamBuilder.createBitstream(context, licenseBundle, + toInputStream("License", defaultCharset())).build(); + Bundle thumbnailBundle = BundleBuilder.createBundle(context, item).withName("THUMBNAIL").build(); + Bitstream thumbnail = BitstreamBuilder.createBitstream(context, thumbnailBundle, + toInputStream("Thumbnail", defaultCharset())).build(); + context.restoreAuthSystemState(); + + assertThat(getStoredEventsAsList(), empty()); + + String bitstreamUrl = "/api/core/bitstreams/" + bitstream.getID() + "/content"; + + downloadBitstreamContent("Postman", "123456", "REF"); + downloadContent("Chrome", "ABCDEFG", "REF-1", license); + downloadContent("Chrome", "987654", "REF-2", thumbnail); + + assertThat(getStoredEventsAsList(), hasSize(1)); + + List storedEvents = getStoredEventsAsList(); + + assertThat(storedEvents, contains( + event("123456", "127.0.0.1", "Postman", "REF", bitstreamUrl, "Test item"))); + + googleAsyncEventListener.sendCollectedEvents(); + + assertThat(getStoredEventsAsList(), empty()); + + verify(firstGaClientMock).isAnalyticsKeySupported(ANALYTICS_KEY); + verify(secondGaClientMock).isAnalyticsKeySupported(ANALYTICS_KEY); + verify(secondGaClientMock).sendEvents(ANALYTICS_KEY, storedEvents); + verifyNoMoreInteractions(firstGaClientMock, secondGaClientMock); + } + + @Test + public void testOnBitstreamContentDownloadIncludeNonContentBitstreams() throws Exception { + configurationService.setProperty("google-analytics.exclude-non-content-bitstreams", false); + + context.turnOffAuthorisationSystem(); + Bundle licenseBundle = BundleBuilder.createBundle(context, item) + .withName(Constants.LICENSE_BUNDLE_NAME).build(); + Bitstream license = BitstreamBuilder.createBitstream(context, licenseBundle, + toInputStream("License", defaultCharset())).build(); + Bundle thumbnailBundle = BundleBuilder.createBundle(context, item).withName("THUMBNAIL").build(); + Bitstream thumbnail = BitstreamBuilder.createBitstream(context, thumbnailBundle, + toInputStream("Thumbnail", defaultCharset())).build(); + context.restoreAuthSystemState(); + + assertThat(getStoredEventsAsList(), empty()); + + String bitstreamUrl = "/api/core/bitstreams/" + bitstream.getID() + "/content"; + String licenseUrl = "/api/core/bitstreams/" + license.getID() + "/content"; + String thumbnailUrl = "/api/core/bitstreams/" + thumbnail.getID() + "/content"; + + downloadBitstreamContent("Postman", "123456", "REF"); + downloadContent("Chrome", "ABCDEFG", "REF-1", license); + downloadContent("Chrome", "987654", "REF-2", thumbnail); + + assertThat(getStoredEventsAsList(), hasSize(3)); + + List storedEvents = getStoredEventsAsList(); + + assertThat(storedEvents, contains( + event("123456", "127.0.0.1", "Postman", "REF", bitstreamUrl, "Test item"), + event("ABCDEFG", "127.0.0.1", "Chrome", "REF-1", licenseUrl, "Test item"), + event("987654", "127.0.0.1", "Chrome", "REF-2", thumbnailUrl, "Test item"))); + + googleAsyncEventListener.sendCollectedEvents(); + + assertThat(getStoredEventsAsList(), empty()); + + verify(firstGaClientMock).isAnalyticsKeySupported(ANALYTICS_KEY); + verify(secondGaClientMock).isAnalyticsKeySupported(ANALYTICS_KEY); + verify(secondGaClientMock).sendEvents(ANALYTICS_KEY, storedEvents); + verifyNoMoreInteractions(firstGaClientMock, secondGaClientMock); + } + @SuppressWarnings("unchecked") private List getStoredEventsAsList() { List events = new ArrayList<>(); @@ -248,13 +336,18 @@ private List getStoredEventsAsList() { return events; } - private void downloadBitstreamContent(String userAgent, String correlationId, String referrer) throws Exception { + private void downloadContent(String userAgent, String correlationId, String referrer, Bitstream bit) + throws Exception { getClient(getAuthToken(admin.getEmail(), password)) - .perform(get("/api/core/bitstreams/" + bitstream.getID() + "/content") - .header("USER-AGENT", userAgent) - .header("X-CORRELATION-ID", correlationId) - .header("X-REFERRER", referrer)) + .perform(get("/api/core/bitstreams/" + bit.getID() + "/content") + .header("USER-AGENT", userAgent) + .header("X-CORRELATION-ID", correlationId) + .header("X-REFERRER", referrer)) .andExpect(status().isOk()); } + private void downloadBitstreamContent(String userAgent, String correlationId, String referrer) throws Exception { + downloadContent(userAgent, correlationId, referrer, bitstream); + } + } diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 8e532310c11b..89c8da92558c 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -1535,7 +1535,12 @@ log.report.dir = ${dspace.dir}/log # Defines a Measurement Protocol API Secret to be used to track interactions which occur outside of the user's browser. # For example , this is required to track downloads of bitstreams. This setting is only used by Google Analytics 4. # For more details see https://developers.google.com/analytics/devguides/collection/protocol/ga4 -# google.analytics.api-secret = +# google.analytics.api-secret = + +# Ensures only views of bitstreams in the 'ORIGINAL' bundle result in a GA4 event. +# Setting this to false may cause inflated bitstream view numbers, since requesting +# bitstreams in the 'THUMBNAIL' and 'LICENSE' bundles, will also result in GA4 events. +google-analytics.exclude-non-content-bitstreams=true #################################################################### #---------------------------------------------------------------# From 1ca4f59bb273bd1a9f861dc5486c11ba775ed20c Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Thu, 6 Jul 2023 15:44:02 -0500 Subject: [PATCH 339/510] Enable Pull Request Opened action to assign PRs to their creator --- .../pull_request_opened.yml | 26 ------------------- .github/workflows/pull_request_opened.yml | 24 +++++++++++++++++ 2 files changed, 24 insertions(+), 26 deletions(-) delete mode 100644 .github/disabled-workflows/pull_request_opened.yml create mode 100644 .github/workflows/pull_request_opened.yml diff --git a/.github/disabled-workflows/pull_request_opened.yml b/.github/disabled-workflows/pull_request_opened.yml deleted file mode 100644 index 0dc718c0b9a3..000000000000 --- a/.github/disabled-workflows/pull_request_opened.yml +++ /dev/null @@ -1,26 +0,0 @@ -# This workflow runs whenever a new pull request is created -# TEMPORARILY DISABLED. Unfortunately this doesn't work for PRs created from forked repositories (which is how we tend to create PRs). -# There is no known workaround yet. See https://github.community/t/how-to-use-github-token-for-prs-from-forks/16818 -name: Pull Request opened - -# Only run for newly opened PRs against the "main" branch -on: - pull_request: - types: [opened] - branches: - - main - -jobs: - automation: - runs-on: ubuntu-latest - steps: - # Assign the PR to whomever created it. This is useful for visualizing assignments on project boards - # See https://github.com/marketplace/actions/pull-request-assigner - - name: Assign PR to creator - uses: thomaseizinger/assign-pr-creator-action@v1.0.0 - # Note, this authentication token is created automatically - # See: https://docs.github.com/en/actions/configuring-and-managing-workflows/authenticating-with-the-github_token - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - # Ignore errors. It is possible the PR was created by someone who cannot be assigned - continue-on-error: true diff --git a/.github/workflows/pull_request_opened.yml b/.github/workflows/pull_request_opened.yml new file mode 100644 index 000000000000..9b61af72d187 --- /dev/null +++ b/.github/workflows/pull_request_opened.yml @@ -0,0 +1,24 @@ +# This workflow runs whenever a new pull request is created +name: Pull Request opened + +# Only run for newly opened PRs against the "main" or maintenance branches +# We allow this to run for `pull_request_target` so that github secrets are available +# (This is required to assign a PR back to the creator when the PR comes from a forked repo) +on: + pull_request_target: + types: [ opened ] + branches: + - main + - 'dspace-**' + +permissions: + pull-requests: write + +jobs: + automation: + runs-on: ubuntu-latest + steps: + # Assign the PR to whomever created it. This is useful for visualizing assignments on project boards + # See https://github.com/toshimaru/auto-author-assign + - name: Assign PR to creator + uses: toshimaru/auto-author-assign@v1.6.2 From 3ac66031baffbab3347694bcb35074153a397e2b Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Thu, 6 Jul 2023 15:44:36 -0500 Subject: [PATCH 340/510] Ensure codescan and label_merge_conflicts run on maintenance branches --- .github/workflows/codescan.yml | 10 +++++++--- .github/workflows/label_merge_conflicts.yml | 7 ++++--- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/.github/workflows/codescan.yml b/.github/workflows/codescan.yml index 7580b4ba3dc3..9e6dcc0b23af 100644 --- a/.github/workflows/codescan.yml +++ b/.github/workflows/codescan.yml @@ -5,12 +5,16 @@ # because CodeQL requires a fresh build with all tests *disabled*. name: "Code Scanning" -# Run this code scan for all pushes / PRs to main branch. Also run once a week. +# Run this code scan for all pushes / PRs to main or maintenance branches. Also run once a week. on: push: - branches: [ main ] + branches: + - main + - 'dspace-**' pull_request: - branches: [ main ] + branches: + - main + - 'dspace-**' # Don't run if PR is only updating static documentation paths-ignore: - '**/*.md' diff --git a/.github/workflows/label_merge_conflicts.yml b/.github/workflows/label_merge_conflicts.yml index cc0c7099f40e..0c3b1a0f2a80 100644 --- a/.github/workflows/label_merge_conflicts.yml +++ b/.github/workflows/label_merge_conflicts.yml @@ -1,11 +1,12 @@ # This workflow checks open PRs for merge conflicts and labels them when conflicts are found name: Check for merge conflicts -# Run whenever the "main" branch is updated -# NOTE: This means merge conflicts are only checked for when a PR is merged to main. +# Run this for all pushes (i.e. merges) to 'main' or maintenance branches on: push: - branches: [ main ] + branches: + - main + - 'dspace-**' # So that the `conflict_label_name` is removed if conflicts are resolved, # we allow this to run for `pull_request_target` so that github secrets are available. pull_request_target: From dea45355818176017802a0b7337b07ae435c5437 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Thu, 6 Jul 2023 16:32:16 -0500 Subject: [PATCH 341/510] Split docker image builds into separate jobs to allow them to run in parallel. --- .github/workflows/docker.yml | 267 ++++++++++++++++++++++++++++------- 1 file changed, 219 insertions(+), 48 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 971954a5e1ee..9ec6b8573500 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -15,30 +15,36 @@ on: permissions: contents: read # to fetch code (actions/checkout) +# Define shared environment variables for all jobs below +env: + # Define tags to use for Docker images based on Git tags/branches (for docker/metadata-action) + # For a new commit on default branch (main), use the literal tag 'dspace-7_x' on Docker image. + # For a new commit on other branches, use the branch name as the tag for Docker image. + # For a new tag, copy that tag name as the tag for Docker image. + IMAGE_TAGS: | + type=raw,value=dspace-7_x,enable=${{ endsWith(github.ref, github.event.repository.default_branch) }} + type=ref,event=branch,enable=${{ !endsWith(github.ref, github.event.repository.default_branch) }} + type=ref,event=tag + # Define default tag "flavor" for docker/metadata-action per + # https://github.com/docker/metadata-action#flavor-input + # We turn off 'latest' tag by default. + TAGS_FLAVOR: | + latest=false + # Architectures / Platforms for which we will build Docker images + # If this is a PR, we ONLY build for AMD64. For PRs we only do a sanity check test to ensure Docker builds work. + # If this is NOT a PR (e.g. a tag or merge commit), also build for ARM64. NOTE: The ARM64 build takes MUCH + # longer (around 45mins or so) which is why we only run it when pushing a new Docker image. + PLATFORMS: linux/amd64${{ github.event_name != 'pull_request' && ', linux/arm64' || '' }} + jobs: - docker: + #################################################### + # Build/Push the 'dspace/dspace-dependencies' image. + # This image is used by all other jobs. + #################################################### + dspace-dependencies: # Ensure this job never runs on forked repos. It's only executed for 'dspace/dspace' if: github.repository == 'dspace/dspace' runs-on: ubuntu-latest - env: - # Define tags to use for Docker images based on Git tags/branches (for docker/metadata-action) - # For a new commit on default branch (main), use the literal tag 'dspace-7_x' on Docker image. - # For a new commit on other branches, use the branch name as the tag for Docker image. - # For a new tag, copy that tag name as the tag for Docker image. - IMAGE_TAGS: | - type=raw,value=dspace-7_x,enable=${{ endsWith(github.ref, github.event.repository.default_branch) }} - type=ref,event=branch,enable=${{ !endsWith(github.ref, github.event.repository.default_branch) }} - type=ref,event=tag - # Define default tag "flavor" for docker/metadata-action per - # https://github.com/docker/metadata-action#flavor-input - # We turn off 'latest' tag by default. - TAGS_FLAVOR: | - latest=false - # Architectures / Platforms for which we will build Docker images - # If this is a PR, we ONLY build for AMD64. For PRs we only do a sanity check test to ensure Docker builds work. - # If this is NOT a PR (e.g. a tag or merge commit), also build for ARM64. NOTE: The ARM64 build takes MUCH - # longer (around 45mins or so) which is why we only run it when pushing a new Docker image. - PLATFORMS: linux/amd64${{ github.event_name != 'pull_request' && ', linux/arm64' || '' }} steps: # https://github.com/actions/checkout @@ -62,9 +68,6 @@ jobs: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_ACCESS_TOKEN }} - #################################################### - # Build/Push the 'dspace/dspace-dependencies' image - #################################################### # https://github.com/docker/metadata-action # Get Metadata for docker_build_deps step below - name: Sync metadata (tags, labels) from GitHub to Docker for 'dspace-dependencies' image @@ -78,7 +81,7 @@ jobs: # https://github.com/docker/build-push-action - name: Build and push 'dspace-dependencies' image id: docker_build_deps - uses: docker/build-push-action@v3 + uses: docker/build-push-action@v4 with: context: . file: ./Dockerfile.dependencies @@ -90,9 +93,38 @@ jobs: tags: ${{ steps.meta_build_deps.outputs.tags }} labels: ${{ steps.meta_build_deps.outputs.labels }} - ####################################### - # Build/Push the 'dspace/dspace' image - ####################################### + ####################################### + # Build/Push the 'dspace/dspace' image + ####################################### + dspace: + # Ensure this job never runs on forked repos. It's only executed for 'dspace/dspace' + if: github.repository == 'dspace/dspace' + # Must run after 'dspace-dependencies' job above + needs: dspace-dependencies + runs-on: ubuntu-latest + + steps: + # https://github.com/actions/checkout + - name: Checkout codebase + uses: actions/checkout@v3 + + # https://github.com/docker/setup-buildx-action + - name: Setup Docker Buildx + uses: docker/setup-buildx-action@v2 + + # https://github.com/docker/setup-qemu-action + - name: Set up QEMU emulation to build for multiple architectures + uses: docker/setup-qemu-action@v2 + + # https://github.com/docker/login-action + - name: Login to DockerHub + # Only login if not a PR, as PRs only trigger a Docker build and not a push + if: github.event_name != 'pull_request' + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_ACCESS_TOKEN }} + # Get Metadata for docker_build step below - name: Sync metadata (tags, labels) from GitHub to Docker for 'dspace' image id: meta_build @@ -104,7 +136,7 @@ jobs: - name: Build and push 'dspace' image id: docker_build - uses: docker/build-push-action@v3 + uses: docker/build-push-action@v4 with: context: . file: ./Dockerfile @@ -116,9 +148,38 @@ jobs: tags: ${{ steps.meta_build.outputs.tags }} labels: ${{ steps.meta_build.outputs.labels }} - ##################################################### - # Build/Push the 'dspace/dspace' image ('-test' tag) - ##################################################### + ############################################################# + # Build/Push the 'dspace/dspace' image ('-test' tag) + ############################################################# + dspace-test: + # Ensure this job never runs on forked repos. It's only executed for 'dspace/dspace' + if: github.repository == 'dspace/dspace' + # Must run after 'dspace-dependencies' job above + needs: dspace-dependencies + runs-on: ubuntu-latest + + steps: + # https://github.com/actions/checkout + - name: Checkout codebase + uses: actions/checkout@v3 + + # https://github.com/docker/setup-buildx-action + - name: Setup Docker Buildx + uses: docker/setup-buildx-action@v2 + + # https://github.com/docker/setup-qemu-action + - name: Set up QEMU emulation to build for multiple architectures + uses: docker/setup-qemu-action@v2 + + # https://github.com/docker/login-action + - name: Login to DockerHub + # Only login if not a PR, as PRs only trigger a Docker build and not a push + if: github.event_name != 'pull_request' + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_ACCESS_TOKEN }} + # Get Metadata for docker_build_test step below - name: Sync metadata (tags, labels) from GitHub to Docker for 'dspace-test' image id: meta_build_test @@ -133,7 +194,7 @@ jobs: - name: Build and push 'dspace-test' image id: docker_build_test - uses: docker/build-push-action@v3 + uses: docker/build-push-action@v4 with: context: . file: ./Dockerfile.test @@ -145,9 +206,38 @@ jobs: tags: ${{ steps.meta_build_test.outputs.tags }} labels: ${{ steps.meta_build_test.outputs.labels }} - ########################################### - # Build/Push the 'dspace/dspace-cli' image - ########################################### + ########################################### + # Build/Push the 'dspace/dspace-cli' image + ########################################### + dspace-cli: + # Ensure this job never runs on forked repos. It's only executed for 'dspace/dspace' + if: github.repository == 'dspace/dspace' + # Must run after 'dspace-dependencies' job above + needs: dspace-dependencies + runs-on: ubuntu-latest + + steps: + # https://github.com/actions/checkout + - name: Checkout codebase + uses: actions/checkout@v3 + + # https://github.com/docker/setup-buildx-action + - name: Setup Docker Buildx + uses: docker/setup-buildx-action@v2 + + # https://github.com/docker/setup-qemu-action + - name: Set up QEMU emulation to build for multiple architectures + uses: docker/setup-qemu-action@v2 + + # https://github.com/docker/login-action + - name: Login to DockerHub + # Only login if not a PR, as PRs only trigger a Docker build and not a push + if: github.event_name != 'pull_request' + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_ACCESS_TOKEN }} + # Get Metadata for docker_build_test step below - name: Sync metadata (tags, labels) from GitHub to Docker for 'dspace-cli' image id: meta_build_cli @@ -159,7 +249,7 @@ jobs: - name: Build and push 'dspace-cli' image id: docker_build_cli - uses: docker/build-push-action@v3 + uses: docker/build-push-action@v4 with: context: . file: ./Dockerfile.cli @@ -171,9 +261,36 @@ jobs: tags: ${{ steps.meta_build_cli.outputs.tags }} labels: ${{ steps.meta_build_cli.outputs.labels }} - ########################################### - # Build/Push the 'dspace/dspace-solr' image - ########################################### + ########################################### + # Build/Push the 'dspace/dspace-solr' image + ########################################### + dspace-solr: + # Ensure this job never runs on forked repos. It's only executed for 'dspace/dspace' + if: github.repository == 'dspace/dspace' + runs-on: ubuntu-latest + + steps: + # https://github.com/actions/checkout + - name: Checkout codebase + uses: actions/checkout@v3 + + # https://github.com/docker/setup-buildx-action + - name: Setup Docker Buildx + uses: docker/setup-buildx-action@v2 + + # https://github.com/docker/setup-qemu-action + - name: Set up QEMU emulation to build for multiple architectures + uses: docker/setup-qemu-action@v2 + + # https://github.com/docker/login-action + - name: Login to DockerHub + # Only login if not a PR, as PRs only trigger a Docker build and not a push + if: github.event_name != 'pull_request' + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_ACCESS_TOKEN }} + # Get Metadata for docker_build_solr step below - name: Sync metadata (tags, labels) from GitHub to Docker for 'dspace-solr' image id: meta_build_solr @@ -185,7 +302,7 @@ jobs: - name: Build and push 'dspace-solr' image id: docker_build_solr - uses: docker/build-push-action@v3 + uses: docker/build-push-action@v4 with: context: . file: ./dspace/src/main/docker/dspace-solr/Dockerfile @@ -197,9 +314,36 @@ jobs: tags: ${{ steps.meta_build_solr.outputs.tags }} labels: ${{ steps.meta_build_solr.outputs.labels }} - ########################################################### - # Build/Push the 'dspace/dspace-postgres-pgcrypto' image - ########################################################### + ########################################################### + # Build/Push the 'dspace/dspace-postgres-pgcrypto' image + ########################################################### + dspace-postgres-pgcrypto: + # Ensure this job never runs on forked repos. It's only executed for 'dspace/dspace' + if: github.repository == 'dspace/dspace' + runs-on: ubuntu-latest + + steps: + # https://github.com/actions/checkout + - name: Checkout codebase + uses: actions/checkout@v3 + + # https://github.com/docker/setup-buildx-action + - name: Setup Docker Buildx + uses: docker/setup-buildx-action@v2 + + # https://github.com/docker/setup-qemu-action + - name: Set up QEMU emulation to build for multiple architectures + uses: docker/setup-qemu-action@v2 + + # https://github.com/docker/login-action + - name: Login to DockerHub + # Only login if not a PR, as PRs only trigger a Docker build and not a push + if: github.event_name != 'pull_request' + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_ACCESS_TOKEN }} + # Get Metadata for docker_build_postgres step below - name: Sync metadata (tags, labels) from GitHub to Docker for 'dspace-postgres-pgcrypto' image id: meta_build_postgres @@ -211,7 +355,7 @@ jobs: - name: Build and push 'dspace-postgres-pgcrypto' image id: docker_build_postgres - uses: docker/build-push-action@v3 + uses: docker/build-push-action@v4 with: # Must build out of subdirectory to have access to install script for pgcrypto context: ./dspace/src/main/docker/dspace-postgres-pgcrypto/ @@ -224,9 +368,36 @@ jobs: tags: ${{ steps.meta_build_postgres.outputs.tags }} labels: ${{ steps.meta_build_postgres.outputs.labels }} - ########################################################### - # Build/Push the 'dspace/dspace-postgres-pgcrypto' image ('-loadsql' tag) - ########################################################### + ######################################################################## + # Build/Push the 'dspace/dspace-postgres-pgcrypto' image (-loadsql tag) + ######################################################################## + dspace-postgres-pgcrypto-loadsql: + # Ensure this job never runs on forked repos. It's only executed for 'dspace/dspace' + if: github.repository == 'dspace/dspace' + runs-on: ubuntu-latest + + steps: + # https://github.com/actions/checkout + - name: Checkout codebase + uses: actions/checkout@v3 + + # https://github.com/docker/setup-buildx-action + - name: Setup Docker Buildx + uses: docker/setup-buildx-action@v2 + + # https://github.com/docker/setup-qemu-action + - name: Set up QEMU emulation to build for multiple architectures + uses: docker/setup-qemu-action@v2 + + # https://github.com/docker/login-action + - name: Login to DockerHub + # Only login if not a PR, as PRs only trigger a Docker build and not a push + if: github.event_name != 'pull_request' + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_ACCESS_TOKEN }} + # Get Metadata for docker_build_postgres_loadsql step below - name: Sync metadata (tags, labels) from GitHub to Docker for 'dspace-postgres-pgcrypto-loadsql' image id: meta_build_postgres_loadsql @@ -241,7 +412,7 @@ jobs: - name: Build and push 'dspace-postgres-pgcrypto-loadsql' image id: docker_build_postgres_loadsql - uses: docker/build-push-action@v3 + uses: docker/build-push-action@v4 with: # Must build out of subdirectory to have access to install script for pgcrypto context: ./dspace/src/main/docker/dspace-postgres-pgcrypto-curl/ From d1e1900b331f5ac1507048b0ebb7b72c7f5047e7 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 7 Jul 2023 11:47:09 -0500 Subject: [PATCH 342/510] Ensure 'main' code is tagged as 'latest' in DockerHub --- .github/workflows/docker.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 9ec6b8573500..f1ae184fd5c0 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -18,16 +18,16 @@ permissions: # Define shared environment variables for all jobs below env: # Define tags to use for Docker images based on Git tags/branches (for docker/metadata-action) - # For a new commit on default branch (main), use the literal tag 'dspace-7_x' on Docker image. + # For a new commit on default branch (main), use the literal tag 'latest' on Docker image. # For a new commit on other branches, use the branch name as the tag for Docker image. # For a new tag, copy that tag name as the tag for Docker image. IMAGE_TAGS: | - type=raw,value=dspace-7_x,enable=${{ endsWith(github.ref, github.event.repository.default_branch) }} + type=raw,value=latest,enable=${{ endsWith(github.ref, github.event.repository.default_branch) }} type=ref,event=branch,enable=${{ !endsWith(github.ref, github.event.repository.default_branch) }} type=ref,event=tag # Define default tag "flavor" for docker/metadata-action per # https://github.com/docker/metadata-action#flavor-input - # We turn off 'latest' tag by default. + # We manage the 'latest' tag ourselves to the 'main' branch (see settings above) TAGS_FLAVOR: | latest=false # Architectures / Platforms for which we will build Docker images From 4aea2a99a91d49c1b7dab36149b179a9156db892 Mon Sep 17 00:00:00 2001 From: Toni Prieto Date: Mon, 10 Jul 2023 13:43:27 +0200 Subject: [PATCH 343/510] Add flag Pattern.UNICODE_CHARACTER_CLASS to pattern compilation to recognize unicode characters --- .../org/dspace/discovery/indexobject/ItemIndexFactoryImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/discovery/indexobject/ItemIndexFactoryImpl.java b/dspace-api/src/main/java/org/dspace/discovery/indexobject/ItemIndexFactoryImpl.java index 005f9b42472e..e1136a3f5937 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/indexobject/ItemIndexFactoryImpl.java +++ b/dspace-api/src/main/java/org/dspace/discovery/indexobject/ItemIndexFactoryImpl.java @@ -845,7 +845,7 @@ private void indexIfFilterTypeFacet(SolrInputDocument doc, DiscoverySearchFilter private void saveFacetPrefixParts(SolrInputDocument doc, DiscoverySearchFilter searchFilter, String value, String separator, String authority, String preferedLabel) { value = StringUtils.normalizeSpace(value); - Pattern pattern = Pattern.compile("\\b\\w+\\b", Pattern.CASE_INSENSITIVE); + Pattern pattern = Pattern.compile("\\b\\w+\\b", Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS); Matcher matcher = pattern.matcher(value); while (matcher.find()) { int index = matcher.start(); From 4b0601e495795782db8877190de5b8ae0cbe07ee Mon Sep 17 00:00:00 2001 From: Sascha Szott Date: Tue, 11 Jul 2023 16:52:20 +0200 Subject: [PATCH 344/510] remove obsolete code fragments --- .../dspace/discovery/indexobject/ItemIndexFactoryImpl.java | 7 ------- 1 file changed, 7 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/discovery/indexobject/ItemIndexFactoryImpl.java b/dspace-api/src/main/java/org/dspace/discovery/indexobject/ItemIndexFactoryImpl.java index 005f9b42472e..39947146ddd8 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/indexobject/ItemIndexFactoryImpl.java +++ b/dspace-api/src/main/java/org/dspace/discovery/indexobject/ItemIndexFactoryImpl.java @@ -172,13 +172,6 @@ public SolrInputDocument buildDocument(Context context, IndexableItem indexableI addNamedResourceTypeIndex(doc, acvalue); } - // write the index and close the inputstreamreaders - try { - log.info("Wrote Item: " + item.getID() + " to Index"); - } catch (RuntimeException e) { - log.error("Error while writing item to discovery index: " + item.getID() + " message:" - + e.getMessage(), e); - } return doc; } From 4cf019f6503b6f9db73ee40b86d71ac90b35dc06 Mon Sep 17 00:00:00 2001 From: Agustina Martinez Date: Fri, 14 Jul 2023 11:06:35 +0100 Subject: [PATCH 345/510] Update FullTextContentStreams.java Fix NPE if bitstream is null --- .../dspace/discovery/FullTextContentStreams.java | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/discovery/FullTextContentStreams.java b/dspace-api/src/main/java/org/dspace/discovery/FullTextContentStreams.java index ee220e5a4fdf..6d0c57c62855 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/FullTextContentStreams.java +++ b/dspace-api/src/main/java/org/dspace/discovery/FullTextContentStreams.java @@ -77,13 +77,19 @@ private void buildFullTextList(Item parentItem) { // a-ha! grab the text out of the bitstreams List bitstreams = myBundle.getBitstreams(); + log.debug("Processing full-text bitstreams. Item handle: " + sourceInfo); + for (Bitstream fulltextBitstream : emptyIfNull(bitstreams)) { fullTextStreams.add(new FullTextBitstream(sourceInfo, fulltextBitstream)); - log.debug("Added BitStream: " - + fulltextBitstream.getStoreNumber() + " " - + fulltextBitstream.getSequenceID() + " " - + fulltextBitstream.getName()); + if (fulltextBitstream != null) { + log.debug("Added BitStream: " + + fulltextBitstream.getStoreNumber() + " " + + fulltextBitstream.getSequenceID() + " " + + fulltextBitstream.getName()); + } else { + log.error("Found a NULL bitstream when processing full-text files: item handle:" + sourceInfo); + } } } } From e645d0fa25195096ca9d60ecfbd2b141967556a2 Mon Sep 17 00:00:00 2001 From: Agustina Martinez Date: Fri, 14 Jul 2023 16:17:22 +0100 Subject: [PATCH 346/510] Update FullTextContentStreams.java Add additional NPE checks --- .../java/org/dspace/discovery/FullTextContentStreams.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/discovery/FullTextContentStreams.java b/dspace-api/src/main/java/org/dspace/discovery/FullTextContentStreams.java index 6d0c57c62855..21468def6866 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/FullTextContentStreams.java +++ b/dspace-api/src/main/java/org/dspace/discovery/FullTextContentStreams.java @@ -76,7 +76,6 @@ private void buildFullTextList(Item parentItem) { if (StringUtils.equals(FULLTEXT_BUNDLE, myBundle.getName())) { // a-ha! grab the text out of the bitstreams List bitstreams = myBundle.getBitstreams(); - log.debug("Processing full-text bitstreams. Item handle: " + sourceInfo); for (Bitstream fulltextBitstream : emptyIfNull(bitstreams)) { @@ -164,16 +163,16 @@ public FullTextBitstream(final String parentHandle, final Bitstream file) { } public String getContentType(final Context context) throws SQLException { - BitstreamFormat format = bitstream.getFormat(context); + BitstreamFormat format = bitstream != null ? bitstream.getFormat(context) : null; return format == null ? null : StringUtils.trimToEmpty(format.getMIMEType()); } public String getFileName() { - return StringUtils.trimToEmpty(bitstream.getName()); + return bitstream != null ? StringUtils.trimToEmpty(bitstream.getName()) : null; } public long getSize() { - return bitstream.getSizeBytes(); + return bitstream != null ? bitstream.getSizeBytes() : -1; } public InputStream getInputStream() throws SQLException, IOException, AuthorizeException { From acf376db346d2f3b979b91c4e007cf39f0ab8d18 Mon Sep 17 00:00:00 2001 From: Agustina Martinez Date: Sun, 16 Jul 2023 20:42:03 +0100 Subject: [PATCH 347/510] Update ItemUtils.java Prevent npe if bitstream is null --- dspace-oai/src/main/java/org/dspace/xoai/util/ItemUtils.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/dspace-oai/src/main/java/org/dspace/xoai/util/ItemUtils.java b/dspace-oai/src/main/java/org/dspace/xoai/util/ItemUtils.java index 955c3a78c392..35bef8c8d77f 100644 --- a/dspace-oai/src/main/java/org/dspace/xoai/util/ItemUtils.java +++ b/dspace-oai/src/main/java/org/dspace/xoai/util/ItemUtils.java @@ -103,6 +103,11 @@ private static Element createBundlesElement(Context context, Item item) throws S bundle.getElement().add(bitstreams); List bits = b.getBitstreams(); for (Bitstream bit : bits) { + // Check if bitstream is null and log the error + if (bit == null) { + log.error("Null bitstream found, check item uuid: " + item.getID()); + break; + } Element bitstream = create("bitstream"); bitstreams.getElement().add(bitstream); String url = ""; From aa35a47add5565a9302d276da2ceb22b8dbc320f Mon Sep 17 00:00:00 2001 From: corrado lombardi Date: Wed, 19 Jul 2023 12:58:36 +0200 Subject: [PATCH 348/510] [DURACOM-179] replaced 'null' value with exception actual value in sendErrorResponse method calls having 'null' --- .../exception/DSpaceApiExceptionControllerAdvice.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/exception/DSpaceApiExceptionControllerAdvice.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/exception/DSpaceApiExceptionControllerAdvice.java index 4ad1e479348f..a65ea13bc2c0 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/exception/DSpaceApiExceptionControllerAdvice.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/exception/DSpaceApiExceptionControllerAdvice.java @@ -132,7 +132,7 @@ protected void handleUnprocessableEntityException(HttpServletRequest request, Ht Exception ex) throws IOException { //422 is not defined in HttpServletResponse. Its meaning is "Unprocessable Entity". //Using the value from HttpStatus. - sendErrorResponse(request, response, null, + sendErrorResponse(request, response, ex, "Unprocessable or invalid entity", HttpStatus.UNPROCESSABLE_ENTITY.value()); } @@ -140,7 +140,7 @@ protected void handleUnprocessableEntityException(HttpServletRequest request, Ht @ExceptionHandler( {InvalidSearchRequestException.class}) protected void handleInvalidSearchRequestException(HttpServletRequest request, HttpServletResponse response, Exception ex) throws IOException { - sendErrorResponse(request, response, null, + sendErrorResponse(request, response, ex, "Invalid search request", HttpStatus.UNPROCESSABLE_ENTITY.value()); } @@ -180,7 +180,7 @@ protected void handleCustomUnprocessableEntityException(HttpServletRequest reque TranslatableException ex) throws IOException { Context context = ContextUtil.obtainContext(request); sendErrorResponse( - request, response, null, ex.getLocalizedMessage(context), HttpStatus.UNPROCESSABLE_ENTITY.value() + request, response, (Exception) ex, ex.getLocalizedMessage(context), HttpStatus.UNPROCESSABLE_ENTITY.value() ); } @@ -188,7 +188,7 @@ protected void handleCustomUnprocessableEntityException(HttpServletRequest reque protected void ParameterConversionException(HttpServletRequest request, HttpServletResponse response, Exception ex) throws IOException { // we want the 400 status for missing parameters, see https://jira.lyrasis.org/browse/DS-4428 - sendErrorResponse(request, response, null, + sendErrorResponse(request, response, ex, "A required parameter is invalid", HttpStatus.BAD_REQUEST.value()); } @@ -197,7 +197,7 @@ protected void ParameterConversionException(HttpServletRequest request, HttpServ protected void MissingParameterException(HttpServletRequest request, HttpServletResponse response, Exception ex) throws IOException { // we want the 400 status for missing parameters, see https://jira.lyrasis.org/browse/DS-4428 - sendErrorResponse(request, response, null, + sendErrorResponse(request, response, ex, "A required parameter is missing", HttpStatus.BAD_REQUEST.value()); } From f9681bb76bf405b5b9ba5d8ca90b7d4ff6432c4c Mon Sep 17 00:00:00 2001 From: damian Date: Fri, 21 Jul 2023 12:02:12 +0200 Subject: [PATCH 349/510] Reading localized license file. --- .../org/dspace/core/LicenseServiceImpl.java | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/core/LicenseServiceImpl.java b/dspace-api/src/main/java/org/dspace/core/LicenseServiceImpl.java index 8324105a3085..f99b3c31e51c 100644 --- a/dspace-api/src/main/java/org/dspace/core/LicenseServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/core/LicenseServiceImpl.java @@ -17,9 +17,12 @@ import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.PrintWriter; +import javax.servlet.http.HttpServletRequest; import org.dspace.core.service.LicenseService; import org.dspace.services.factory.DSpaceServicesFactory; +import org.dspace.services.model.Request; +import org.dspace.web.ContextUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -101,13 +104,14 @@ public String getLicenseText(String licenseFile) { /** * Get the site-wide default license that submitters need to grant * + * Localized license requires: default_{{locale}}.license file. + * Locale also must be listed in webui.supported.locales setting. + * * @return the default license */ @Override public String getDefaultSubmissionLicense() { - if (null == license) { - init(); - } + init(); return license; } @@ -115,9 +119,8 @@ public String getDefaultSubmissionLicense() { * Load in the default license. */ protected void init() { - File licenseFile = new File( - DSpaceServicesFactory.getInstance().getConfigurationService().getProperty("dspace.dir") - + File.separator + "config" + File.separator + "default.license"); + Context context = obtainContext(); + File licenseFile = new File(I18nUtil.getDefaultLicense(context)); FileInputStream fir = null; InputStreamReader ir = null; @@ -169,4 +172,14 @@ protected void init() { } } } + + private Context obtainContext() { + Request currentRequest = DSpaceServicesFactory.getInstance().getRequestService().getCurrentRequest(); + if (currentRequest != null) { + HttpServletRequest request = currentRequest.getHttpServletRequest(); + return ContextUtil.obtainContext(request); + } else { + return new Context(); + } + } } From 0df490d4af2ef2fbb4b540b9a399147301fee65a Mon Sep 17 00:00:00 2001 From: damian Date: Fri, 21 Jul 2023 12:02:12 +0200 Subject: [PATCH 350/510] Reading localized license file. --- .../org/dspace/core/LicenseServiceImpl.java | 28 +++++++++++++++---- 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/core/LicenseServiceImpl.java b/dspace-api/src/main/java/org/dspace/core/LicenseServiceImpl.java index 8324105a3085..5e42b04e7170 100644 --- a/dspace-api/src/main/java/org/dspace/core/LicenseServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/core/LicenseServiceImpl.java @@ -17,9 +17,12 @@ import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.PrintWriter; +import javax.servlet.http.HttpServletRequest; import org.dspace.core.service.LicenseService; import org.dspace.services.factory.DSpaceServicesFactory; +import org.dspace.services.model.Request; +import org.dspace.web.ContextUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -101,13 +104,14 @@ public String getLicenseText(String licenseFile) { /** * Get the site-wide default license that submitters need to grant * + * Localized license requires: default_{{locale}}.license file. + * Locale also must be listed in webui.supported.locales setting. + * * @return the default license */ @Override public String getDefaultSubmissionLicense() { - if (null == license) { - init(); - } + init(); return license; } @@ -115,9 +119,8 @@ public String getDefaultSubmissionLicense() { * Load in the default license. */ protected void init() { - File licenseFile = new File( - DSpaceServicesFactory.getInstance().getConfigurationService().getProperty("dspace.dir") - + File.separator + "config" + File.separator + "default.license"); + Context context = obtainContext(); + File licenseFile = new File(I18nUtil.getDefaultLicense(context)); FileInputStream fir = null; InputStreamReader ir = null; @@ -169,4 +172,17 @@ protected void init() { } } } + + /** + * Obtaining current request context + */ + private Context obtainContext() { + Request currentRequest = DSpaceServicesFactory.getInstance().getRequestService().getCurrentRequest(); + if (currentRequest != null) { + HttpServletRequest request = currentRequest.getHttpServletRequest(); + return ContextUtil.obtainContext(request); + } else { + return new Context(); + } + } } From c004a33c9dd1224d23de3ca23b371e5d8e258a22 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 24 Jul 2023 11:08:15 -0500 Subject: [PATCH 351/510] Replace all old docker "dspace-7_x" tags with "latest" --- Dockerfile | 5 ++-- Dockerfile.cli | 5 ++-- Dockerfile.test | 5 ++-- docker-compose-cli.yml | 2 +- docker-compose.yml | 6 ++--- dspace/src/main/docker-compose/README.md | 4 +-- .../src/main/docker-compose/db.entities.yml | 2 +- dspace/src/main/docker-compose/db.restore.yml | 2 +- .../docker-compose/docker-compose-angular.yml | 2 +- dspace/src/main/docker/README.md | 26 +++++++++---------- 10 files changed, 31 insertions(+), 28 deletions(-) diff --git a/Dockerfile b/Dockerfile index f1ff6adf5ac5..664cba89faea 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,14 +1,15 @@ # This image will be published as dspace/dspace # See https://github.com/DSpace/DSpace/tree/main/dspace/src/main/docker for usage details # -# - note: default tag for branch: dspace/dspace: dspace/dspace:dspace-7_x +# - note: default tag for branch: dspace/dspace: dspace/dspace:latest # This Dockerfile uses JDK11 by default, but has also been tested with JDK17. # To build with JDK17, use "--build-arg JDK_VERSION=17" ARG JDK_VERSION=11 +ARG DSPACE_VERSION=latest # Step 1 - Run Maven Build -FROM dspace/dspace-dependencies:dspace-7_x as build +FROM dspace/dspace-dependencies:${DSPACE_VERSION} as build ARG TARGET_DIR=dspace-installer WORKDIR /app # The dspace-installer directory will be written to /install diff --git a/Dockerfile.cli b/Dockerfile.cli index 62e83b79ef02..d54978375e54 100644 --- a/Dockerfile.cli +++ b/Dockerfile.cli @@ -1,14 +1,15 @@ # This image will be published as dspace/dspace-cli # See https://github.com/DSpace/DSpace/tree/main/dspace/src/main/docker for usage details # -# - note: default tag for branch: dspace/dspace-cli: dspace/dspace-cli:dspace-7_x +# - note: default tag for branch: dspace/dspace-cli: dspace/dspace-cli:latest # This Dockerfile uses JDK11 by default, but has also been tested with JDK17. # To build with JDK17, use "--build-arg JDK_VERSION=17" ARG JDK_VERSION=11 +ARG DSPACE_VERSION=latest # Step 1 - Run Maven Build -FROM dspace/dspace-dependencies:dspace-7_x as build +FROM dspace/dspace-dependencies:${DSPACE_VERSION} as build ARG TARGET_DIR=dspace-installer WORKDIR /app # The dspace-installer directory will be written to /install diff --git a/Dockerfile.test b/Dockerfile.test index 4e9b2b5b4343..16a04d00025f 100644 --- a/Dockerfile.test +++ b/Dockerfile.test @@ -1,16 +1,17 @@ # This image will be published as dspace/dspace # See https://github.com/DSpace/DSpace/tree/main/dspace/src/main/docker for usage details # -# - note: default tag for branch: dspace/dspace: dspace/dspace:dspace-7_x-test +# - note: default tag for branch: dspace/dspace: dspace/dspace:latest-test # # This image is meant for TESTING/DEVELOPMENT ONLY as it deploys the old v6 REST API under HTTP (not HTTPS) # This Dockerfile uses JDK11 by default, but has also been tested with JDK17. # To build with JDK17, use "--build-arg JDK_VERSION=17" ARG JDK_VERSION=11 +ARG DSPACE_VERSION=latest # Step 1 - Run Maven Build -FROM dspace/dspace-dependencies:dspace-7_x as build +FROM dspace/dspace-dependencies:${DSPACE_VERSION} as build ARG TARGET_DIR=dspace-installer WORKDIR /app # The dspace-installer directory will be written to /install diff --git a/docker-compose-cli.yml b/docker-compose-cli.yml index 9c66fed6835b..7dbdde370378 100644 --- a/docker-compose-cli.yml +++ b/docker-compose-cli.yml @@ -2,7 +2,7 @@ version: "3.7" services: dspace-cli: - image: "${DOCKER_OWNER:-dspace}/dspace-cli:${DSPACE_VER:-dspace-7_x}" + image: "${DOCKER_OWNER:-dspace}/dspace-cli:${DSPACE_VER:-latest}" container_name: dspace-cli build: context: . diff --git a/docker-compose.yml b/docker-compose.yml index 36ba6af2c981..e623d9607931 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -28,7 +28,7 @@ services: # proxies.trusted.ipranges: This setting is required for a REST API running in Docker to trust requests # from the host machine. This IP range MUST correspond to the 'dspacenet' subnet defined above. proxies__P__trusted__P__ipranges: '172.23.0' - image: "${DOCKER_OWNER:-dspace}/dspace:${DSPACE_VER:-dspace-7_x-test}" + image: "${DOCKER_OWNER:-dspace}/dspace:${DSPACE_VER:-latest-test}" build: context: . dockerfile: Dockerfile.test @@ -66,7 +66,7 @@ services: dspacedb: container_name: dspacedb # Uses a custom Postgres image with pgcrypto installed - image: "${DOCKER_OWNER:-dspace}/dspace-postgres-pgcrypto:${DSPACE_VER:-dspace-7_x}" + image: "${DOCKER_OWNER:-dspace}/dspace-postgres-pgcrypto:${DSPACE_VER:-latest}" build: # Must build out of subdirectory to have access to install script for pgcrypto context: ./dspace/src/main/docker/dspace-postgres-pgcrypto/ @@ -86,7 +86,7 @@ services: # DSpace Solr container dspacesolr: container_name: dspacesolr - image: "${DOCKER_OWNER:-dspace}/dspace-solr:${DSPACE_VER:-dspace-7_x}" + image: "${DOCKER_OWNER:-dspace}/dspace-solr:${DSPACE_VER:-latest}" build: context: . dockerfile: ./dspace/src/main/docker/dspace-solr/Dockerfile diff --git a/dspace/src/main/docker-compose/README.md b/dspace/src/main/docker-compose/README.md index 35a6e6055433..8660a9796c7d 100644 --- a/dspace/src/main/docker-compose/README.md +++ b/dspace/src/main/docker-compose/README.md @@ -268,8 +268,8 @@ Here's how to fix those issues by migrating your old Postgres data to the new ve * Pull down an older version of the image from Dockerhub (using a tag) * Or, temporarily rebuild your local image with the old version of Postgres. For example: ``` - # This command will rebuild using PostgreSQL v11 & tag it locally as "dspace-7_x" - docker build --build-arg POSTGRES_VERSION=11 -t dspace/dspace-postgres-pgcrypto:dspace-7_x ./dspace/src/main/docker/dspace-postgres-pgcrypto/ + # This command will rebuild using PostgreSQL v11 & tag it locally as "latest" + docker build --build-arg POSTGRES_VERSION=11 -t dspace/dspace-postgres-pgcrypto:latest ./dspace/src/main/docker/dspace-postgres-pgcrypto/ # Then restart container with that image docker-compose -p d7 up -d ``` diff --git a/dspace/src/main/docker-compose/db.entities.yml b/dspace/src/main/docker-compose/db.entities.yml index 32c54a5d0bd1..943f0732c69a 100644 --- a/dspace/src/main/docker-compose/db.entities.yml +++ b/dspace/src/main/docker-compose/db.entities.yml @@ -10,7 +10,7 @@ version: "3.7" services: dspacedb: - image: dspace/dspace-postgres-pgcrypto:dspace-7_x-loadsql + image: dspace/dspace-postgres-pgcrypto:${DSPACE_VER:-latest}-loadsql environment: # This SQL is available from https://github.com/DSpace-Labs/AIP-Files/releases/tag/demo-entities-data - LOADSQL=https://github.com/DSpace-Labs/AIP-Files/releases/download/demo-entities-data/dspace7-entities-data.sql diff --git a/dspace/src/main/docker-compose/db.restore.yml b/dspace/src/main/docker-compose/db.restore.yml index fc2f30b9d8e0..5646370a91ef 100644 --- a/dspace/src/main/docker-compose/db.restore.yml +++ b/dspace/src/main/docker-compose/db.restore.yml @@ -14,7 +14,7 @@ version: "3.7" # This can be used to restore a "dspacedb" container from a pg_dump, or during upgrade to a new version of PostgreSQL. services: dspacedb: - image: dspace/dspace-postgres-pgcrypto:dspace-7_x-loadsql + image: dspace/dspace-postgres-pgcrypto:${DSPACE_VER:-latest}-loadsql environment: # Location where the dump SQL file will be available on the running container - LOCALSQL=/tmp/pgdump.sql diff --git a/dspace/src/main/docker-compose/docker-compose-angular.yml b/dspace/src/main/docker-compose/docker-compose-angular.yml index 00dde2e83187..6690fb8bc526 100644 --- a/dspace/src/main/docker-compose/docker-compose-angular.yml +++ b/dspace/src/main/docker-compose/docker-compose-angular.yml @@ -23,7 +23,7 @@ services: DSPACE_REST_HOST: localhost DSPACE_REST_PORT: 8080 DSPACE_REST_NAMESPACE: /server - image: dspace/dspace-angular:dspace-7_x + image: dspace/dspace-angular:${DSPACE_VER:-latest} networks: dspacenet: ports: diff --git a/dspace/src/main/docker/README.md b/dspace/src/main/docker/README.md index ac1b4cb9236b..bee59ad876ea 100644 --- a/dspace/src/main/docker/README.md +++ b/dspace/src/main/docker/README.md @@ -16,7 +16,7 @@ Caching these Maven dependencies provides a speed increase to all later builds b are only downloaded once. ``` -docker build -t dspace/dspace-dependencies:dspace-7_x -f Dockerfile.dependencies . +docker build -t dspace/dspace-dependencies:latest -f Dockerfile.dependencies . ``` This image is built *automatically* after each commit is made to the `main` branch. @@ -25,7 +25,7 @@ A corresponding image exists for DSpace 4-6. Admins to our DockerHub repo can manually publish with the following command. ``` -docker push dspace/dspace-dependencies:dspace-7_x +docker push dspace/dspace-dependencies:latest ``` ## Dockerfile.test (in root folder) @@ -37,7 +37,7 @@ This image deploys two DSpace webapps to Tomcat running in Docker: This image also sets up debugging in Tomcat for development. ``` -docker build -t dspace/dspace:dspace-7_x-test -f Dockerfile.test . +docker build -t dspace/dspace:latest-test -f Dockerfile.test . ``` This image is built *automatically* after each commit is made to the `main` branch. @@ -46,7 +46,7 @@ A corresponding image exists for DSpace 4-6. Admins to our DockerHub repo can manually publish with the following command. ``` -docker push dspace/dspace:dspace-7_x-test +docker push dspace/dspace:latest-test ``` ## Dockerfile (in root folder) @@ -56,7 +56,7 @@ This image deploys one DSpace webapp to Tomcat running in Docker: 1. The DSpace 7 REST API (at `http://localhost:8080/server`) ``` -docker build -t dspace/dspace:dspace-7_x -f Dockerfile . +docker build -t dspace/dspace:latest -f Dockerfile . ``` This image is built *automatically* after each commit is made to the `main` branch. @@ -65,14 +65,14 @@ A corresponding image exists for DSpace 4-6. Admins to our DockerHub repo can publish with the following command. ``` -docker push dspace/dspace:dspace-7_x +docker push dspace/dspace:latest ``` ## Dockerfile.cli (in root folder) This Dockerfile builds a DSpace 7 CLI (command line interface) image, which can be used to run DSpace's commandline tools via Docker. ``` -docker build -t dspace/dspace-cli:dspace-7_x -f Dockerfile.cli . +docker build -t dspace/dspace-cli:latest -f Dockerfile.cli . ``` This image is built *automatically* after each commit is made to the `main` branch. @@ -81,7 +81,7 @@ A corresponding image exists for DSpace 6. Admins to our DockerHub repo can publish with the following command. ``` -docker push dspace/dspace-cli:dspace-7_x +docker push dspace/dspace-cli:latest ``` ## ./dspace-postgres-pgcrypto/Dockerfile @@ -92,20 +92,20 @@ This image is built *automatically* after each commit is made to the `main` bran How to build manually: ``` cd dspace/src/main/docker/dspace-postgres-pgcrypto -docker build -t dspace/dspace-postgres-pgcrypto:dspace-7_x . +docker build -t dspace/dspace-postgres-pgcrypto:latest . ``` It is also possible to change the version of PostgreSQL or the PostgreSQL user's password during the build: ``` cd dspace/src/main/docker/dspace-postgres-pgcrypto -docker build -t dspace/dspace-postgres-pgcrypto:dspace-7_x --build-arg POSTGRES_VERSION=11 --build-arg POSTGRES_PASSWORD=mypass . +docker build -t dspace/dspace-postgres-pgcrypto:latest --build-arg POSTGRES_VERSION=11 --build-arg POSTGRES_PASSWORD=mypass . ``` A copy of this file exists in the DSpace 6 branch. A specialized version of this file exists for DSpace 4 in DSpace-Docker-Images. Admins to our DockerHub repo can (manually) publish with the following command. ``` -docker push dspace/dspace-postgres-pgcrypto:dspace-7_x +docker push dspace/dspace-postgres-pgcrypto:latest ``` ## ./dspace-postgres-pgcrypto-curl/Dockerfile @@ -118,7 +118,7 @@ This image is built *automatically* after each commit is made to the `main` bran How to build manually: ``` cd dspace/src/main/docker/dspace-postgres-pgcrypto-curl -docker build -t dspace/dspace-postgres-pgcrypto:dspace-7_x-loadsql . +docker build -t dspace/dspace-postgres-pgcrypto:latest-loadsql . ``` Similar to `dspace-postgres-pgcrypto` above, you can also modify the version of PostgreSQL or the PostgreSQL user's password. @@ -128,7 +128,7 @@ A copy of this file exists in the DSpace 6 branch. Admins to our DockerHub repo can (manually) publish with the following command. ``` -docker push dspace/dspace-postgres-pgcrypto:dspace-7_x-loadsql +docker push dspace/dspace-postgres-pgcrypto:latest-loadsql ``` ## ./dspace-shibboleth/Dockerfile From f6a898c3d13360286c416b2588ab0447d9e3d81b Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Thu, 27 Jul 2023 16:55:42 -0500 Subject: [PATCH 352/510] Add action to automatically create a port PR when specified --- .../workflows/port_merged_pull_request.yml | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 .github/workflows/port_merged_pull_request.yml diff --git a/.github/workflows/port_merged_pull_request.yml b/.github/workflows/port_merged_pull_request.yml new file mode 100644 index 000000000000..418498fa4499 --- /dev/null +++ b/.github/workflows/port_merged_pull_request.yml @@ -0,0 +1,38 @@ +# This workflow will attempt to port a merged pull request to +# the branch specified in a "port to" label (if exists) +name: Port merged Pull Request + +# Only run for merged PRs against the "main" or maintenance branches +# We allow this to run for `pull_request_target` so that github secrets are available +# (This is required when the PR comes from a forked repo) +on: + pull_request_target: + types: [ closed ] + branches: + - main + - 'dspace-**' + +permissions: + contents: write # so action can add comments + pull-requests: write # so action can create pull requests + +jobs: + port_pr: + runs-on: ubuntu-latest + # Don't run on closed *unmerged* pull requests + if: github.event.pull_request.merged + steps: + # Checkout code + - uses: actions/checkout@v3 + # Port PR to other branch (ONLY if labeled with "port to") + # See https://github.com/korthout/backport-action + - name: Create backport pull requests + uses: korthout/backport-action@v1 + with: + # Trigger based on a "port to [branch]" label on PR + # (This label must specify the branch name to port to) + label_pattern: '^port to ([^ ]+)$' + # Title to add to the (newly created) port PR + pull_title: '[Port ${target_branch}] ${pull_title}' + # Description to add to the (newly created) port PR + pull_description: 'Port of #${pull_number} by @${pull_author} to `${target_branch}`.' \ No newline at end of file From 16c46c49797cf4a15b8ef51efdd365610bdf73ab Mon Sep 17 00:00:00 2001 From: Alan Orth Date: Wed, 26 Jul 2023 21:26:04 +0300 Subject: [PATCH 353/510] dspace.cfg: remove old webui.itemlist properties These properties are no longer used in DSpace 7: webui.itemlist.widths webui.itemlist.*.widths webui.itemlist.tablewidth --- dspace/config/dspace.cfg | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index cafd37931fd4..c37cfea0b196 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -1472,11 +1472,6 @@ log.report.dir = ${dspace.dir}/log # # webui.itemlist.columns = thumbnail, dc.date.issued(date), dc.title, dc.contributor.* # -# You can customise the width of each column with the following line - you can have numbers (pixels) -# or percentages. For the 'thumbnail' column, a setting of '*' will use the max width specified -# for browse thumbnails (webui.browse.thumbnail.maxwidth, thumbnail.maxwidth) -# webui.itemlist.widths = *, 130, 60%, 40% - # Additionally, you can override the DC fields used on the listing page for # a given browse index and/or sort option. As a sort option or index may be defined # on a field that isn't normally included in the list, this allows you to display @@ -1502,14 +1497,6 @@ log.report.dir = ${dspace.dir}/log # and thumbnails in the item list # # webui.itemlist.dateaccessioned.columns = thumbnail, dc.date.accessioned(date), dc.title, dc.contributor.* -# -# As above, you can customise the width of the columns for each configured column list, substituting '.widths' for -# '.columns' in the property name. See the setting for webui.itemlist.widths for more details -# webui.itemlist.dateaccessioned.widths = *, 130, 60%, 40% - -# You can also set the overall size of the item list table with the following setting. It can lead to faster -# table rendering when used with the column widths above, but not generally recommended. -# webui.itemlist.tablewidth = 100% ##### SFX Server (OpenURL) ##### From 213a546486073f09e73d91d014c489ed300bf59d Mon Sep 17 00:00:00 2001 From: Alan Orth Date: Wed, 26 Jul 2023 21:42:16 +0300 Subject: [PATCH 354/510] dspace.cfg: remove old webui.browse.thumbnail.show property The webui.browse.thumbnail.show property is no longer used as of DSpace 7. Also remove subsequent references to adding thumbnails to item view columns since these are from legacy DSpace. --- dspace/config/dspace.cfg | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index c37cfea0b196..c7f1cb80a978 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -1460,9 +1460,6 @@ log.report.dir = ${dspace.dir}/log # fields at least the date and title fields as specified by the # webui.browse.index.* configuration options below. # -# If you have enabled thumbnails (webui.browse.thumbnail.show), you must also -# include a 'thumbnail' entry in your columns - this is where the thumbnail will be displayed -# # If you want to mark each item include a 'mark_[value]' (without the brackets - replace the word 'value' with anything that # has a meaning for your mark) entry in your columns - this is where the icon will be displayed. # Do not forget to add a Spring bean with id = "org.dspace.app.itemmarking.ItemMarkingExtractor.[value]" @@ -1470,7 +1467,7 @@ log.report.dir = ${dspace.dir}/log # You can add more than one 'mark_[value]' options (with different value) in case you need to mark items more than one time for # different purposes. Remember to add the respective beans in file 'config/spring/api/item-marking.xml'. # -# webui.itemlist.columns = thumbnail, dc.date.issued(date), dc.title, dc.contributor.* +# webui.itemlist.columns = dc.date.issued(date), dc.title, dc.contributor.* # # Additionally, you can override the DC fields used on the listing page for # a given browse index and/or sort option. As a sort option or index may be defined @@ -1489,14 +1486,6 @@ log.report.dir = ${dspace.dir}/log # In the last case, a sort option name will always take precedence over a browse # index name. Note also, that for any additional columns you list, you will need to # ensure there is an itemlist. entry in the messages file. -# -# The following example would display the date of accession in place of the issue date -# whenever the dateaccessioned browse index or sort option is selected. -# -# Just like webui.itemlist.columns, you will need to include a 'thumbnail' entry to display -# and thumbnails in the item list -# -# webui.itemlist.dateaccessioned.columns = thumbnail, dc.date.accessioned(date), dc.title, dc.contributor.* ##### SFX Server (OpenURL) ##### From 35f72bc9d0c1a01aa0b9313216bbbe63e6960a81 Mon Sep 17 00:00:00 2001 From: Alan Orth Date: Wed, 26 Jul 2023 21:45:42 +0300 Subject: [PATCH 355/510] dspace.cfg: remove old webui.itemlist.browse.* property The webui.itemlist.browse.* properties are no longer used in DSpace 7. --- dspace/config/dspace.cfg | 6 ------ 1 file changed, 6 deletions(-) diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index c7f1cb80a978..1feaf05cda30 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -1478,14 +1478,8 @@ log.report.dir = ${dspace.dir}/log # they are listed below is the priority in which they will be used (so a combination # of an index name and sort name will take precedence over just the browse name). # -# webui.itemlist.browse..sort..columns # webui.itemlist.sort..columns -# webui.itemlist.browse..columns # webui.itemlist..columns -# -# In the last case, a sort option name will always take precedence over a browse -# index name. Note also, that for any additional columns you list, you will need to -# ensure there is an itemlist. entry in the messages file. ##### SFX Server (OpenURL) ##### From 22974e982c99b7faa9d287ddc5bef4715f19849a Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Fri, 28 Jul 2023 10:50:07 -0400 Subject: [PATCH 356/510] On failure log the name of the assetstore file and trace causes of exception. --- .../mediafilter/MediaFilterServiceImpl.java | 12 +++--- .../java/org/dspace/util/ThrowableUtils.java | 41 +++++++++++++++++++ 2 files changed, 48 insertions(+), 5 deletions(-) create mode 100644 dspace-api/src/main/java/org/dspace/util/ThrowableUtils.java diff --git a/dspace-api/src/main/java/org/dspace/app/mediafilter/MediaFilterServiceImpl.java b/dspace-api/src/main/java/org/dspace/app/mediafilter/MediaFilterServiceImpl.java index e2c6c9c5db06..1a8c2ddd3ea4 100644 --- a/dspace-api/src/main/java/org/dspace/app/mediafilter/MediaFilterServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/app/mediafilter/MediaFilterServiceImpl.java @@ -40,6 +40,7 @@ import org.dspace.eperson.service.GroupService; import org.dspace.scripts.handler.DSpaceRunnableHandler; import org.dspace.services.ConfigurationService; +import org.dspace.util.ThrowableUtils; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; @@ -240,8 +241,9 @@ public boolean filterBitstream(Context context, Item myItem, sb.append("\tFile Size: ").append(size); sb.append("\tChecksum: ").append(checksum); sb.append("\tAsset Store: ").append(assetstore); + sb.append("\tInternal ID: ").append(myBitstream.getInternalId()); logError(sb.toString()); - logError(e.getMessage(), e); + logError(ThrowableUtils.formatCauseChain(e)); } } else if (filterClass instanceof SelfRegisterInputFormats) { // Filter implements self registration, so check to see if it should be applied @@ -319,10 +321,10 @@ public boolean processBitstream(Context context, Item item, Bitstream source, Fo // check if destination bitstream exists Bundle existingBundle = null; - List existingBitstreams = new ArrayList(); + List existingBitstreams = new ArrayList<>(); List bundles = itemService.getBundles(item, formatFilter.getBundleName()); - if (bundles.size() > 0) { + if (!bundles.isEmpty()) { // only finds the last matching bundle and all matching bitstreams in the proper bundle(s) for (Bundle bundle : bundles) { List bitstreams = bundle.getBitstreams(); @@ -337,7 +339,7 @@ public boolean processBitstream(Context context, Item item, Bitstream source, Fo } // if exists and overwrite = false, exit - if (!overWrite && (existingBitstreams.size() > 0)) { + if (!overWrite && (!existingBitstreams.isEmpty())) { if (!isQuiet) { logInfo("SKIPPED: bitstream " + source.getID() + " (item: " + item.getHandle() + ") because '" + newName + "' already exists"); @@ -370,7 +372,7 @@ public boolean processBitstream(Context context, Item item, Bitstream source, Fo } Bundle targetBundle; // bundle we're modifying - if (bundles.size() < 1) { + if (bundles.isEmpty()) { // create new bundle if needed targetBundle = bundleService.create(context, item, formatFilter.getBundleName()); } else { diff --git a/dspace-api/src/main/java/org/dspace/util/ThrowableUtils.java b/dspace-api/src/main/java/org/dspace/util/ThrowableUtils.java new file mode 100644 index 000000000000..7809e2048a07 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/util/ThrowableUtils.java @@ -0,0 +1,41 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.util; + +/** + * Things you wish {@link Throwable} or some logging package would do for you. + * + * @author mwood + */ +public class ThrowableUtils { + /** + * Utility class: do not instantiate. + */ + private ThrowableUtils() { } + + /** + * Trace a chain of {@code Throwable}s showing only causes. + * Less voluminous than a stack trace. Useful if you just want to know + * what caused third-party code to return an uninformative exception + * message. + * + * @param throwable the exception or whatever. + * @return list of messages from each {@code Throwable} in the chain, + * separated by '\n'. + */ + static public String formatCauseChain(Throwable throwable) { + StringBuilder trace = new StringBuilder(); + trace.append(throwable.getMessage()); + Throwable cause = throwable.getCause(); + while (null != cause) { + trace.append("\nCaused by: ").append(cause.getMessage()); + cause = cause.getCause(); + } + return trace.toString(); + } +} From d6b612fc5cf84fe6b7226649451b7b927ded8997 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Fri, 28 Jul 2023 11:23:20 -0400 Subject: [PATCH 357/510] Report Throwable's type too. --- dspace-api/src/main/java/org/dspace/util/ThrowableUtils.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/util/ThrowableUtils.java b/dspace-api/src/main/java/org/dspace/util/ThrowableUtils.java index 7809e2048a07..e1502e89b514 100644 --- a/dspace-api/src/main/java/org/dspace/util/ThrowableUtils.java +++ b/dspace-api/src/main/java/org/dspace/util/ThrowableUtils.java @@ -33,7 +33,9 @@ static public String formatCauseChain(Throwable throwable) { trace.append(throwable.getMessage()); Throwable cause = throwable.getCause(); while (null != cause) { - trace.append("\nCaused by: ").append(cause.getMessage()); + trace.append("\nCaused by: ") + .append(cause.getClass().getCanonicalName()).append(' ') + .append(cause.getMessage()); cause = cause.getCause(); } return trace.toString(); From 2dc7c90e83867247df9004c54d27b5c551136283 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 28 Jul 2023 11:15:08 -0500 Subject: [PATCH 358/510] Run PR Port action as 'dspace-bot' to allow new PRs to trigger CI checks --- .github/workflows/port_merged_pull_request.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/port_merged_pull_request.yml b/.github/workflows/port_merged_pull_request.yml index 418498fa4499..6c491e41c21b 100644 --- a/.github/workflows/port_merged_pull_request.yml +++ b/.github/workflows/port_merged_pull_request.yml @@ -35,4 +35,10 @@ jobs: # Title to add to the (newly created) port PR pull_title: '[Port ${target_branch}] ${pull_title}' # Description to add to the (newly created) port PR - pull_description: 'Port of #${pull_number} by @${pull_author} to `${target_branch}`.' \ No newline at end of file + pull_description: 'Port of #${pull_number} by @${pull_author} to `${target_branch}`.' + # Copy all labels from original PR to (newly created) port PR + # NOTE: The labels matching 'label_pattern' are automatically excluded + copy_labels_pattern: '*' + # Use a personal access token (PAT) to create PR as 'dspace-bot' user. + # A PAT is required in order for the new PR to trigger its own actions (for CI checks) + github_token: ${{ secrets.PR_PORT_TOKEN }} \ No newline at end of file From 5bff43356fca0aa9ee78782f000c0d1a25a6cfbb Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 28 Jul 2023 11:15:42 -0500 Subject: [PATCH 359/510] Minor update to label_merge_conflicts to ignore any errors (seem random at this time) --- .github/workflows/label_merge_conflicts.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/label_merge_conflicts.yml b/.github/workflows/label_merge_conflicts.yml index 0c3b1a0f2a80..a023f4eef246 100644 --- a/.github/workflows/label_merge_conflicts.yml +++ b/.github/workflows/label_merge_conflicts.yml @@ -25,6 +25,8 @@ jobs: # See: https://github.com/prince-chrismc/label-merge-conflicts-action - name: Auto-label PRs with merge conflicts uses: prince-chrismc/label-merge-conflicts-action@v3 + # Ignore any failures -- may occur (randomly?) for older, outdated PRs. + continue-on-error: true # Add "merge conflict" label if a merge conflict is detected. Remove it when resolved. # Note, the authentication token is created automatically # See: https://docs.github.com/en/actions/configuring-and-managing-workflows/authenticating-with-the-github_token From 799528963e3c0391852ecbaf82ef21ec7d477342 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 28 Jul 2023 11:48:30 -0500 Subject: [PATCH 360/510] Fix typo. Config must be a valid regex --- .github/workflows/port_merged_pull_request.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/port_merged_pull_request.yml b/.github/workflows/port_merged_pull_request.yml index 6c491e41c21b..50faf3f88679 100644 --- a/.github/workflows/port_merged_pull_request.yml +++ b/.github/workflows/port_merged_pull_request.yml @@ -38,7 +38,7 @@ jobs: pull_description: 'Port of #${pull_number} by @${pull_author} to `${target_branch}`.' # Copy all labels from original PR to (newly created) port PR # NOTE: The labels matching 'label_pattern' are automatically excluded - copy_labels_pattern: '*' + copy_labels_pattern: '.*' # Use a personal access token (PAT) to create PR as 'dspace-bot' user. # A PAT is required in order for the new PR to trigger its own actions (for CI checks) github_token: ${{ secrets.PR_PORT_TOKEN }} \ No newline at end of file From 71cfe184a5b831f1cbc1d487f6ef32e04eb127b7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 28 Jul 2023 17:38:04 +0000 Subject: [PATCH 361/510] Bump h2 from 2.1.214 to 2.2.220 Bumps [h2](https://github.com/h2database/h2database) from 2.1.214 to 2.2.220. - [Release notes](https://github.com/h2database/h2database/releases) - [Commits](https://github.com/h2database/h2database/compare/version-2.1.214...version-2.2.220) --- updated-dependencies: - dependency-name: com.h2database:h2 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 5dc53194b335..7822d4310998 100644 --- a/pom.xml +++ b/pom.xml @@ -1688,7 +1688,7 @@ com.h2database h2 - 2.1.214 + 2.2.220 test From bbe5df3f7dd4a33423fdf47702e23f3eb9ef821f Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Mon, 31 Jul 2023 09:55:09 -0400 Subject: [PATCH 362/510] More description on OutOfMemoryError too. --- .../mediafilter/MediaFilterServiceImpl.java | 50 +++++++++++++------ 1 file changed, 34 insertions(+), 16 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/mediafilter/MediaFilterServiceImpl.java b/dspace-api/src/main/java/org/dspace/app/mediafilter/MediaFilterServiceImpl.java index 1a8c2ddd3ea4..b50fb22355a3 100644 --- a/dspace-api/src/main/java/org/dspace/app/mediafilter/MediaFilterServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/app/mediafilter/MediaFilterServiceImpl.java @@ -10,6 +10,7 @@ import java.io.InputStream; import java.sql.SQLException; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -226,23 +227,8 @@ public boolean filterBitstream(Context context, Item myItem, filtered = true; } } catch (Exception e) { - String handle = myItem.getHandle(); - List bundles = myBitstream.getBundles(); - long size = myBitstream.getSizeBytes(); - String checksum = myBitstream.getChecksum() + " (" + myBitstream.getChecksumAlgorithm() + ")"; - int assetstore = myBitstream.getStoreNumber(); - // Printout helpful information to find the errored bitstream. - StringBuilder sb = new StringBuilder("ERROR filtering, skipping bitstream:\n"); - sb.append("\tItem Handle: ").append(handle); - for (Bundle bundle : bundles) { - sb.append("\tBundle Name: ").append(bundle.getName()); - } - sb.append("\tFile Size: ").append(size); - sb.append("\tChecksum: ").append(checksum); - sb.append("\tAsset Store: ").append(assetstore); - sb.append("\tInternal ID: ").append(myBitstream.getInternalId()); - logError(sb.toString()); + logError(formatBitstreamDetails(myItem.getHandle(), myBitstream)); logError(ThrowableUtils.formatCauseChain(e)); } } else if (filterClass instanceof SelfRegisterInputFormats) { @@ -401,6 +387,7 @@ public boolean processBitstream(Context context, Item item, Bitstream source, Fo } catch (OutOfMemoryError oome) { logError("!!! OutOfMemoryError !!!"); + logError(formatBitstreamDetails(item.getHandle(), source)); } // we are overwriting, so remove old bitstream @@ -498,6 +485,37 @@ public boolean inSkipList(String identifier) { } } + /** + * Describe a Bitstream in detail. Format a single line of text with + * information such as Bitstore index, backing file ID, size, checksum, + * enclosing Item and Bundles. + * + * @param itemHandle Handle of the Item by which we found the Bitstream. + * @param bitstream the Bitstream to be described. + * @return Bitstream details. + */ + private String formatBitstreamDetails(String itemHandle, + Bitstream bitstream) { + List bundles; + try { + bundles = bitstream.getBundles(); + } catch (SQLException ex) { + logError("Unexpected error fetching Bundles", ex); + bundles = Collections.EMPTY_LIST; + } + StringBuilder sb = new StringBuilder("ERROR filtering, skipping bitstream:\n"); + sb.append("\tItem Handle: ").append(itemHandle); + for (Bundle bundle : bundles) { + sb.append("\tBundle Name: ").append(bundle.getName()); + } + sb.append("\tFile Size: ").append(bitstream.getSizeBytes()); + sb.append("\tChecksum: ").append(bitstream.getChecksum()) + .append(" (").append(bitstream.getChecksumAlgorithm()).append(')'); + sb.append("\tAsset Store: ").append(bitstream.getStoreNumber()); + sb.append("\tInternal ID: ").append(bitstream.getInternalId()); + return sb.toString(); + } + private void logInfo(String message) { if (handler != null) { handler.logInfo(message); From a76af35a0cd4f0c0e8737c736578b17bcc349691 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Tue, 1 Aug 2023 17:13:07 -0400 Subject: [PATCH 363/510] Make workflow curation tasks actually work. When curation runs, there was no "current user" and no claimed task, so the code broke when trying to find people to notify about curation failures. --- .../curate/XmlWorkflowCuratorServiceImpl.java | 29 ++++++++++++++----- .../dspace/eperson/EPersonServiceImpl.java | 18 ++++++++++++ .../eperson/service/EPersonService.java | 12 ++++++++ 3 files changed, 52 insertions(+), 7 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/curate/XmlWorkflowCuratorServiceImpl.java b/dspace-api/src/main/java/org/dspace/curate/XmlWorkflowCuratorServiceImpl.java index 05c7a8d99930..dd6c8d5e154a 100644 --- a/dspace-api/src/main/java/org/dspace/curate/XmlWorkflowCuratorServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/curate/XmlWorkflowCuratorServiceImpl.java @@ -13,6 +13,7 @@ import java.util.ArrayList; import java.util.List; +import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.dspace.authorize.AuthorizeException; import org.dspace.content.Collection; @@ -47,14 +48,17 @@ * Manage interactions between curation and workflow. A curation task can be * attached to a workflow step, to be executed during the step. * + *

+ * NOTE: when run in workflow, curation tasks run with + * authorization disabled. + * * @see CurationTaskConfig * @author mwood */ @Service public class XmlWorkflowCuratorServiceImpl implements XmlWorkflowCuratorService { - private static final Logger LOG - = org.apache.logging.log4j.LogManager.getLogger(); + private static final Logger LOG = LogManager.getLogger(); @Autowired(required = true) protected XmlWorkflowFactory workflowFactory; @@ -97,7 +101,13 @@ public boolean doCuration(Context c, XmlWorkflowItem wfi) throws AuthorizeException, IOException, SQLException { Curator curator = new Curator(); curator.setReporter(reporter); - return curate(curator, c, wfi); + c.turnOffAuthorisationSystem(); + if (null == c.getCurrentUser()) { // We need someone to email + c.setCurrentUser(ePersonService.findAnAdministrator(c)); + } + boolean failedP = curate(curator, c, wfi); + c.restoreAuthSystemState(); + return failedP; } @Override @@ -123,7 +133,7 @@ public boolean curate(Curator curator, Context c, XmlWorkflowItem wfi) item.setOwningCollection(wfi.getCollection()); for (Task task : step.tasks) { curator.addTask(task.name); - curator.curate(item); + curator.curate(c, item); int status = curator.getStatus(task.name); String result = curator.getResult(task.name); String action = "none"; @@ -223,8 +233,12 @@ protected void notifyContacts(Context c, XmlWorkflowItem wfi, String status, String action, String message) throws AuthorizeException, IOException, SQLException { List epa = resolveContacts(c, task.getContacts(status), wfi); - if (epa.size() > 0) { + if (!epa.isEmpty()) { workflowService.notifyOfCuration(c, wfi, epa, task.name, action, message); + } else { + LOG.warn("No contacts were found for workflow item {}: " + + "task {} returned action {} with message {}", + wfi.getID(), task.name, action, message); } } @@ -247,8 +261,7 @@ protected List resolveContacts(Context c, List contacts, // decode contacts if ("$flowgroup".equals(contact)) { // special literal for current flowgoup - ClaimedTask claimedTask = claimedTaskService.findByWorkflowIdAndEPerson(c, wfi, c.getCurrentUser()); - String stepID = claimedTask.getStepID(); + String stepID = getFlowStep(c, wfi).step; Step step; try { Workflow workflow = workflowFactory.getWorkflow(wfi.getCollection()); @@ -266,11 +279,13 @@ protected List resolveContacts(Context c, List contacts, epList.addAll(group.getMembers()); } } else if ("$colladmin".equals(contact)) { + // special literal for collection administrators Group adGroup = wfi.getCollection().getAdministrators(); if (adGroup != null) { epList.addAll(groupService.allMembers(c, adGroup)); } } else if ("$siteadmin".equals(contact)) { + // special literal for site administrator EPerson siteEp = ePersonService.findByEmail(c, configurationService.getProperty("mail.admin")); if (siteEp != null) { diff --git a/dspace-api/src/main/java/org/dspace/eperson/EPersonServiceImpl.java b/dspace-api/src/main/java/org/dspace/eperson/EPersonServiceImpl.java index 61477995c7ed..e3b743c4a711 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/EPersonServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/EPersonServiceImpl.java @@ -47,6 +47,7 @@ import org.dspace.eperson.service.SubscribeService; import org.dspace.event.Event; import org.dspace.orcid.service.OrcidTokenService; +import org.dspace.services.ConfigurationService; import org.dspace.util.UUIDUtils; import org.dspace.versioning.Version; import org.dspace.versioning.VersionHistory; @@ -101,6 +102,8 @@ public class EPersonServiceImpl extends DSpaceObjectServiceImpl impleme protected VersionDAO versionDAO; @Autowired(required = true) protected ClaimedTaskService claimedTaskService; + @Autowired(required = true) + protected ConfigurationService configurationService; @Autowired protected OrcidTokenService orcidTokenService; @@ -113,6 +116,21 @@ public EPerson find(Context context, UUID id) throws SQLException { return ePersonDAO.findByID(context, EPerson.class, id); } + @Override + public EPerson findAnAdministrator(Context c) + throws SQLException { + List contacts = groupService.findByName(c, Group.ADMIN).getMembers(); + EPerson currentUser; + if (contacts.isEmpty()) { + log.warn("Administrators group is empty"); + currentUser = findByEmail(c, configurationService.getProperty("mail.admin")); + // Null if no such EPerson + } else { + currentUser = contacts.get(0); + } + return currentUser; + } + @Override public EPerson findByIdOrLegacyId(Context context, String id) throws SQLException { if (StringUtils.isNumeric(id)) { diff --git a/dspace-api/src/main/java/org/dspace/eperson/service/EPersonService.java b/dspace-api/src/main/java/org/dspace/eperson/service/EPersonService.java index c5c9801c16dd..c3def01a829d 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/service/EPersonService.java +++ b/dspace-api/src/main/java/org/dspace/eperson/service/EPersonService.java @@ -157,6 +157,18 @@ public List findAll(Context context, int sortField) public List findAll(Context context, int sortField, int pageSize, int offset) throws SQLException; + /** + * Try very hard to find an administrator's account. Might return a member + * of the Administrators group, or an account with a configured email + * address. + * + * @param context current DSpace session. + * @return a presumed administrator account, or null if none could be found. + * @throws SQLException + */ + public EPerson findAnAdministrator(Context context) + throws SQLException; + /** * Create a new eperson * From 895926f021a355181faef47b5c41e78031700475 Mon Sep 17 00:00:00 2001 From: Agustina Martinez Date: Wed, 2 Aug 2023 15:24:29 +0100 Subject: [PATCH 364/510] Refactored access-status to include embargo date based on the DefaultAccessStatusHelper logic (look at primary or first bitstream) --- .../access/status/AccessStatusHelper.java | 10 +++ .../status/AccessStatusServiceImpl.java | 5 ++ .../status/DefaultAccessStatusHelper.java | 89 ++++++++++++++++++- .../status/service/AccessStatusService.java | 12 +++ .../status/DefaultAccessStatusHelperTest.java | 7 ++ .../AccessStatusElementItemCompilePlugin.java | 14 +++ .../oai/metadataFormats/uketd_dc.xsl | 11 +-- 7 files changed, 139 insertions(+), 9 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/access/status/AccessStatusHelper.java b/dspace-api/src/main/java/org/dspace/access/status/AccessStatusHelper.java index 1cacbf6aedf6..d847e907b403 100644 --- a/dspace-api/src/main/java/org/dspace/access/status/AccessStatusHelper.java +++ b/dspace-api/src/main/java/org/dspace/access/status/AccessStatusHelper.java @@ -27,4 +27,14 @@ public interface AccessStatusHelper { */ public String getAccessStatusFromItem(Context context, Item item, Date threshold) throws SQLException; + + /** + * Retrieve embargo information for the item + * + * @param context the DSpace context + * @param item the item to check for embargo information + * @return an embargo date + * @throws SQLException An exception that provides information on a database access error or other errors. + */ + public String getEmbargoFromItem(Context context, Item item) throws SQLException; } diff --git a/dspace-api/src/main/java/org/dspace/access/status/AccessStatusServiceImpl.java b/dspace-api/src/main/java/org/dspace/access/status/AccessStatusServiceImpl.java index 544dc99cb4dd..f0f68b22a195 100644 --- a/dspace-api/src/main/java/org/dspace/access/status/AccessStatusServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/access/status/AccessStatusServiceImpl.java @@ -63,4 +63,9 @@ public void init() throws Exception { public String getAccessStatus(Context context, Item item) throws SQLException { return helper.getAccessStatusFromItem(context, item, forever_date); } + + @Override + public String getEmbargoFromItem(Context context, Item item) throws SQLException { + return helper.getEmbargoFromItem(context, item); + } } diff --git a/dspace-api/src/main/java/org/dspace/access/status/DefaultAccessStatusHelper.java b/dspace-api/src/main/java/org/dspace/access/status/DefaultAccessStatusHelper.java index a67fa67af3b9..e7055181aa95 100644 --- a/dspace-api/src/main/java/org/dspace/access/status/DefaultAccessStatusHelper.java +++ b/dspace-api/src/main/java/org/dspace/access/status/DefaultAccessStatusHelper.java @@ -26,6 +26,7 @@ import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.eperson.Group; +import org.joda.time.LocalDate; /** * Default plugin implementation of the access status helper. @@ -33,6 +34,11 @@ * calculate the access status of an item based on the policies of * the primary or the first bitstream in the original bundle. * Users can override this method for enhanced functionality. + * + * The getEmbargoInformationFromItem method provides a simple logic to + * * retrieve embargo information of bitstreams from an item based on the policies of + * * the primary or the first bitstream in the original bundle. + * * Users can override this method for enhanced functionality. */ public class DefaultAccessStatusHelper implements AccessStatusHelper { public static final String EMBARGO = "embargo"; @@ -54,12 +60,12 @@ public DefaultAccessStatusHelper() { /** * Look at the item's policies to determine an access status value. - * It is also considering a date threshold for embargos and restrictions. + * It is also considering a date threshold for embargoes and restrictions. * * If the item is null, simply returns the "unknown" value. * * @param context the DSpace context - * @param item the item to embargo + * @param item the item to check for embargoes * @param threshold the embargo threshold date * @return an access status value */ @@ -86,7 +92,7 @@ public String getAccessStatusFromItem(Context context, Item item, Date threshold .findFirst() .orElse(null); } - return caculateAccessStatusForDso(context, bitstream, threshold); + return calculateAccessStatusForDso(context, bitstream, threshold); } /** @@ -104,7 +110,7 @@ public String getAccessStatusFromItem(Context context, Item item, Date threshold * @param threshold the embargo threshold date * @return an access status value */ - private String caculateAccessStatusForDso(Context context, DSpaceObject dso, Date threshold) + private String calculateAccessStatusForDso(Context context, DSpaceObject dso, Date threshold) throws SQLException { if (dso == null) { return METADATA_ONLY; @@ -156,4 +162,79 @@ private String caculateAccessStatusForDso(Context context, DSpaceObject dso, Dat } return RESTRICTED; } + + /** + * Look at the policies of the primary (or first) bitstream of the item to retrieve its embargo. + * + * If the item is null, simply returns an empty map with no embargo information. + * + * @param context the DSpace context + * @param item the item to embargo + * @return an access status value + */ + @Override + public String getEmbargoFromItem(Context context, Item item) + throws SQLException { + Date embargoDate; + + if (item == null) { + return null; + } + // Consider only the original bundles. + List bundles = item.getBundles(Constants.DEFAULT_BUNDLE_NAME); + // Check for primary bitstreams first. + Bitstream bitstream = bundles.stream() + .map(bundle -> bundle.getPrimaryBitstream()) + .filter(Objects::nonNull) + .findFirst() + .orElse(null); + if (bitstream == null) { + // If there is no primary bitstream, + // take the first bitstream in the bundles. + bitstream = bundles.stream() + .map(bundle -> bundle.getBitstreams()) + .flatMap(List::stream) + .findFirst() + .orElse(null); + } + + embargoDate = this.retrieveLongestEmbargo(context, bitstream); + + return embargoDate != null ? embargoDate.toString() : null; + } + + /** + * + */ + private Date retrieveLongestEmbargo(Context context, Bitstream bitstream) throws SQLException { + Date embargoDate = null; + // Only consider read policies. + List policies = authorizeService + .getPoliciesActionFilter(context, bitstream, Constants.READ); + + // Looks at all read policies. + for (ResourcePolicy policy : policies) { + boolean isValid = resourcePolicyService.isDateValid(policy); + Group group = policy.getGroup(); + + if (group != null && StringUtils.equals(group.getName(), Group.ANONYMOUS)) { + // Only calculate the status for the anonymous group. + if (!isValid) { + // If the policy is not valid there is an active embargo + Date startDate = policy.getStartDate(); + + if (startDate != null && !startDate.before(LocalDate.now().toDate())) { + // There is an active embargo: aim to take the longest embargo + if (embargoDate == null) { + embargoDate = startDate; + } else { + embargoDate = startDate.after(embargoDate) ? startDate : embargoDate; + } + } + } + } + } + + return embargoDate; + } } diff --git a/dspace-api/src/main/java/org/dspace/access/status/service/AccessStatusService.java b/dspace-api/src/main/java/org/dspace/access/status/service/AccessStatusService.java index 43de5e3c47f1..937cb02692ff 100644 --- a/dspace-api/src/main/java/org/dspace/access/status/service/AccessStatusService.java +++ b/dspace-api/src/main/java/org/dspace/access/status/service/AccessStatusService.java @@ -8,6 +8,7 @@ package org.dspace.access.status.service; import java.sql.SQLException; +import java.util.Date; import org.dspace.content.Item; import org.dspace.core.Context; @@ -40,7 +41,18 @@ public interface AccessStatusService { * * @param context the DSpace context * @param item the item + * @return an access status value * @throws SQLException An exception that provides information on a database access error or other errors. */ public String getAccessStatus(Context context, Item item) throws SQLException; + + /** + * Retrieve embargo information for the item + * + * @param context the DSpace context + * @param item the item to check for embargo information + * @return an embargo date + * @throws SQLException An exception that provides information on a database access error or other errors. + */ + public String getEmbargoFromItem(Context context, Item item) throws SQLException; } diff --git a/dspace-api/src/test/java/org/dspace/access/status/DefaultAccessStatusHelperTest.java b/dspace-api/src/test/java/org/dspace/access/status/DefaultAccessStatusHelperTest.java index a41e985deb32..9d90452beeda 100644 --- a/dspace-api/src/test/java/org/dspace/access/status/DefaultAccessStatusHelperTest.java +++ b/dspace-api/src/test/java/org/dspace/access/status/DefaultAccessStatusHelperTest.java @@ -8,6 +8,7 @@ package org.dspace.access.status; import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.fail; @@ -273,6 +274,8 @@ public void testWithEmbargo() throws Exception { context.restoreAuthSystemState(); String status = helper.getAccessStatusFromItem(context, itemWithEmbargo, threshold); assertThat("testWithEmbargo 0", status, equalTo(DefaultAccessStatusHelper.EMBARGO)); + String embargoDate = helper.getEmbargoFromItem(context, itemWithEmbargo); + assertThat("testWithEmbargo 1", embargoDate, equalTo(policy.getStartDate().toString())); } /** @@ -390,6 +393,8 @@ public void testWithPrimaryAndMultipleBitstreams() throws Exception { context.restoreAuthSystemState(); String status = helper.getAccessStatusFromItem(context, itemWithPrimaryAndMultipleBitstreams, threshold); assertThat("testWithPrimaryAndMultipleBitstreams 0", status, equalTo(DefaultAccessStatusHelper.EMBARGO)); + String embargoDate = helper.getEmbargoFromItem(context, itemWithPrimaryAndMultipleBitstreams); + assertThat("testWithPrimaryAndMultipleBitstreams 1", embargoDate, equalTo(policy.getStartDate().toString())); } /** @@ -419,5 +424,7 @@ public void testWithNoPrimaryAndMultipleBitstreams() throws Exception { context.restoreAuthSystemState(); String status = helper.getAccessStatusFromItem(context, itemWithoutPrimaryAndMultipleBitstreams, threshold); assertThat("testWithNoPrimaryAndMultipleBitstreams 0", status, equalTo(DefaultAccessStatusHelper.OPEN_ACCESS)); + String embargoDate = helper.getEmbargoFromItem(context, itemWithEmbargo); + assertThat("testWithNoPrimaryAndMultipleBitstreams 1", embargoDate, equalTo(null)); } } diff --git a/dspace-oai/src/main/java/org/dspace/xoai/app/plugins/AccessStatusElementItemCompilePlugin.java b/dspace-oai/src/main/java/org/dspace/xoai/app/plugins/AccessStatusElementItemCompilePlugin.java index 6b3c5ded9882..3201a0229178 100644 --- a/dspace-oai/src/main/java/org/dspace/xoai/app/plugins/AccessStatusElementItemCompilePlugin.java +++ b/dspace-oai/src/main/java/org/dspace/xoai/app/plugins/AccessStatusElementItemCompilePlugin.java @@ -12,6 +12,7 @@ import com.lyncode.xoai.dataprovider.xml.xoai.Element; import com.lyncode.xoai.dataprovider.xml.xoai.Metadata; +import org.apache.commons.lang3.StringUtils; import org.dspace.access.status.factory.AccessStatusServiceFactory; import org.dspace.access.status.service.AccessStatusService; import org.dspace.content.Item; @@ -31,6 +32,13 @@ * open.access * * + * OR + * + * + * embargo + * 2024-10-10 + * + * * } * * Returning Values are based on: @@ -46,9 +54,15 @@ public Metadata additionalMetadata(Context context, Metadata metadata, Item item String accessStatusType; accessStatusType = accessStatusService.getAccessStatus(context, item); + String embargoFromItem = accessStatusService.getEmbargoFromItem(context, item); + Element accessStatus = ItemUtils.create("access-status"); accessStatus.getField().add(ItemUtils.createValue("value", accessStatusType)); + if (StringUtils.isNotEmpty(embargoFromItem)) { + accessStatus.getField().add(ItemUtils.createValue("embargo", embargoFromItem)); + } + Element others; List elements = metadata.getElement(); if (ItemUtils.getElement(elements, "others") != null) { diff --git a/dspace/config/crosswalks/oai/metadataFormats/uketd_dc.xsl b/dspace/config/crosswalks/oai/metadataFormats/uketd_dc.xsl index b9d81aef5da8..a180b49c561b 100644 --- a/dspace/config/crosswalks/oai/metadataFormats/uketd_dc.xsl +++ b/dspace/config/crosswalks/oai/metadataFormats/uketd_dc.xsl @@ -115,6 +115,12 @@ + + + + + + @@ -123,11 +129,6 @@ - - - - From 4bd2cfdf0f931aec7a05db42f255423fe806ea77 Mon Sep 17 00:00:00 2001 From: Agustina Martinez Date: Wed, 2 Aug 2023 16:22:54 +0100 Subject: [PATCH 365/510] Remove unused imports --- .../org/dspace/access/status/service/AccessStatusService.java | 1 - .../org/dspace/access/status/DefaultAccessStatusHelperTest.java | 1 - 2 files changed, 2 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/access/status/service/AccessStatusService.java b/dspace-api/src/main/java/org/dspace/access/status/service/AccessStatusService.java index 937cb02692ff..2ed47bde4cd2 100644 --- a/dspace-api/src/main/java/org/dspace/access/status/service/AccessStatusService.java +++ b/dspace-api/src/main/java/org/dspace/access/status/service/AccessStatusService.java @@ -8,7 +8,6 @@ package org.dspace.access.status.service; import java.sql.SQLException; -import java.util.Date; import org.dspace.content.Item; import org.dspace.core.Context; diff --git a/dspace-api/src/test/java/org/dspace/access/status/DefaultAccessStatusHelperTest.java b/dspace-api/src/test/java/org/dspace/access/status/DefaultAccessStatusHelperTest.java index 9d90452beeda..f450f72e6a81 100644 --- a/dspace-api/src/test/java/org/dspace/access/status/DefaultAccessStatusHelperTest.java +++ b/dspace-api/src/test/java/org/dspace/access/status/DefaultAccessStatusHelperTest.java @@ -8,7 +8,6 @@ package org.dspace.access.status; import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.fail; From 724a4ffb0ed9ffefb2866930655767590b462bb5 Mon Sep 17 00:00:00 2001 From: Agustina Martinez Date: Wed, 2 Aug 2023 18:01:07 +0100 Subject: [PATCH 366/510] Fix style issues --- .../src/main/java/org/dspace/xoai/util/ItemUtils.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/dspace-oai/src/main/java/org/dspace/xoai/util/ItemUtils.java b/dspace-oai/src/main/java/org/dspace/xoai/util/ItemUtils.java index 2d252ff47698..2af526c56046 100644 --- a/dspace-oai/src/main/java/org/dspace/xoai/util/ItemUtils.java +++ b/dspace-oai/src/main/java/org/dspace/xoai/util/ItemUtils.java @@ -13,9 +13,9 @@ import java.sql.SQLException; import java.text.SimpleDateFormat; import java.util.Date; -import java.util.List; import java.util.ArrayList; import java.util.Collections; +import java.util.List; import com.lyncode.xoai.dataprovider.xml.xoai.Element; import com.lyncode.xoai.dataprovider.xml.xoai.Metadata; @@ -180,7 +180,9 @@ private static Element createBundlesElement(Context context, Item item) throws S private static void addEmbargoField(Context context, Bitstream bitstream, Element bitstreamEl) throws SQLException { GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); Group anonymousGroup = groupService.findByName(context, Group.ANONYMOUS); - List policies = authorizeService.findPoliciesByDSOAndType(context, bitstream, ResourcePolicy.TYPE_CUSTOM); + List policies = authorizeService.findPoliciesByDSOAndType(context, + bitstream, + ResourcePolicy.TYPE_CUSTOM); List embargoDates = new ArrayList<>(); // Account for cases where there could be more than one embargo policy From 6e2c8a4ae0068d844d0fc796001c170c8849babf Mon Sep 17 00:00:00 2001 From: Agustina Martinez Date: Wed, 2 Aug 2023 18:56:05 +0100 Subject: [PATCH 367/510] Fix style issues --- dspace-oai/src/main/java/org/dspace/xoai/util/ItemUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-oai/src/main/java/org/dspace/xoai/util/ItemUtils.java b/dspace-oai/src/main/java/org/dspace/xoai/util/ItemUtils.java index 2af526c56046..6a0808259e8e 100644 --- a/dspace-oai/src/main/java/org/dspace/xoai/util/ItemUtils.java +++ b/dspace-oai/src/main/java/org/dspace/xoai/util/ItemUtils.java @@ -12,9 +12,9 @@ import java.io.InputStream; import java.sql.SQLException; import java.text.SimpleDateFormat; -import java.util.Date; import java.util.ArrayList; import java.util.Collections; +import java.util.Date; import java.util.List; import com.lyncode.xoai.dataprovider.xml.xoai.Element; From bb9e88d1bb452d0865f4827134baf907e6d34044 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Wed, 2 Aug 2023 16:25:46 -0400 Subject: [PATCH 368/510] Community request: fake EPerson from configuration. --- .../curate/XmlWorkflowCuratorServiceImpl.java | 7 ++++- .../dspace/eperson/EPersonServiceImpl.java | 29 ++++++++++++------- .../eperson/service/EPersonService.java | 12 ++++---- 3 files changed, 32 insertions(+), 16 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/curate/XmlWorkflowCuratorServiceImpl.java b/dspace-api/src/main/java/org/dspace/curate/XmlWorkflowCuratorServiceImpl.java index dd6c8d5e154a..97537befd2f4 100644 --- a/dspace-api/src/main/java/org/dspace/curate/XmlWorkflowCuratorServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/curate/XmlWorkflowCuratorServiceImpl.java @@ -102,10 +102,15 @@ public boolean doCuration(Context c, XmlWorkflowItem wfi) Curator curator = new Curator(); curator.setReporter(reporter); c.turnOffAuthorisationSystem(); + boolean wasAnonymous = false; if (null == c.getCurrentUser()) { // We need someone to email - c.setCurrentUser(ePersonService.findAnAdministrator(c)); + wasAnonymous = true; + c.setCurrentUser(ePersonService.getSystemEPerson(c)); } boolean failedP = curate(curator, c, wfi); + if (wasAnonymous) { + c.setCurrentUser(null); + } c.restoreAuthSystemState(); return failedP; } diff --git a/dspace-api/src/main/java/org/dspace/eperson/EPersonServiceImpl.java b/dspace-api/src/main/java/org/dspace/eperson/EPersonServiceImpl.java index e3b743c4a711..2d0574a6301d 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/EPersonServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/EPersonServiceImpl.java @@ -116,19 +116,28 @@ public EPerson find(Context context, UUID id) throws SQLException { return ePersonDAO.findByID(context, EPerson.class, id); } + /** + * Create a fake EPerson which can receive email. Its address will be the + * value of "mail.admin", or "postmaster" if all else fails. + * @param c + * @return + * @throws SQLException + */ @Override - public EPerson findAnAdministrator(Context c) + public EPerson getSystemEPerson(Context c) throws SQLException { - List contacts = groupService.findByName(c, Group.ADMIN).getMembers(); - EPerson currentUser; - if (contacts.isEmpty()) { - log.warn("Administrators group is empty"); - currentUser = findByEmail(c, configurationService.getProperty("mail.admin")); - // Null if no such EPerson - } else { - currentUser = contacts.get(0); + String adminEmail = configurationService.getProperty("mail.admin"); + if (null == adminEmail) { + adminEmail = "postmaster"; // Last-ditch attempt to send *somewhere* } - return currentUser; + EPerson systemEPerson = findByEmail(c, adminEmail); + + if (null == systemEPerson) { + systemEPerson = new EPerson(); + systemEPerson.setEmail(adminEmail); + } + + return systemEPerson; } @Override diff --git a/dspace-api/src/main/java/org/dspace/eperson/service/EPersonService.java b/dspace-api/src/main/java/org/dspace/eperson/service/EPersonService.java index c3def01a829d..47be942e97e9 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/service/EPersonService.java +++ b/dspace-api/src/main/java/org/dspace/eperson/service/EPersonService.java @@ -13,6 +13,7 @@ import java.util.Date; import java.util.List; import java.util.Set; +import javax.validation.constraints.NotNull; import org.dspace.authorize.AuthorizeException; import org.dspace.content.Item; @@ -158,15 +159,16 @@ public List findAll(Context context, int sortField, int pageSize, int o throws SQLException; /** - * Try very hard to find an administrator's account. Might return a member - * of the Administrators group, or an account with a configured email - * address. + * The "System EPerson" is a fake account that exists only to receive email. + * It has an email address that should be presumed usable. It does not + * exist in the database and is not complete. * * @param context current DSpace session. - * @return a presumed administrator account, or null if none could be found. + * @return an EPerson that can presumably receive email. * @throws SQLException */ - public EPerson findAnAdministrator(Context context) + @NotNull + public EPerson getSystemEPerson(Context context) throws SQLException; /** From b71eee89c1e1dd7569e800e13eb8878548853ce6 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 2 Aug 2023 15:36:40 -0500 Subject: [PATCH 369/510] Enable entity type to submission form mapping by default --- dspace/config/item-submission.xml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/dspace/config/item-submission.xml b/dspace/config/item-submission.xml index a6cd49bdf1e8..1060a3303119 100644 --- a/dspace/config/item-submission.xml +++ b/dspace/config/item-submission.xml @@ -55,9 +55,7 @@ - - - - - - PLEASE NOTICE THAT YOU WILL HAVE TO RESTART DSPACE - - - - - - - Uncomment if you intend to use them - --> - @@ -65,8 +63,6 @@ - --> - From be22790aad7f627e2ac027773e272b703986f589 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Wed, 2 Aug 2023 17:23:36 -0400 Subject: [PATCH 370/510] Correct some documentation. --- .../curate/service/XmlWorkflowCuratorService.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/curate/service/XmlWorkflowCuratorService.java b/dspace-api/src/main/java/org/dspace/curate/service/XmlWorkflowCuratorService.java index 2ad1eac12904..778b779cfe03 100644 --- a/dspace-api/src/main/java/org/dspace/curate/service/XmlWorkflowCuratorService.java +++ b/dspace-api/src/main/java/org/dspace/curate/service/XmlWorkflowCuratorService.java @@ -42,9 +42,9 @@ public boolean needsCuration(Context c, XmlWorkflowItem wfi) * * @param c the context * @param wfi the workflow item - * @return true if curation was completed or not required, + * @return true if curation was completed or not required; * false if tasks were queued for later completion, - * or item was rejected + * or item was rejected. * @throws AuthorizeException if authorization error * @throws IOException if IO error * @throws SQLException if database error @@ -58,7 +58,9 @@ public boolean doCuration(Context c, XmlWorkflowItem wfi) * @param curator the curation context * @param c the user context * @param wfId the workflow item's ID - * @return true if curation failed. + * @return true if curation curation was completed or not required; + * false if tasks were queued for later completion, + * or item was rejected. * @throws AuthorizeException if authorization error * @throws IOException if IO error * @throws SQLException if database error @@ -72,7 +74,9 @@ public boolean curate(Curator curator, Context c, String wfId) * @param curator the curation context * @param c the user context * @param wfi the workflow item - * @return true if curation failed. + * @return true if workflow curation was completed or not required; + * false if tasks were queued for later completion, + * or item was rejected. * @throws AuthorizeException if authorization error * @throws IOException if IO error * @throws SQLException if database error From 0de4c3945ed7f30d41841cda4bf01acf9ffc130f Mon Sep 17 00:00:00 2001 From: Agustina Martinez Date: Thu, 3 Aug 2023 08:54:01 +0100 Subject: [PATCH 371/510] Add null check --- .../org/dspace/access/status/DefaultAccessStatusHelper.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/dspace-api/src/main/java/org/dspace/access/status/DefaultAccessStatusHelper.java b/dspace-api/src/main/java/org/dspace/access/status/DefaultAccessStatusHelper.java index e7055181aa95..9b5227491bc0 100644 --- a/dspace-api/src/main/java/org/dspace/access/status/DefaultAccessStatusHelper.java +++ b/dspace-api/src/main/java/org/dspace/access/status/DefaultAccessStatusHelper.java @@ -198,6 +198,10 @@ public String getEmbargoFromItem(Context context, Item item) .orElse(null); } + if (bitstream == null) { + return null; + } + embargoDate = this.retrieveLongestEmbargo(context, bitstream); return embargoDate != null ? embargoDate.toString() : null; From 291afa765d29836a67727fdd2f82ac0c9f9310c4 Mon Sep 17 00:00:00 2001 From: Agustina Martinez Date: Thu, 3 Aug 2023 09:54:00 +0100 Subject: [PATCH 372/510] ItemUtils.java: refactored addEmbargoField --- .../java/org/dspace/xoai/util/ItemUtils.java | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/dspace-oai/src/main/java/org/dspace/xoai/util/ItemUtils.java b/dspace-oai/src/main/java/org/dspace/xoai/util/ItemUtils.java index 6a0808259e8e..80eb67a2b994 100644 --- a/dspace-oai/src/main/java/org/dspace/xoai/util/ItemUtils.java +++ b/dspace-oai/src/main/java/org/dspace/xoai/util/ItemUtils.java @@ -12,8 +12,6 @@ import java.io.InputStream; import java.sql.SQLException; import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Collections; import java.util.Date; import java.util.List; @@ -184,22 +182,28 @@ private static void addEmbargoField(Context context, Bitstream bitstream, Elemen bitstream, ResourcePolicy.TYPE_CUSTOM); - List embargoDates = new ArrayList<>(); + Date embargoDate = null; + // Account for cases where there could be more than one embargo policy for (ResourcePolicy policy : policies) { if (policy.getGroup() == anonymousGroup && policy.getAction() == Constants.READ) { Date startDate = policy.getStartDate(); if (startDate != null && startDate.after(new Date())) { - embargoDates.add(startDate); + // There is an active embargo: aim to take the longest embargo + if (embargoDate == null) { + embargoDate = startDate; + } else { + embargoDate = startDate.after(embargoDate) ? startDate : embargoDate; + } } } } - if (embargoDates.size() >= 1) { + + if (embargoDate != null) { // Sort array of dates to extract the longest embargo SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd"); - Collections.sort(embargoDates, Date::compareTo); bitstreamEl.getField().add( - createValue("embargo", formatter.format(embargoDates.get(embargoDates.size() - 1)))); + createValue("embargo", formatter.format(embargoDate))); } } From 29a88d7e2dcfc36d2cd7991de3b84ef5f5623b63 Mon Sep 17 00:00:00 2001 From: Christian Bethge <54576195+ChrisBethgster@users.noreply.github.com> Date: Thu, 3 Aug 2023 13:01:12 +0200 Subject: [PATCH 373/510] #9006 fix referenced configuration file --- .../src/main/java/org/dspace/statistics/GeoIpService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/statistics/GeoIpService.java b/dspace-api/src/main/java/org/dspace/statistics/GeoIpService.java index 7f8a11e5ba13..40fea6cf54da 100644 --- a/dspace-api/src/main/java/org/dspace/statistics/GeoIpService.java +++ b/dspace-api/src/main/java/org/dspace/statistics/GeoIpService.java @@ -37,7 +37,7 @@ public class GeoIpService { public DatabaseReader getDatabaseReader() throws IllegalStateException { String dbPath = configurationService.getProperty("usage-statistics.dbfile"); if (StringUtils.isBlank(dbPath)) { - throw new IllegalStateException("The required 'dbfile' configuration is missing in solr-statistics.cfg!"); + throw new IllegalStateException("The required 'dbfile' configuration is missing in usage-statistics.cfg!"); } try { From 309b0b355e4bffd6a1be3e6341dd6d17f99892c8 Mon Sep 17 00:00:00 2001 From: Christian Bethge <54576195+ChrisBethgster@users.noreply.github.com> Date: Thu, 3 Aug 2023 13:04:03 +0200 Subject: [PATCH 374/510] #9006 fix referenced configuration file (Test) --- .../src/test/java/org/dspace/app/rest/HealthIndicatorsIT.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/HealthIndicatorsIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/HealthIndicatorsIT.java index 8c1c534de14c..0bb679339877 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/HealthIndicatorsIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/HealthIndicatorsIT.java @@ -67,7 +67,7 @@ public void testWithAdminUser() throws Exception { match("solrSearchCore", Status.UP, Map.of("status", 0, "detectedPathType", "root")), match("solrStatisticsCore", Status.UP, Map.of("status", 0, "detectedPathType", "root")), match("geoIp", UP_WITH_ISSUES_STATUS, - Map.of("reason", "The required 'dbfile' configuration is missing in solr-statistics.cfg!")) + Map.of("reason", "The required 'dbfile' configuration is missing in usage-statistics.cfg!")) ))); } From 4b40872a6d5a3934c1f79c6babf439a21ce25f66 Mon Sep 17 00:00:00 2001 From: Agustina Martinez Date: Thu, 3 Aug 2023 14:30:33 +0100 Subject: [PATCH 375/510] uketd_dc.xsl: also expose access-status if embargo or restricted --- dspace/config/crosswalks/oai/metadataFormats/uketd_dc.xsl | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/dspace/config/crosswalks/oai/metadataFormats/uketd_dc.xsl b/dspace/config/crosswalks/oai/metadataFormats/uketd_dc.xsl index a180b49c561b..5c434e49ed35 100644 --- a/dspace/config/crosswalks/oai/metadataFormats/uketd_dc.xsl +++ b/dspace/config/crosswalks/oai/metadataFormats/uketd_dc.xsl @@ -115,6 +115,14 @@ + + + + + + + + From d44507d647dd0c96ab80bce6d9a7aeb6edfb5540 Mon Sep 17 00:00:00 2001 From: Max Nuding Date: Fri, 4 Aug 2023 08:39:03 +0200 Subject: [PATCH 376/510] Remove duplicate code --- .../java/org/dspace/statistics/SolrLoggerServiceImpl.java | 5 ----- 1 file changed, 5 deletions(-) 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 9f34a4204721..19c79af34dcc 100644 --- a/dspace-api/src/main/java/org/dspace/statistics/SolrLoggerServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/statistics/SolrLoggerServiceImpl.java @@ -1699,11 +1699,6 @@ protected synchronized void initSolrYearCores() { //Also add the core containing the current year, if it hasn't been added already statisticYearCores.add(baseCore); } - //Also add the core containing the current year ! - statisticYearCores.add(((HttpSolrClient) solr) - .getBaseURL() - .replace("http://", "") - .replace("https://", "")); } catch (IOException | SolrServerException e) { log.error(e.getMessage(), e); } From d5dd2c93bb5dc3fef58080adb71fddc0f7d105b3 Mon Sep 17 00:00:00 2001 From: Francesco Bacchelli Date: Mon, 7 Aug 2023 08:59:01 +0200 Subject: [PATCH 377/510] CST-11298 sql file renaming and junit java fix --- .../OpenaireEventsImportScriptConfiguration.java | 12 ++++-------- ...ed.sql => V8.0_2023.08.07__qaevent_processed.sql} | 0 ...ed.sql => V8.0_2023.08.07__qaevent_processed.sql} | 0 ...ed.sql => V8.0_2023.08.07__qaevent_processed.sql} | 0 4 files changed, 4 insertions(+), 8 deletions(-) rename dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/{V7.3_2022.02.17__qaevent_processed.sql => V8.0_2023.08.07__qaevent_processed.sql} (100%) rename dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/{V7.3_2022.02.17__qaevent_processed.sql => V8.0_2023.08.07__qaevent_processed.sql} (100%) rename dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/{V7.3_2022.02.17__qaevent_processed.sql => V8.0_2023.08.07__qaevent_processed.sql} (100%) diff --git a/dspace-api/src/main/java/org/dspace/qaevent/script/OpenaireEventsImportScriptConfiguration.java b/dspace-api/src/main/java/org/dspace/qaevent/script/OpenaireEventsImportScriptConfiguration.java index 1a6f94f6a56d..14737de6350b 100644 --- a/dspace-api/src/main/java/org/dspace/qaevent/script/OpenaireEventsImportScriptConfiguration.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/script/OpenaireEventsImportScriptConfiguration.java @@ -8,13 +8,9 @@ package org.dspace.qaevent.script; import java.io.InputStream; -import java.sql.SQLException; import org.apache.commons.cli.Options; -import org.dspace.authorize.service.AuthorizeService; -import org.dspace.core.Context; import org.dspace.scripts.configuration.ScriptConfiguration; -import org.springframework.beans.factory.annotation.Autowired; /** * Extension of {@link ScriptConfiguration} to perfom a QAEvents import from @@ -25,9 +21,9 @@ */ public class OpenaireEventsImportScriptConfiguration extends ScriptConfiguration { - @Autowired + /* private AuthorizeService authorizeService; - + */ private Class dspaceRunnableClass; @Override @@ -43,7 +39,7 @@ public Class getDspaceRunnableClass() { public void setDspaceRunnableClass(Class dspaceRunnableClass) { this.dspaceRunnableClass = dspaceRunnableClass; } - +/* @Override public boolean isAllowedToExecute(Context context) { try { @@ -52,7 +48,7 @@ public boolean isAllowedToExecute(Context context) { throw new RuntimeException("SQLException occurred when checking if the current user is an admin", e); } } - +*/ @Override public Options getOptions() { if (options == null) { diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.3_2022.02.17__qaevent_processed.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V8.0_2023.08.07__qaevent_processed.sql similarity index 100% rename from dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.3_2022.02.17__qaevent_processed.sql rename to dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V8.0_2023.08.07__qaevent_processed.sql diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.3_2022.02.17__qaevent_processed.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V8.0_2023.08.07__qaevent_processed.sql similarity index 100% rename from dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.3_2022.02.17__qaevent_processed.sql rename to dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V8.0_2023.08.07__qaevent_processed.sql diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.3_2022.02.17__qaevent_processed.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V8.0_2023.08.07__qaevent_processed.sql similarity index 100% rename from dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.3_2022.02.17__qaevent_processed.sql rename to dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V8.0_2023.08.07__qaevent_processed.sql From 8ae5ffbf9b41fe2ad4e1146eceeff47e6ba985a0 Mon Sep 17 00:00:00 2001 From: Christian Bethge Date: Mon, 7 Aug 2023 09:42:07 +0200 Subject: [PATCH 378/510] fix MissingOptionException on help --- .../dspace/app/launcher/ScriptLauncher.java | 14 ++++- .../org/dspace/scripts/DSpaceRunnable.java | 55 +++++++++++++++++-- .../configuration/ScriptConfiguration.java | 15 +++++ .../dspace/app/bulkedit/MetadataExportIT.java | 15 +++-- .../dspace/app/bulkedit/MetadataImportIT.java | 5 +- .../app/csv/CSVMetadataImportReferenceIT.java | 5 +- .../java/org/dspace/curate/CurationIT.java | 10 ++-- 7 files changed, 98 insertions(+), 21 deletions(-) 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 fcb2098bd066..e6df016613b9 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 @@ -21,6 +21,7 @@ import org.apache.logging.log4j.Logger; import org.dspace.core.Context; import org.dspace.scripts.DSpaceRunnable; +import org.dspace.scripts.DSpaceRunnable.StepResult; import org.dspace.scripts.configuration.ScriptConfiguration; import org.dspace.scripts.factory.ScriptServiceFactory; import org.dspace.scripts.handler.DSpaceRunnableHandler; @@ -145,9 +146,16 @@ public static int handleScript(String[] args, Document commandConfigs, private static int executeScript(String[] args, DSpaceRunnableHandler dSpaceRunnableHandler, DSpaceRunnable script) { try { - script.initialize(args, dSpaceRunnableHandler, null); - script.run(); - return 0; + StepResult result = script.initialize(args, dSpaceRunnableHandler, null); + + if (StepResult.Continue.equals(result)) { + // only run the script, if the normal initialize is successful + script.run(); + } else { + // otherwise - for example the script is started with the help argument + } + + return 0; } catch (ParseException e) { script.printHelp(); e.printStackTrace(); diff --git a/dspace-api/src/main/java/org/dspace/scripts/DSpaceRunnable.java b/dspace-api/src/main/java/org/dspace/scripts/DSpaceRunnable.java index 2319aee31752..4f64f6973179 100644 --- a/dspace-api/src/main/java/org/dspace/scripts/DSpaceRunnable.java +++ b/dspace-api/src/main/java/org/dspace/scripts/DSpaceRunnable.java @@ -35,6 +35,11 @@ public abstract class DSpaceRunnable implements R * The CommandLine object for the script that'll hold the information */ protected CommandLine commandLine; + + /** + * The minimal CommandLine object for the script that'll hold help information + */ + protected CommandLine helpCommandLine; /** * This EPerson identifier variable is the UUID of the EPerson that's running the script @@ -64,27 +69,64 @@ private void setHandler(DSpaceRunnableHandler dSpaceRunnableHandler) { * @param args The arguments given to the script * @param dSpaceRunnableHandler The DSpaceRunnableHandler object that defines from where the script was ran * @param currentUser + * @return the result of this step; StepResult.Continue: continue the normal process, initialize is successful; + * otherwise exit the process (the help or version is shown) * @throws ParseException If something goes wrong */ - public void initialize(String[] args, DSpaceRunnableHandler dSpaceRunnableHandler, + public StepResult initialize(String[] args, DSpaceRunnableHandler dSpaceRunnableHandler, EPerson currentUser) throws ParseException { if (currentUser != null) { this.setEpersonIdentifier(currentUser.getID()); } this.setHandler(dSpaceRunnableHandler); - this.parse(args); + + // parse the command line in a first step for the help options + // --> no other option is required + StepResult result = this.parseForHelp(args); + switch (result) { + case Exit: + // arguments of the command line matches the help options, handle this + handleHelpCommandLine(); + break; + + case Continue: + // arguments of the command line matches NOT the help options, parse the args for the normal options + result = this.parse(args); + break; + } + + return result; } - /** + + /** This method handle the help command line. In this easy implementation only the help is printed. + * For more complexity override this method. + */ + private void handleHelpCommandLine() { + printHelp(); + } + + + /** * This method will take the primitive array of String objects that represent the parameters given to the String * and it'll parse these into a CommandLine object that can be used by the script to retrieve the data * @param args The primitive array of Strings representing the parameters * @throws ParseException If something goes wrong */ - private void parse(String[] args) throws ParseException { + private StepResult parse(String[] args) throws ParseException { commandLine = new DefaultParser().parse(getScriptConfiguration().getOptions(), args); setup(); + return StepResult.Continue; } + + private StepResult parseForHelp(String[] args) throws ParseException { + helpCommandLine = new DefaultParser().parse(getScriptConfiguration().getHelpOptions(), args); + if (helpCommandLine.getOptions() != null && helpCommandLine.getOptions().length > 0) { + return StepResult.Exit; + } + + return StepResult.Continue; + } /** * This method has to be included in every script and handles the setup of the script by parsing the CommandLine @@ -158,4 +200,9 @@ public UUID getEpersonIdentifier() { public void setEpersonIdentifier(UUID epersonIdentifier) { this.epersonIdentifier = epersonIdentifier; } + + public enum StepResult { + Continue, + Exit; + } } diff --git a/dspace-api/src/main/java/org/dspace/scripts/configuration/ScriptConfiguration.java b/dspace-api/src/main/java/org/dspace/scripts/configuration/ScriptConfiguration.java index 642409a924f7..62f30f99f65a 100644 --- a/dspace-api/src/main/java/org/dspace/scripts/configuration/ScriptConfiguration.java +++ b/dspace-api/src/main/java/org/dspace/scripts/configuration/ScriptConfiguration.java @@ -10,6 +10,7 @@ import java.sql.SQLException; import java.util.List; +import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; import org.dspace.authorize.service.AuthorizeService; import org.dspace.core.Context; @@ -104,6 +105,20 @@ public boolean isAllowedToExecute(Context context, List Date: Mon, 7 Aug 2023 11:27:08 +0200 Subject: [PATCH 379/510] fix stylecheck --- .../dspace/app/launcher/ScriptLauncher.java | 10 ++- .../org/dspace/scripts/DSpaceRunnable.java | 70 ++++++++++--------- .../configuration/ScriptConfiguration.java | 11 ++- .../dspace/app/bulkedit/MetadataExportIT.java | 18 ++--- .../dspace/app/bulkedit/MetadataImportIT.java | 6 +- .../app/csv/CSVMetadataImportReferenceIT.java | 7 +- .../java/org/dspace/curate/CurationIT.java | 12 ++-- 7 files changed, 67 insertions(+), 67 deletions(-) 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 e6df016613b9..bcb61a48ee76 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 @@ -147,15 +147,13 @@ private static int executeScript(String[] args, DSpaceRunnableHandler dSpaceRunn DSpaceRunnable script) { try { StepResult result = script.initialize(args, dSpaceRunnableHandler, null); - if (StepResult.Continue.equals(result)) { - // only run the script, if the normal initialize is successful - script.run(); + // only run the script, if the normal initialize is successful + script.run(); } else { - // otherwise - for example the script is started with the help argument + // otherwise - for example the script is started with the help argument } - - return 0; + return 0; } catch (ParseException e) { script.printHelp(); e.printStackTrace(); diff --git a/dspace-api/src/main/java/org/dspace/scripts/DSpaceRunnable.java b/dspace-api/src/main/java/org/dspace/scripts/DSpaceRunnable.java index 4f64f6973179..7fb8567f8cd1 100644 --- a/dspace-api/src/main/java/org/dspace/scripts/DSpaceRunnable.java +++ b/dspace-api/src/main/java/org/dspace/scripts/DSpaceRunnable.java @@ -35,7 +35,7 @@ public abstract class DSpaceRunnable implements R * The CommandLine object for the script that'll hold the information */ protected CommandLine commandLine; - + /** * The minimal CommandLine object for the script that'll hold help information */ @@ -69,8 +69,8 @@ private void setHandler(DSpaceRunnableHandler dSpaceRunnableHandler) { * @param args The arguments given to the script * @param dSpaceRunnableHandler The DSpaceRunnableHandler object that defines from where the script was ran * @param currentUser - * @return the result of this step; StepResult.Continue: continue the normal process, initialize is successful; - * otherwise exit the process (the help or version is shown) + * @return the result of this step; StepResult.Continue: continue the normal process, + * initialize is successful; otherwise exit the process (the help or version is shown) * @throws ParseException If something goes wrong */ public StepResult initialize(String[] args, DSpaceRunnableHandler dSpaceRunnableHandler, @@ -79,35 +79,38 @@ public StepResult initialize(String[] args, DSpaceRunnableHandler dSpaceRunnable this.setEpersonIdentifier(currentUser.getID()); } this.setHandler(dSpaceRunnableHandler); - - // parse the command line in a first step for the help options + + // parse the command line in a first step for the help options // --> no other option is required StepResult result = this.parseForHelp(args); switch (result) { - case Exit: - // arguments of the command line matches the help options, handle this - handleHelpCommandLine(); - break; - - case Continue: - // arguments of the command line matches NOT the help options, parse the args for the normal options - result = this.parse(args); - break; - } - + case Exit: + // arguments of the command line matches the help options, handle this + handleHelpCommandLine(); + break; + + case Continue: + // arguments of the command line matches NOT the help options, parse the args for the normal options + result = this.parse(args); + break; + default: + break; + } + return result; } - /** This method handle the help command line. In this easy implementation only the help is printed. - * For more complexity override this method. - */ - private void handleHelpCommandLine() { - printHelp(); - } + /** + * This method handle the help command line. In this easy implementation only the help is printed. For more + * complexity override this method. + */ + private void handleHelpCommandLine() { + printHelp(); + } - /** + /** * This method will take the primitive array of String objects that represent the parameters given to the String * and it'll parse these into a CommandLine object that can be used by the script to retrieve the data * @param args The primitive array of Strings representing the parameters @@ -118,15 +121,15 @@ private StepResult parse(String[] args) throws ParseException { setup(); return StepResult.Continue; } - + private StepResult parseForHelp(String[] args) throws ParseException { - helpCommandLine = new DefaultParser().parse(getScriptConfiguration().getHelpOptions(), args); - if (helpCommandLine.getOptions() != null && helpCommandLine.getOptions().length > 0) { - return StepResult.Exit; - } - - return StepResult.Continue; - } + helpCommandLine = new DefaultParser().parse(getScriptConfiguration().getHelpOptions(), args); + if (helpCommandLine.getOptions() != null && helpCommandLine.getOptions().length > 0) { + return StepResult.Exit; + } + + return StepResult.Continue; + } /** * This method has to be included in every script and handles the setup of the script by parsing the CommandLine @@ -200,9 +203,8 @@ public UUID getEpersonIdentifier() { public void setEpersonIdentifier(UUID epersonIdentifier) { this.epersonIdentifier = epersonIdentifier; } - + public enum StepResult { - Continue, - Exit; + Continue, Exit; } } diff --git a/dspace-api/src/main/java/org/dspace/scripts/configuration/ScriptConfiguration.java b/dspace-api/src/main/java/org/dspace/scripts/configuration/ScriptConfiguration.java index 62f30f99f65a..bbedab04e278 100644 --- a/dspace-api/src/main/java/org/dspace/scripts/configuration/ScriptConfiguration.java +++ b/dspace-api/src/main/java/org/dspace/scripts/configuration/ScriptConfiguration.java @@ -105,17 +105,16 @@ public boolean isAllowedToExecute(Context context, List Date: Mon, 7 Aug 2023 13:37:22 +0200 Subject: [PATCH 380/510] CST-11298 openaire test fix --- .../script/OpenaireEventsImportIT.java | 34 ++++++++++++----- .../dspace/app/openaire-events/events.json | 37 ++++++++++++++++++- 2 files changed, 60 insertions(+), 11 deletions(-) diff --git a/dspace-api/src/test/java/org/dspace/qaevent/script/OpenaireEventsImportIT.java b/dspace-api/src/test/java/org/dspace/qaevent/script/OpenaireEventsImportIT.java index 22da785d3146..dbe44fd2e72f 100644 --- a/dspace-api/src/test/java/org/dspace/qaevent/script/OpenaireEventsImportIT.java +++ b/dspace-api/src/test/java/org/dspace/qaevent/script/OpenaireEventsImportIT.java @@ -153,12 +153,15 @@ public void testManyEventsImportFromFile() throws Exception { assertThat(handler.getWarningMessages(), empty()); assertThat(handler.getInfoMessages(), contains( "Trying to read the QA events from the provided file", - "Found 2 events in the given file")); + "Found 5 events in the given file")); - assertThat(qaEventService.findAllSources(0, 20), contains(QASourceMatcher.with(OPENAIRE_SOURCE, 2L))); + assertThat(qaEventService.findAllSources(0, 20), contains(QASourceMatcher.with(OPENAIRE_SOURCE, 5L))); assertThat(qaEventService.findAllTopics(0, 20), containsInAnyOrder( QATopicMatcher.with("ENRICH/MORE/PROJECT", 1L), + QATopicMatcher.with("ENRICH/MORE/PID", 1L), + QATopicMatcher.with("ENRICH/MISSING/PID", 1L), + QATopicMatcher.with("ENRICH/MISSING/PROJECT", 1L), QATopicMatcher.with("ENRICH/MISSING/ABSTRACT", 1L))); String projectMessage = "{\"projects[0].acronym\":\"PAThs\",\"projects[0].code\":\"687567\"," @@ -200,14 +203,21 @@ public void testManyEventsImportFromFileWithUnknownHandle() throws Exception { assertThat(handler.getWarningMessages(), contains("An error occurs storing the event with id b4e09c71312cd7c397969f56c900823f: " + "Skipped event b4e09c71312cd7c397969f56c900823f related to the oai record " + + "oai:www.openstarts.units.it:123456789/99998 as the record was not found", + "An error occurs storing the event with id d050d2c4399c6c6ccf27d52d479d26e4: " + + "Skipped event d050d2c4399c6c6ccf27d52d479d26e4 related to the oai record " + "oai:www.openstarts.units.it:123456789/99998 as the record was not found")); assertThat(handler.getInfoMessages(), contains( "Trying to read the QA events from the provided file", - "Found 2 events in the given file")); + "Found 5 events in the given file")); - assertThat(qaEventService.findAllSources(0, 20), contains(QASourceMatcher.with(OPENAIRE_SOURCE, 1L))); + assertThat(qaEventService.findAllSources(0, 20), contains(QASourceMatcher.with(OPENAIRE_SOURCE, 3L))); - assertThat(qaEventService.findAllTopics(0, 20), contains(QATopicMatcher.with("ENRICH/MISSING/ABSTRACT", 1L))); + assertThat(qaEventService.findAllTopics(0, 20), containsInAnyOrder( + QATopicMatcher.with("ENRICH/MISSING/ABSTRACT", 1L), + QATopicMatcher.with("ENRICH/MISSING/PROJECT", 1L), + QATopicMatcher.with("ENRICH/MORE/PID", 1L) + )); String abstractMessage = "{\"abstracts[0]\":\"Missing Abstract\"}"; @@ -311,14 +321,17 @@ public void testImportFromOpenaireBroker() throws Exception { assertThat(handler.getInfoMessages(), contains( "Trying to read the QA events from the OPENAIRE broker", "Found 3 subscriptions related to the given email", - "Found 2 events from the subscription sub1", + "Found 5 events from the subscription sub1", "Found 0 events from the subscription sub2", "Found 2 events from the subscription sub3")); - assertThat(qaEventService.findAllSources(0, 20), contains(QASourceMatcher.with(OPENAIRE_SOURCE, 3L))); + assertThat(qaEventService.findAllSources(0, 20), contains(QASourceMatcher.with(OPENAIRE_SOURCE, 6L))); assertThat(qaEventService.findAllTopics(0, 20), containsInAnyOrder( QATopicMatcher.with("ENRICH/MORE/PROJECT", 1L), + QATopicMatcher.with("ENRICH/MORE/PID", 1L), + QATopicMatcher.with("ENRICH/MISSING/PID", 1L), + QATopicMatcher.with("ENRICH/MISSING/PROJECT", 1L), QATopicMatcher.with("ENRICH/MISSING/ABSTRACT", 2L))); String projectMessage = "{\"projects[0].acronym\":\"PAThs\",\"projects[0].code\":\"687567\"," @@ -413,14 +426,17 @@ public void testImportFromOpenaireBrokerWithErrorDuringEventsDownload() throws E assertThat(handler.getInfoMessages(), contains( "Trying to read the QA events from the OPENAIRE broker", "Found 3 subscriptions related to the given email", - "Found 2 events from the subscription sub1", + "Found 5 events from the subscription sub1", "Found 0 events from the subscription sub2", "Found 2 events from the subscription sub3")); - assertThat(qaEventService.findAllSources(0, 20), contains(QASourceMatcher.with(OPENAIRE_SOURCE, 3L))); + assertThat(qaEventService.findAllSources(0, 20), contains(QASourceMatcher.with(OPENAIRE_SOURCE, 6L))); assertThat(qaEventService.findAllTopics(0, 20), containsInAnyOrder( QATopicMatcher.with("ENRICH/MORE/PROJECT", 1L), + QATopicMatcher.with("ENRICH/MISSING/PID", 1L), + QATopicMatcher.with("ENRICH/MORE/PID", 1L), + QATopicMatcher.with("ENRICH/MISSING/PROJECT", 1L), QATopicMatcher.with("ENRICH/MISSING/ABSTRACT", 2L))); assertThat(qaEventService.findEventsByTopic("ENRICH/MORE/PROJECT"), hasSize(1)); diff --git a/dspace-api/src/test/resources/org/dspace/app/openaire-events/events.json b/dspace-api/src/test/resources/org/dspace/app/openaire-events/events.json index 7d8dbd37f16a..9bb8daae36c7 100644 --- a/dspace-api/src/test/resources/org/dspace/app/openaire-events/events.json +++ b/dspace-api/src/test/resources/org/dspace/app/openaire-events/events.json @@ -24,6 +24,39 @@ "message": { "abstracts[0]": "Missing Abstract" } - } - + }, + { + "originalId": "oai:www.openstarts.units.it:123456789/99998", + "title": "Egypt, crossroad of translations and literary interweavings", + "topic": "ENRICH/MISSING/PID", + "trust": 1.0, + "message": { + "pids[0].type": "doi", + "pids[0].value": "10.13137/2282-572x/987" + } + }, + { + "originalId": "oai:www.openstarts.units.it:123456789/99999", + "title": "Test Publication", + "topic": "ENRICH/MORE/PID", + "trust": 0.375, + "message": { + "pids[0].type": "doi", + "pids[0].value": "987654" + } + }, + { + "originalId": "oai:www.openstarts.units.it:123456789/99999", + "title": "Test Publication", + "topic": "ENRICH/MISSING/PROJECT", + "trust": 1.0, + "message": { + "projects[0].acronym": "02.SNES missing project acronym", + "projects[0].code": "prjcode_snes", + "projects[0].funder": "02.SNES missing project funder", + "projects[0].fundingProgram": "02.SNES missing project fundingProgram", + "projects[0].jurisdiction": "02.SNES missing project jurisdiction", + "projects[0].title": "Project01" + } + } ] \ No newline at end of file From 54280e8fe2d0d2e98250ce647c451d588f4c3559 Mon Sep 17 00:00:00 2001 From: Koen Pauwels Date: Fri, 4 Aug 2023 11:39:35 +0200 Subject: [PATCH 381/510] 103818 ItemServiceImpl#inheritCollectionDefaultPolicies now clears item READ policies if new parent collection has a default READ policy --- .../org/dspace/content/ItemServiceImpl.java | 8 +++ .../content/service/ItemServiceTest.java | 54 +++++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java index 8d1ba14b2c4a..3458361f43e6 100644 --- a/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java @@ -920,6 +920,14 @@ public void removeGroupPolicies(Context context, Item item, Group group) throws @Override public void inheritCollectionDefaultPolicies(Context context, Item item, Collection collection) throws SQLException, AuthorizeException { + + // If collection has READ policies, remove the item's READ policies. + List defaultCollectionPolicies = authorizeService + .getPoliciesActionFilter(context, collection, Constants.DEFAULT_ITEM_READ); + if (!defaultCollectionPolicies.isEmpty()) { + authorizeService.removePoliciesActionFilter(context, item, Constants.READ); + } + adjustItemPolicies(context, item, collection); adjustBundleBitstreamPolicies(context, item, collection); diff --git a/dspace-api/src/test/java/org/dspace/content/service/ItemServiceTest.java b/dspace-api/src/test/java/org/dspace/content/service/ItemServiceTest.java index 50b4d3f3b48e..1847a27c7f5a 100644 --- a/dspace-api/src/test/java/org/dspace/content/service/ItemServiceTest.java +++ b/dspace-api/src/test/java/org/dspace/content/service/ItemServiceTest.java @@ -26,6 +26,8 @@ import org.dspace.app.requestitem.RequestItem; import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.ResourcePolicy; +import org.dspace.authorize.factory.AuthorizeServiceFactory; +import org.dspace.authorize.service.AuthorizeService; import org.dspace.builder.BitstreamBuilder; import org.dspace.builder.CollectionBuilder; import org.dspace.builder.CommunityBuilder; @@ -48,6 +50,8 @@ import org.dspace.content.factory.ContentServiceFactory; import org.dspace.core.Constants; import org.dspace.eperson.Group; +import org.dspace.eperson.factory.EPersonServiceFactory; +import org.dspace.eperson.service.GroupService; import org.dspace.versioning.Version; import org.dspace.versioning.factory.VersionServiceFactory; import org.dspace.versioning.service.VersioningService; @@ -68,6 +72,8 @@ public class ItemServiceTest extends AbstractIntegrationTestWithDatabase { protected WorkspaceItemService workspaceItemService = ContentServiceFactory.getInstance().getWorkspaceItemService(); protected MetadataValueService metadataValueService = ContentServiceFactory.getInstance().getMetadataValueService(); protected VersioningService versioningService = VersionServiceFactory.getInstance().getVersionService(); + protected AuthorizeService authorizeService = AuthorizeServiceFactory.getInstance().getAuthorizeService(); + protected GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); Community community; Collection collection1; @@ -752,6 +758,54 @@ public void testRemoveItemThatHasRequests() throws Exception { assertNull(itemService.find(context, item.getID())); } + + @Test + public void testMoveItemToCollectionWithMoreRestrictiveReadPolicy() throws Exception { + /* Verify that, if we move an item from a collection with a permissive default READ policy + * to a collection with a restrictive default READ policy, + * that the item does not retain the original permissive READ policy. + */ + + context.turnOffAuthorisationSystem(); + + Group anonymous = groupService.findByName(context, Group.ANONYMOUS); + Group admin = groupService.findByName(context, Group.ADMIN); + + // Set up the two different collections: one permissive and one restrictive in its default READ policy. + Collection permissive = CollectionBuilder + .createCollection(context, community) + .build(); + Collection restrictive = CollectionBuilder + .createCollection(context, community) + .build(); + authorizeService.removePoliciesActionFilter(context, restrictive, Constants.DEFAULT_ITEM_READ); + authorizeService.addPolicy(context, restrictive, Constants.DEFAULT_ITEM_READ, admin); + + // Add an item to the permissive collection. + Item item = ItemBuilder + .createItem(context, permissive) + .build(); + + // Verify that the item has exactly one READ policy, for the anonymous group. + assertEquals( + List.of(anonymous), + authorizeService.getPoliciesActionFilter(context, item, Constants.READ) + .stream().map(ResourcePolicy::getGroup).collect(Collectors.toList()) + ); + + // Move the item to the restrictive collection, making sure to inherit default policies. + itemService.move(context, item, permissive, restrictive, true); + + // Verify that the item has exactly one READ policy, but now for the admin group. + assertEquals( + List.of(admin), + authorizeService.getPoliciesActionFilter(context, item, Constants.READ) + .stream().map(ResourcePolicy::getGroup).collect(Collectors.toList()) + ); + + context.restoreAuthSystemState(); + } + private void assertMetadataValue(String authorQualifier, String contributorElement, String dcSchema, String value, String authority, int place, MetadataValue metadataValue) { assertThat(metadataValue.getValue(), equalTo(value)); From e4ff24a2d95ebf8e91f70304ee6a7de48382a15a Mon Sep 17 00:00:00 2001 From: Koen Pauwels Date: Fri, 4 Aug 2023 15:34:14 +0200 Subject: [PATCH 382/510] 103818 Add boolean parameter to ItemServiceImpl#inheritCollectionDefaultPolicies to decide whether to override item read policies --- .../content/InstallItemServiceImpl.java | 2 +- .../org/dspace/content/ItemServiceImpl.java | 17 ++++++++++---- .../dspace/content/service/ItemService.java | 22 ++++++++++++++++++- 3 files changed, 35 insertions(+), 6 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/InstallItemServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/InstallItemServiceImpl.java index 32c5b92c605b..b52043e2673c 100644 --- a/dspace-api/src/main/java/org/dspace/content/InstallItemServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/InstallItemServiceImpl.java @@ -93,7 +93,7 @@ public Item installItem(Context c, InProgressSubmission is, // As this is a BRAND NEW item, as a final step we need to remove the // submitter item policies created during deposit and replace them with // the default policies from the collection. - itemService.inheritCollectionDefaultPolicies(c, item, collection); + itemService.inheritCollectionDefaultPolicies(c, item, collection, false); return item; } diff --git a/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java index 3458361f43e6..663585034d12 100644 --- a/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java @@ -920,12 +920,21 @@ public void removeGroupPolicies(Context context, Item item, Group group) throws @Override public void inheritCollectionDefaultPolicies(Context context, Item item, Collection collection) throws SQLException, AuthorizeException { + inheritCollectionDefaultPolicies(context, item, collection, true); + } + + @Override + public void inheritCollectionDefaultPolicies(Context context, Item item, Collection collection, + boolean overrideItemReadPolicies) + throws SQLException, AuthorizeException { // If collection has READ policies, remove the item's READ policies. - List defaultCollectionPolicies = authorizeService - .getPoliciesActionFilter(context, collection, Constants.DEFAULT_ITEM_READ); - if (!defaultCollectionPolicies.isEmpty()) { - authorizeService.removePoliciesActionFilter(context, item, Constants.READ); + if (overrideItemReadPolicies) { + List defaultCollectionPolicies = authorizeService + .getPoliciesActionFilter(context, collection, Constants.DEFAULT_ITEM_READ); + if (!defaultCollectionPolicies.isEmpty()) { + authorizeService.removePoliciesActionFilter(context, item, Constants.READ); + } } adjustItemPolicies(context, item, collection); diff --git a/dspace-api/src/main/java/org/dspace/content/service/ItemService.java b/dspace-api/src/main/java/org/dspace/content/service/ItemService.java index b6bf7aa5cfa2..8b93e953eb81 100644 --- a/dspace-api/src/main/java/org/dspace/content/service/ItemService.java +++ b/dspace-api/src/main/java/org/dspace/content/service/ItemService.java @@ -473,7 +473,7 @@ public void replaceAllBitstreamPolicies(Context context, Item item, List Date: Fri, 4 Aug 2023 16:38:30 +0200 Subject: [PATCH 383/510] 103818 Add boolean parameters to ItemServiceImpl methodds to decide whether to override read policies --- .../org/dspace/content/ItemServiceImpl.java | 47 +++++++--- .../dspace/content/service/ItemService.java | 93 ++++++++++++++++--- 2 files changed, 113 insertions(+), 27 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java index 663585034d12..ac38f0cca49c 100644 --- a/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java @@ -925,20 +925,11 @@ public void inheritCollectionDefaultPolicies(Context context, Item item, Collect @Override public void inheritCollectionDefaultPolicies(Context context, Item item, Collection collection, - boolean overrideItemReadPolicies) + boolean replaceReadRPWithCollectionRP) throws SQLException, AuthorizeException { - // If collection has READ policies, remove the item's READ policies. - if (overrideItemReadPolicies) { - List defaultCollectionPolicies = authorizeService - .getPoliciesActionFilter(context, collection, Constants.DEFAULT_ITEM_READ); - if (!defaultCollectionPolicies.isEmpty()) { - authorizeService.removePoliciesActionFilter(context, item, Constants.READ); - } - } - - adjustItemPolicies(context, item, collection); - adjustBundleBitstreamPolicies(context, item, collection); + adjustItemPolicies(context, item, collection, replaceReadRPWithCollectionRP); + adjustBundleBitstreamPolicies(context, item, collection, replaceReadRPWithCollectionRP); log.debug(LogHelper.getHeader(context, "item_inheritCollectionDefaultPolicies", "item_id=" + item.getID())); @@ -947,6 +938,13 @@ public void inheritCollectionDefaultPolicies(Context context, Item item, Collect @Override public void adjustBundleBitstreamPolicies(Context context, Item item, Collection collection) throws SQLException, AuthorizeException { + adjustBundleBitstreamPolicies(context, item, collection, true); + } + + @Override + public void adjustBundleBitstreamPolicies(Context context, Item item, Collection collection, + boolean replaceReadRPWithCollectionRP) + throws SQLException, AuthorizeException { // Bundles should inherit from DEFAULT_ITEM_READ so that if the item is readable, the files // can be listed (even if they are themselves not readable as per DEFAULT_BITSTREAM_READ or other // policies or embargos applied @@ -969,6 +967,10 @@ public void adjustBundleBitstreamPolicies(Context context, Item item, Collection // Remove bundles List bunds = item.getBundles(); for (Bundle mybundle : bunds) { + // If collection has default READ policies, remove the bitstream's READ policies. + if (replaceReadRPWithCollectionRP && defaultCollectionBitstreamPolicies.size() > 0) { + authorizeService.removePoliciesActionFilter(context, item, Constants.READ); + } // if come from InstallItem: remove all submission/workflow policies authorizeService.removeAllPoliciesByDSOAndType(context, mybundle, ResourcePolicy.TYPE_SUBMISSION); @@ -985,7 +987,14 @@ public void adjustBundleBitstreamPolicies(Context context, Item item, Collection } @Override - public void adjustBitstreamPolicies(Context context, Item item, Collection collection , Bitstream bitstream) + public void adjustBitstreamPolicies(Context context, Item item, Collection collection, Bitstream bitstream) + throws SQLException, AuthorizeException { + adjustBitstreamPolicies(context, item, collection, bitstream, true); + } + + @Override + public void adjustBitstreamPolicies(Context context, Item item, Collection collection , Bitstream bitstream, + boolean replaceReadRPWithCollectionRP) throws SQLException, AuthorizeException { List defaultCollectionPolicies = authorizeService .getPoliciesActionFilter(context, collection, Constants.DEFAULT_BITSTREAM_READ); @@ -1015,10 +1024,22 @@ private void removeAllPoliciesAndAddDefault(Context context, Bitstream bitstream @Override public void adjustItemPolicies(Context context, Item item, Collection collection) throws SQLException, AuthorizeException { + adjustItemPolicies(context, item, collection, true); + } + + @Override + public void adjustItemPolicies(Context context, Item item, Collection collection, + boolean replaceReadRPWithCollectionRP) + throws SQLException, AuthorizeException { // read collection's default READ policies List defaultCollectionPolicies = authorizeService .getPoliciesActionFilter(context, collection, Constants.DEFAULT_ITEM_READ); + // If collection has defaultREAD policies, remove the item's READ policies. + if (replaceReadRPWithCollectionRP && defaultCollectionPolicies.size() > 0) { + authorizeService.removePoliciesActionFilter(context, item, Constants.READ); + } + // MUST have default policies if (defaultCollectionPolicies.size() < 1) { throw new SQLException("Collection " + collection.getID() diff --git a/dspace-api/src/main/java/org/dspace/content/service/ItemService.java b/dspace-api/src/main/java/org/dspace/content/service/ItemService.java index 8b93e953eb81..de7644af83fe 100644 --- a/dspace-api/src/main/java/org/dspace/content/service/ItemService.java +++ b/dspace-api/src/main/java/org/dspace/content/service/ItemService.java @@ -527,6 +527,28 @@ public void inheritCollectionDefaultPolicies(Context context, Item item, Collect public void adjustBundleBitstreamPolicies(Context context, Item item, Collection collection) throws SQLException, AuthorizeException; + /** + * Adjust the Bundle and Bitstream policies to reflect what have been defined + * during the submission/workflow. The temporary SUBMISSION and WORKFLOW + * policies are removed and the policies defined at the item and collection + * level are copied and inherited as appropriate. Custom selected Item policies + * are copied to the bundle/bitstream only if no explicit custom policies were + * already applied to the bundle/bitstream. Collection's policies are inherited + * if there are no other policies defined or if the append mode is defined by + * the configuration via the core.authorization.installitem.inheritance-read.append-mode property + * + * @param context DSpace context object + * @param item Item to adjust policies on + * @param collection Collection + * @param replaceReadRPWithCollectionRP if true, all read policies on the item are replaced (but only if the + * collection has a default read policy) + * @throws SQLException If database error + * @throws AuthorizeException If authorization error + */ + public void adjustBundleBitstreamPolicies(Context context, Item item, Collection collection, + boolean replaceReadRPWithCollectionRP) + throws SQLException, AuthorizeException; + /** * Adjust the Bitstream policies to reflect what have been defined * during the submission/workflow. The temporary SUBMISSION and WORKFLOW @@ -547,6 +569,29 @@ public void adjustBundleBitstreamPolicies(Context context, Item item, Collection public void adjustBitstreamPolicies(Context context, Item item, Collection collection, Bitstream bitstream) throws SQLException, AuthorizeException; + /** + * Adjust the Bitstream policies to reflect what have been defined + * during the submission/workflow. The temporary SUBMISSION and WORKFLOW + * policies are removed and the policies defined at the item and collection + * level are copied and inherited as appropriate. Custom selected Item policies + * are copied to the bitstream only if no explicit custom policies were + * already applied to the bitstream. Collection's policies are inherited + * if there are no other policies defined or if the append mode is defined by + * the configuration via the core.authorization.installitem.inheritance-read.append-mode property + * + * @param context DSpace context object + * @param item Item to adjust policies on + * @param collection Collection + * @param bitstream Bitstream to adjust policies on + * @param replaceReadRPWithCollectionRP If true, all read policies on the bitstream are replaced (but only if the + * collection has a default read policy) + * @throws SQLException If database error + * @throws AuthorizeException If authorization error + */ + public void adjustBitstreamPolicies(Context context, Item item, Collection collection, Bitstream bitstream, + boolean replaceReadRPWithCollectionRP) + throws SQLException, AuthorizeException; + /** * Adjust the Item's policies to reflect what have been defined during the @@ -565,6 +610,26 @@ public void adjustBitstreamPolicies(Context context, Item item, Collection colle public void adjustItemPolicies(Context context, Item item, Collection collection) throws SQLException, AuthorizeException; + /** + * Adjust the Item's policies to reflect what have been defined during the + * submission/workflow. The temporary SUBMISSION and WORKFLOW policies are + * removed and the default policies defined at the collection level are + * inherited as appropriate. Collection's policies are inherited if there are no + * other policies defined or if the append mode is defined by the configuration + * via the core.authorization.installitem.inheritance-read.append-mode property + * + * @param context DSpace context object + * @param item Item to adjust policies on + * @param collection Collection + * @param replaceReadRPWithCollectionRP If true, all read policies on the item are replaced (but only if the + * collection has a default read policy) + * @throws SQLException If database error + * @throws AuthorizeException If authorization error + */ + public void adjustItemPolicies(Context context, Item item, Collection collection, + boolean replaceReadRPWithCollectionRP) + throws SQLException, AuthorizeException; + /** * Moves the item from one collection to another one * @@ -810,24 +875,24 @@ public Iterator findByLastModifiedSince(Context context, Date last) int countWithdrawnItems(Context context) throws SQLException; /** - * finds all items for which the current user has editing rights - * @param context DSpace context object - * @param offset page offset - * @param limit page size limit - * @return list of items for which the current user has editing rights - * @throws SQLException - * @throws SearchServiceException - */ + * finds all items for which the current user has editing rights + * @param context DSpace context object + * @param offset page offset + * @param limit page size limit + * @return list of items for which the current user has editing rights + * @throws SQLException + * @throws SearchServiceException + */ public List findItemsWithEdit(Context context, int offset, int limit) throws SQLException, SearchServiceException; /** - * counts all items for which the current user has editing rights - * @param context DSpace context object - * @return list of items for which the current user has editing rights - * @throws SQLException - * @throws SearchServiceException - */ + * counts all items for which the current user has editing rights + * @param context DSpace context object + * @return list of items for which the current user has editing rights + * @throws SQLException + * @throws SearchServiceException + */ public int countItemsWithEdit(Context context) throws SQLException, SearchServiceException; /** From 82c9b6fc9baee9f4f5d8b4cc967b5d12b63cdd39 Mon Sep 17 00:00:00 2001 From: Christian Bethge Date: Tue, 8 Aug 2023 09:36:16 +0200 Subject: [PATCH 384/510] ingore unrecognized arguments on help --- dspace-api/src/main/java/org/dspace/scripts/DSpaceRunnable.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/scripts/DSpaceRunnable.java b/dspace-api/src/main/java/org/dspace/scripts/DSpaceRunnable.java index 7fb8567f8cd1..5e16fea5aecd 100644 --- a/dspace-api/src/main/java/org/dspace/scripts/DSpaceRunnable.java +++ b/dspace-api/src/main/java/org/dspace/scripts/DSpaceRunnable.java @@ -123,7 +123,7 @@ private StepResult parse(String[] args) throws ParseException { } private StepResult parseForHelp(String[] args) throws ParseException { - helpCommandLine = new DefaultParser().parse(getScriptConfiguration().getHelpOptions(), args); + helpCommandLine = new DefaultParser().parse(getScriptConfiguration().getHelpOptions(), args, true); if (helpCommandLine.getOptions() != null && helpCommandLine.getOptions().length > 0) { return StepResult.Exit; } From 88749f6c61559b98fe35de3b8f4346f5995d1ef0 Mon Sep 17 00:00:00 2001 From: Koen Pauwels Date: Fri, 4 Aug 2023 17:00:35 +0200 Subject: [PATCH 385/510] 103818 Extend ItemServiceTest#testMoveItemToCollectionWithMoreRestrictiveReadPolicy --- .../org/dspace/content/ItemServiceImpl.java | 16 ++++++++-- .../content/service/ItemServiceTest.java | 30 +++++++++++++++++-- 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java index ac38f0cca49c..ebea2aa5b820 100644 --- a/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java @@ -963,13 +963,18 @@ public void adjustBundleBitstreamPolicies(Context context, Item item, Collection } // TODO: should we also throw an exception if no DEFAULT_ITEM_READ? + boolean removeCurrentReadRPBitstream = + replaceReadRPWithCollectionRP && defaultCollectionBitstreamPolicies.size() > 0; + boolean removeCurrentReadRPBundle = + replaceReadRPWithCollectionRP && defaultCollectionBundlePolicies.size() > 0; + // remove all policies from bundles, add new ones // Remove bundles List bunds = item.getBundles(); for (Bundle mybundle : bunds) { - // If collection has default READ policies, remove the bitstream's READ policies. - if (replaceReadRPWithCollectionRP && defaultCollectionBitstreamPolicies.size() > 0) { - authorizeService.removePoliciesActionFilter(context, item, Constants.READ); + // If collection has default READ policies, remove the bundle's READ policies. + if (removeCurrentReadRPBundle) { + authorizeService.removePoliciesActionFilter(context, mybundle, Constants.READ); } // if come from InstallItem: remove all submission/workflow policies @@ -979,6 +984,11 @@ public void adjustBundleBitstreamPolicies(Context context, Item item, Collection addDefaultPoliciesNotInPlace(context, mybundle, defaultCollectionBundlePolicies); for (Bitstream bitstream : mybundle.getBitstreams()) { + // If collection has default READ policies, remove the bundle's READ policies. + if (removeCurrentReadRPBitstream) { + authorizeService.removePoliciesActionFilter(context, bitstream, Constants.READ); + } + // if come from InstallItem: remove all submission/workflow policies removeAllPoliciesAndAddDefault(context, bitstream, defaultItemPolicies, defaultCollectionBitstreamPolicies); diff --git a/dspace-api/src/test/java/org/dspace/content/service/ItemServiceTest.java b/dspace-api/src/test/java/org/dspace/content/service/ItemServiceTest.java index 1847a27c7f5a..18e0047599ce 100644 --- a/dspace-api/src/test/java/org/dspace/content/service/ItemServiceTest.java +++ b/dspace-api/src/test/java/org/dspace/content/service/ItemServiceTest.java @@ -39,6 +39,7 @@ import org.dspace.builder.RequestItemBuilder; import org.dspace.builder.ResourcePolicyBuilder; import org.dspace.content.Bitstream; +import org.dspace.content.Bundle; import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.EntityType; @@ -786,22 +787,47 @@ public void testMoveItemToCollectionWithMoreRestrictiveReadPolicy() throws Excep .createItem(context, permissive) .build(); - // Verify that the item has exactly one READ policy, for the anonymous group. + Bitstream bitstream = BitstreamBuilder.createBitstream(context, item, InputStream.nullInputStream()) + .build(); + + Bundle bundle = item.getBundles("ORIGINAL").get(0); + + // Verify that the item, bundle and bitstream each have exactly one READ policy, for the anonymous group. assertEquals( List.of(anonymous), authorizeService.getPoliciesActionFilter(context, item, Constants.READ) .stream().map(ResourcePolicy::getGroup).collect(Collectors.toList()) ); + assertEquals( + List.of(anonymous), + authorizeService.getPoliciesActionFilter(context, bundle, Constants.READ) + .stream().map(ResourcePolicy::getGroup).collect(Collectors.toList()) + ); + assertEquals( + List.of(anonymous), + authorizeService.getPoliciesActionFilter(context, bitstream, Constants.READ) + .stream().map(ResourcePolicy::getGroup).collect(Collectors.toList()) + ); // Move the item to the restrictive collection, making sure to inherit default policies. itemService.move(context, item, permissive, restrictive, true); - // Verify that the item has exactly one READ policy, but now for the admin group. + // Verify that the item, bundle and bitstream each have exactly one READ policy, but now for the admin group. assertEquals( List.of(admin), authorizeService.getPoliciesActionFilter(context, item, Constants.READ) .stream().map(ResourcePolicy::getGroup).collect(Collectors.toList()) ); + assertEquals( + List.of(anonymous), + authorizeService.getPoliciesActionFilter(context, bundle, Constants.READ) + .stream().map(ResourcePolicy::getGroup).collect(Collectors.toList()) + ); + assertEquals( + List.of(anonymous), + authorizeService.getPoliciesActionFilter(context, bitstream, Constants.READ) + .stream().map(ResourcePolicy::getGroup).collect(Collectors.toList()) + ); context.restoreAuthSystemState(); } From 8c76f491eedf12b3328f8279cb53ba3ac83cb40d Mon Sep 17 00:00:00 2001 From: Koen Pauwels Date: Tue, 8 Aug 2023 11:45:44 +0200 Subject: [PATCH 386/510] 104878 Fix error in ItemServiceTest related to inheriting collection policies upon item move --- .../content/service/ItemServiceTest.java | 85 +++++++++++++++++-- 1 file changed, 80 insertions(+), 5 deletions(-) diff --git a/dspace-api/src/test/java/org/dspace/content/service/ItemServiceTest.java b/dspace-api/src/test/java/org/dspace/content/service/ItemServiceTest.java index 18e0047599ce..16d78a8e3e45 100644 --- a/dspace-api/src/test/java/org/dspace/content/service/ItemServiceTest.java +++ b/dspace-api/src/test/java/org/dspace/content/service/ItemServiceTest.java @@ -761,10 +761,11 @@ public void testRemoveItemThatHasRequests() throws Exception { } @Test - public void testMoveItemToCollectionWithMoreRestrictiveReadPolicy() throws Exception { - /* Verify that, if we move an item from a collection with a permissive default READ policy - * to a collection with a restrictive default READ policy, - * that the item does not retain the original permissive READ policy. + public void testMoveItemToCollectionWithMoreRestrictiveItemReadPolicy() throws Exception { + /* Verify that, if we move an item from a collection with a permissive default item READ policy + * to a collection with a restrictive default item READ policy, + * that the item does not retain the original permissive item READ policy. + * However, its bundles and bitstreams do. */ context.turnOffAuthorisationSystem(); @@ -812,7 +813,7 @@ public void testMoveItemToCollectionWithMoreRestrictiveReadPolicy() throws Excep // Move the item to the restrictive collection, making sure to inherit default policies. itemService.move(context, item, permissive, restrictive, true); - // Verify that the item, bundle and bitstream each have exactly one READ policy, but now for the admin group. + // Verify that the item's read policy now only allows administrators. assertEquals( List.of(admin), authorizeService.getPoliciesActionFilter(context, item, Constants.READ) @@ -832,6 +833,80 @@ public void testMoveItemToCollectionWithMoreRestrictiveReadPolicy() throws Excep context.restoreAuthSystemState(); } + @Test + public void testMoveItemToCollectionWithMoreRestrictiveBitstreamReadPolicy() throws Exception { + /* Verify that, if we move an item from a collection with a permissive default bitstream READ policy + * to a collection with a restrictive default bitstream READ policy, + * that the item's bundles and bitstreams do not retain the original permissive READ policy. + * However, the item itself does retain the original policy. + */ + + context.turnOffAuthorisationSystem(); + + Group anonymous = groupService.findByName(context, Group.ANONYMOUS); + Group admin = groupService.findByName(context, Group.ADMIN); + + // Set up the two different collections: one permissive and one restrictive in its default READ policy. + Collection permissive = CollectionBuilder + .createCollection(context, community) + .build(); + Collection restrictive = CollectionBuilder + .createCollection(context, community) + .build(); + authorizeService.removePoliciesActionFilter(context, restrictive, Constants.DEFAULT_BITSTREAM_READ); + authorizeService.addPolicy(context, restrictive, Constants.DEFAULT_BITSTREAM_READ, admin); + + // Add an item to the permissive collection. + Item item = ItemBuilder + .createItem(context, permissive) + .build(); + + Bitstream bitstream = BitstreamBuilder.createBitstream(context, item, InputStream.nullInputStream()) + .build(); + + Bundle bundle = item.getBundles("ORIGINAL").get(0); + + // Verify that the item, bundle and bitstream each have exactly one READ policy, for the anonymous group. + assertEquals( + List.of(anonymous), + authorizeService.getPoliciesActionFilter(context, item, Constants.READ) + .stream().map(ResourcePolicy::getGroup).collect(Collectors.toList()) + ); + assertEquals( + List.of(anonymous), + authorizeService.getPoliciesActionFilter(context, bundle, Constants.READ) + .stream().map(ResourcePolicy::getGroup).collect(Collectors.toList()) + ); + assertEquals( + List.of(anonymous), + authorizeService.getPoliciesActionFilter(context, bitstream, Constants.READ) + .stream().map(ResourcePolicy::getGroup).collect(Collectors.toList()) + ); + + // Move the item to the restrictive collection, making sure to inherit default policies. + itemService.move(context, item, permissive, restrictive, true); + + // Verify that the bundle and bitstream's read policies now only allows administrators. + assertEquals( + List.of(anonymous), + authorizeService.getPoliciesActionFilter(context, item, Constants.READ) + .stream().map(ResourcePolicy::getGroup).collect(Collectors.toList()) + ); + assertEquals( + List.of(admin), + authorizeService.getPoliciesActionFilter(context, bundle, Constants.READ) + .stream().map(ResourcePolicy::getGroup).collect(Collectors.toList()) + ); + assertEquals( + List.of(admin), + authorizeService.getPoliciesActionFilter(context, bitstream, Constants.READ) + .stream().map(ResourcePolicy::getGroup).collect(Collectors.toList()) + ); + + context.restoreAuthSystemState(); + + } + private void assertMetadataValue(String authorQualifier, String contributorElement, String dcSchema, String value, String authority, int place, MetadataValue metadataValue) { assertThat(metadataValue.getValue(), equalTo(value)); From 0e9b482f784948350509b13df96bbd8737cd218a Mon Sep 17 00:00:00 2001 From: Koen Pauwels Date: Tue, 8 Aug 2023 12:56:10 +0200 Subject: [PATCH 387/510] 104878 Adjust ItemServiceTest to expect correct behavior of bundles when item is migrated --- .../org/dspace/content/service/ItemServiceTest.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/dspace-api/src/test/java/org/dspace/content/service/ItemServiceTest.java b/dspace-api/src/test/java/org/dspace/content/service/ItemServiceTest.java index 16d78a8e3e45..d2f4b6d85197 100644 --- a/dspace-api/src/test/java/org/dspace/content/service/ItemServiceTest.java +++ b/dspace-api/src/test/java/org/dspace/content/service/ItemServiceTest.java @@ -764,8 +764,8 @@ public void testRemoveItemThatHasRequests() throws Exception { public void testMoveItemToCollectionWithMoreRestrictiveItemReadPolicy() throws Exception { /* Verify that, if we move an item from a collection with a permissive default item READ policy * to a collection with a restrictive default item READ policy, - * that the item does not retain the original permissive item READ policy. - * However, its bundles and bitstreams do. + * that the item and its bundles do not retain the original permissive item READ policy. + * However, its bitstreams do. */ context.turnOffAuthorisationSystem(); @@ -820,7 +820,7 @@ public void testMoveItemToCollectionWithMoreRestrictiveItemReadPolicy() throws E .stream().map(ResourcePolicy::getGroup).collect(Collectors.toList()) ); assertEquals( - List.of(anonymous), + List.of(admin), authorizeService.getPoliciesActionFilter(context, bundle, Constants.READ) .stream().map(ResourcePolicy::getGroup).collect(Collectors.toList()) ); @@ -837,8 +837,8 @@ public void testMoveItemToCollectionWithMoreRestrictiveItemReadPolicy() throws E public void testMoveItemToCollectionWithMoreRestrictiveBitstreamReadPolicy() throws Exception { /* Verify that, if we move an item from a collection with a permissive default bitstream READ policy * to a collection with a restrictive default bitstream READ policy, - * that the item's bundles and bitstreams do not retain the original permissive READ policy. - * However, the item itself does retain the original policy. + * that the item's bitstreams do not retain the original permissive READ policy. + * However, the item itself and its bundles do retain the original policy. */ context.turnOffAuthorisationSystem(); @@ -893,7 +893,7 @@ public void testMoveItemToCollectionWithMoreRestrictiveBitstreamReadPolicy() thr .stream().map(ResourcePolicy::getGroup).collect(Collectors.toList()) ); assertEquals( - List.of(admin), + List.of(anonymous), authorizeService.getPoliciesActionFilter(context, bundle, Constants.READ) .stream().map(ResourcePolicy::getGroup).collect(Collectors.toList()) ); From 51d20fa7fd2960657b77d0fe5fe88fbce6289b60 Mon Sep 17 00:00:00 2001 From: Koen Pauwels Date: Tue, 8 Aug 2023 16:22:49 +0200 Subject: [PATCH 388/510] Fix failing IT in BulkAccessControlIT --- .../org/dspace/app/bulkaccesscontrol/BulkAccessControl.java | 2 +- 1 file changed, 1 insertion(+), 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 50e1022dbe37..7bef232f0450 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 @@ -464,7 +464,7 @@ private void setItemPolicies(Item item, BulkAccessControlInput accessControl) .forEach(accessCondition -> createResourcePolicy(item, accessCondition, itemAccessConditions.get(accessCondition.getName()))); - itemService.adjustItemPolicies(context, item, item.getOwningCollection()); + itemService.adjustItemPolicies(context, item, item.getOwningCollection(), false); } /** From 2e62fa3fd1f264aac0bb4a12953b6385211e5656 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Tue, 8 Aug 2023 11:04:28 -0400 Subject: [PATCH 389/510] Handle missing role. --- .../curate/XmlWorkflowCuratorServiceImpl.java | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/curate/XmlWorkflowCuratorServiceImpl.java b/dspace-api/src/main/java/org/dspace/curate/XmlWorkflowCuratorServiceImpl.java index 97537befd2f4..70a36f278ed1 100644 --- a/dspace-api/src/main/java/org/dspace/curate/XmlWorkflowCuratorServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/curate/XmlWorkflowCuratorServiceImpl.java @@ -31,6 +31,7 @@ import org.dspace.workflow.FlowStep; import org.dspace.workflow.Task; import org.dspace.workflow.TaskSet; +import org.dspace.xmlworkflow.Role; import org.dspace.xmlworkflow.RoleMembers; import org.dspace.xmlworkflow.WorkflowConfigurationException; import org.dspace.xmlworkflow.factory.XmlWorkflowFactory; @@ -276,12 +277,17 @@ protected List resolveContacts(Context c, List contacts, String.valueOf(wfi.getID()), e); return epList; } - RoleMembers roleMembers = step.getRole().getMembers(c, wfi); - for (EPerson ep : roleMembers.getEPersons()) { - epList.add(ep); - } - for (Group group : roleMembers.getGroups()) { - epList.addAll(group.getMembers()); + Role role = step.getRole(); + if (null != role) { + RoleMembers roleMembers = role.getMembers(c, wfi); + for (EPerson ep : roleMembers.getEPersons()) { + epList.add(ep); + } + for (Group group : roleMembers.getGroups()) { + epList.addAll(group.getMembers()); + } + } else { + epList.add(ePersonService.getSystemEPerson(c)); } } else if ("$colladmin".equals(contact)) { // special literal for collection administrators From 1f3ad993cc4d10694112227245be3de1ec7b3762 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 8 Aug 2023 16:43:12 -0500 Subject: [PATCH 390/510] Remove useless log.info --- .../src/main/java/org/dspace/content/BundleServiceImpl.java | 1 - 1 file changed, 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/content/BundleServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/BundleServiceImpl.java index 20c43e4bfc73..546d48d4306b 100644 --- a/dspace-api/src/main/java/org/dspace/content/BundleServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/BundleServiceImpl.java @@ -194,7 +194,6 @@ public void addBitstream(Context context, Bundle bundle, Bitstream bitstream) List defaultBitstreamReadGroups = authorizeService.getAuthorizedGroups(context, owningCollection, Constants.DEFAULT_BITSTREAM_READ); - log.info(defaultBitstreamReadGroups.size()); // If this collection is configured with a DEFAULT_BITSTREAM_READ group, overwrite the READ policy // inherited from the bundle with this policy. if (!defaultBitstreamReadGroups.isEmpty()) { From 6fbe4f4c0003dbb257deee79b2f6f57b4dc13b50 Mon Sep 17 00:00:00 2001 From: nwoodward Date: Wed, 9 Aug 2023 11:35:46 -0500 Subject: [PATCH 391/510] fix logical bug when checking if field is controlled authority --- .../src/main/java/org/dspace/app/bulkedit/MetadataImport.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImport.java b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImport.java index 4161bbb4d817..9044c723ff53 100644 --- a/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImport.java +++ b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImport.java @@ -1363,7 +1363,7 @@ private int displayChanges(List changes, boolean changed) { * is the field is defined as authority controlled */ private static boolean isAuthorityControlledField(String md) { - String mdf = StringUtils.substringAfter(md, ":"); + String mdf = md.contains(":") ? StringUtils.substringAfter(md, ":") : md; mdf = StringUtils.substringBefore(mdf, "["); return authorityControlled.contains(mdf); } From 08c650921d7ce5210906db846ff29a053c2155a7 Mon Sep 17 00:00:00 2001 From: aroman-arvo Date: Fri, 11 Aug 2023 08:47:31 +0200 Subject: [PATCH 392/510] 3331 - remove the --optimize feature of 'dspace stats-util' --- .../dspace/statistics/SolrLoggerServiceImpl.java | 16 ---------------- .../statistics/service/SolrLoggerService.java | 6 ------ .../dspace/statistics/util/StatisticsClient.java | 3 --- 3 files changed, 25 deletions(-) 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 19c79af34dcc..97585f5a47cb 100644 --- a/dspace-api/src/main/java/org/dspace/statistics/SolrLoggerServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/statistics/SolrLoggerServiceImpl.java @@ -1203,22 +1203,6 @@ public String getIgnoreSpiderIPs() { } - @Override - public void optimizeSOLR() { - try { - long start = System.currentTimeMillis(); - System.out.println("SOLR Optimize -- Process Started:" + start); - solr.optimize(); - long finish = System.currentTimeMillis(); - System.out.println("SOLR Optimize -- Process Finished:" + finish); - System.out.println("SOLR Optimize -- Total time taken:" + (finish - start) + " (ms)."); - } catch (SolrServerException sse) { - System.err.println(sse.getMessage()); - } catch (IOException ioe) { - System.err.println(ioe.getMessage()); - } - } - @Override public void shardSolrIndex() throws IOException, SolrServerException { if (!(solr instanceof HttpSolrClient)) { diff --git a/dspace-api/src/main/java/org/dspace/statistics/service/SolrLoggerService.java b/dspace-api/src/main/java/org/dspace/statistics/service/SolrLoggerService.java index 3728318625e3..61b2bb6013de 100644 --- a/dspace-api/src/main/java/org/dspace/statistics/service/SolrLoggerService.java +++ b/dspace-api/src/main/java/org/dspace/statistics/service/SolrLoggerService.java @@ -266,12 +266,6 @@ public QueryResponse query(String query, String filterQuery, */ public String getIgnoreSpiderIPs(); - /** - * Maintenance to keep a SOLR index efficient. - * Note: This might take a long time. - */ - public void optimizeSOLR(); - public void shardSolrIndex() throws IOException, SolrServerException; public void reindexBitstreamHits(boolean removeDeletedBitstreams) throws Exception; diff --git a/dspace-api/src/main/java/org/dspace/statistics/util/StatisticsClient.java b/dspace-api/src/main/java/org/dspace/statistics/util/StatisticsClient.java index e45ce163ed77..319fe437d648 100644 --- a/dspace-api/src/main/java/org/dspace/statistics/util/StatisticsClient.java +++ b/dspace-api/src/main/java/org/dspace/statistics/util/StatisticsClient.java @@ -67,7 +67,6 @@ public static void main(String[] args) throws Exception { options.addOption("m", "mark-spiders", false, "Update isBot Flag in Solr"); options.addOption("f", "delete-spiders-by-flag", false, "Delete Spiders in Solr By isBot Flag"); options.addOption("i", "delete-spiders-by-ip", false, "Delete Spiders in Solr By IP Address"); - options.addOption("o", "optimize", false, "Run maintenance on the SOLR index"); options.addOption("b", "reindex-bitstreams", false, "Reindex the bitstreams to ensure we have the bundle name"); options.addOption("e", "export", false, "Export SOLR view statistics data to usage-statistics-intermediate-format"); @@ -93,8 +92,6 @@ public static void main(String[] args) throws Exception { solrLoggerService.deleteRobotsByIsBotFlag(); } else if (line.hasOption('i')) { solrLoggerService.deleteRobotsByIP(); - } else if (line.hasOption('o')) { - solrLoggerService.optimizeSOLR(); } else if (line.hasOption('b')) { solrLoggerService.reindexBitstreamHits(line.hasOption('r')); } else if (line.hasOption('e')) { From 22e209322c61bc1c7f940a1b686b87d90b5fd433 Mon Sep 17 00:00:00 2001 From: Nona Luypaert Date: Thu, 17 Aug 2023 16:43:21 +0200 Subject: [PATCH 393/510] 103837: Refactor GA config to list bundles --- .../google/GoogleAsyncEventListener.java | 30 +++++----- .../google/GoogleAsyncEventListenerIT.java | 56 ++++++++++++------- dspace/config/dspace.cfg | 12 ++-- 3 files changed, 60 insertions(+), 38 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/google/GoogleAsyncEventListener.java b/dspace-api/src/main/java/org/dspace/google/GoogleAsyncEventListener.java index e84d9f8591f2..cb9a120fd08f 100644 --- a/dspace-api/src/main/java/org/dspace/google/GoogleAsyncEventListener.java +++ b/dspace-api/src/main/java/org/dspace/google/GoogleAsyncEventListener.java @@ -23,6 +23,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.dspace.content.Bitstream; +import org.dspace.content.Bundle; import org.dspace.content.factory.ContentServiceFactory; import org.dspace.core.Constants; import org.dspace.core.Context; @@ -176,26 +177,27 @@ private String getDocumentPath(HttpServletRequest request) { * Verifies if the usage event is a content bitstream view event, by checking if:

    *
  • the usage event is a view event
  • *
  • the object of the usage event is a bitstream
  • - *
  • the bitstream belongs to the ORIGINAL bundle
- * This last one can be skipped if 'google-analytics.exclude-non-content-bitstreams' is set to false. - * This will make it so the bundle name is completely ignored when sending events. + *
  • the bitstream belongs to one of the configured bundles (fallback: ORIGINAL bundle)
  • */ private boolean isContentBitstream(UsageEvent usageEvent) { // check if event is a VIEW event and object is a Bitstream if (usageEvent.getAction() == UsageEvent.Action.VIEW || usageEvent.getObject().getType() == Constants.BITSTREAM) { - // check if config is set to true - if (configurationService.getBooleanProperty("google-analytics.exclude-non-content-bitstreams")) { - try { - // check if bitstream belongs to the ORIGINAL bundle - return ((Bitstream) usageEvent.getObject()) - .getBundles().stream() - .anyMatch(bundle -> bundle.getName().equals(Constants.CONTENT_BUNDLE_NAME)); - } catch (SQLException e) { - throw new RuntimeException(e.getMessage(), e); - } + // check if bitstream belongs to a configured bundle + List allowedBundles = List.of(configurationService + .getArrayProperty("google-analytics.bundles", new String[]{Constants.CONTENT_BUNDLE_NAME})); + if (allowedBundles.contains("none")) { + // GA events for bitstream views were turned off in config + return false; } - return true; + List bitstreamBundles; + try { + bitstreamBundles = ((Bitstream) usageEvent.getObject()) + .getBundles().stream().map(Bundle::getName).collect(Collectors.toList()); + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + return allowedBundles.stream().anyMatch(bitstreamBundles::contains); } return false; } diff --git a/dspace-server-webapp/src/test/java/org/dspace/google/GoogleAsyncEventListenerIT.java b/dspace-server-webapp/src/test/java/org/dspace/google/GoogleAsyncEventListenerIT.java index e43e9fd82035..17df839ebf1f 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/google/GoogleAsyncEventListenerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/google/GoogleAsyncEventListenerIT.java @@ -245,17 +245,12 @@ public void testOnBitstreamContentDownloadWithTooManyEvents() throws Exception { } @Test - public void testOnBitstreamContentDownloadExcludeNonContentBitstreams() throws Exception { - configurationService.setProperty("google-analytics.exclude-non-content-bitstreams", true); - + public void testOnBitstreamContentDownloadDefaultBundleConfig() throws Exception { context.turnOffAuthorisationSystem(); Bundle licenseBundle = BundleBuilder.createBundle(context, item) .withName(Constants.LICENSE_BUNDLE_NAME).build(); Bitstream license = BitstreamBuilder.createBitstream(context, licenseBundle, toInputStream("License", defaultCharset())).build(); - Bundle thumbnailBundle = BundleBuilder.createBundle(context, item).withName("THUMBNAIL").build(); - Bitstream thumbnail = BitstreamBuilder.createBitstream(context, thumbnailBundle, - toInputStream("Thumbnail", defaultCharset())).build(); context.restoreAuthSystemState(); assertThat(getStoredEventsAsList(), empty()); @@ -264,14 +259,14 @@ public void testOnBitstreamContentDownloadExcludeNonContentBitstreams() throws E downloadBitstreamContent("Postman", "123456", "REF"); downloadContent("Chrome", "ABCDEFG", "REF-1", license); - downloadContent("Chrome", "987654", "REF-2", thumbnail); assertThat(getStoredEventsAsList(), hasSize(1)); List storedEvents = getStoredEventsAsList(); assertThat(storedEvents, contains( - event("123456", "127.0.0.1", "Postman", "REF", bitstreamUrl, "Test item"))); + event("123456", "127.0.0.1", "Postman", "REF", bitstreamUrl, "Test item")) + ); googleAsyncEventListener.sendCollectedEvents(); @@ -284,14 +279,14 @@ public void testOnBitstreamContentDownloadExcludeNonContentBitstreams() throws E } @Test - public void testOnBitstreamContentDownloadIncludeNonContentBitstreams() throws Exception { - configurationService.setProperty("google-analytics.exclude-non-content-bitstreams", false); + public void testOnBitstreamContentDownloadMultipleBundleConfig() throws Exception { + configurationService.setProperty("google-analytics.bundles", + List.of(Constants.DEFAULT_BUNDLE_NAME, "CONTENT")); context.turnOffAuthorisationSystem(); - Bundle licenseBundle = BundleBuilder.createBundle(context, item) - .withName(Constants.LICENSE_BUNDLE_NAME).build(); - Bitstream license = BitstreamBuilder.createBitstream(context, licenseBundle, - toInputStream("License", defaultCharset())).build(); + Bundle contentBundle = BundleBuilder.createBundle(context, item).withName("CONTENT").build(); + Bitstream content = BitstreamBuilder.createBitstream(context, contentBundle, + toInputStream("Test Content", defaultCharset())).build(); Bundle thumbnailBundle = BundleBuilder.createBundle(context, item).withName("THUMBNAIL").build(); Bitstream thumbnail = BitstreamBuilder.createBitstream(context, thumbnailBundle, toInputStream("Thumbnail", defaultCharset())).build(); @@ -300,21 +295,20 @@ public void testOnBitstreamContentDownloadIncludeNonContentBitstreams() throws E assertThat(getStoredEventsAsList(), empty()); String bitstreamUrl = "/api/core/bitstreams/" + bitstream.getID() + "/content"; - String licenseUrl = "/api/core/bitstreams/" + license.getID() + "/content"; - String thumbnailUrl = "/api/core/bitstreams/" + thumbnail.getID() + "/content"; + String contentUrl = "/api/core/bitstreams/" + content.getID() + "/content"; downloadBitstreamContent("Postman", "123456", "REF"); - downloadContent("Chrome", "ABCDEFG", "REF-1", license); + downloadContent("Chrome", "ABCDEFG", "REF-1", content); downloadContent("Chrome", "987654", "REF-2", thumbnail); - assertThat(getStoredEventsAsList(), hasSize(3)); + assertThat(getStoredEventsAsList(), hasSize(2)); List storedEvents = getStoredEventsAsList(); assertThat(storedEvents, contains( event("123456", "127.0.0.1", "Postman", "REF", bitstreamUrl, "Test item"), - event("ABCDEFG", "127.0.0.1", "Chrome", "REF-1", licenseUrl, "Test item"), - event("987654", "127.0.0.1", "Chrome", "REF-2", thumbnailUrl, "Test item"))); + event("ABCDEFG", "127.0.0.1", "Chrome", "REF-1", contentUrl, "Test item") + )); googleAsyncEventListener.sendCollectedEvents(); @@ -326,6 +320,28 @@ public void testOnBitstreamContentDownloadIncludeNonContentBitstreams() throws E verifyNoMoreInteractions(firstGaClientMock, secondGaClientMock); } + @Test + public void testOnBitstreamContentDownloadNoneBundleConfig() throws Exception { + configurationService.setProperty("google-analytics.bundles", "none"); + + context.turnOffAuthorisationSystem(); + Bundle contentBundle = BundleBuilder.createBundle(context, item).withName("CONTENT").build(); + Bitstream content = BitstreamBuilder.createBitstream(context, contentBundle, + toInputStream("Test Content", defaultCharset())).build(); + Bundle thumbnailBundle = BundleBuilder.createBundle(context, item).withName("THUMBNAIL").build(); + Bitstream thumbnail = BitstreamBuilder.createBitstream(context, thumbnailBundle, + toInputStream("Thumbnail", defaultCharset())).build(); + context.restoreAuthSystemState(); + + assertThat(getStoredEventsAsList(), empty()); + + downloadBitstreamContent("Postman", "123456", "REF"); + downloadContent("Chrome", "ABCDEFG", "REF-1", content); + downloadContent("Chrome", "987654", "REF-2", thumbnail); + + assertThat(getStoredEventsAsList(), empty()); + } + @SuppressWarnings("unchecked") private List getStoredEventsAsList() { List events = new ArrayList<>(); diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 89c8da92558c..be11cbf03303 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -1537,10 +1537,14 @@ log.report.dir = ${dspace.dir}/log # For more details see https://developers.google.com/analytics/devguides/collection/protocol/ga4 # google.analytics.api-secret = -# Ensures only views of bitstreams in the 'ORIGINAL' bundle result in a GA4 event. -# Setting this to false may cause inflated bitstream view numbers, since requesting -# bitstreams in the 'THUMBNAIL' and 'LICENSE' bundles, will also result in GA4 events. -google-analytics.exclude-non-content-bitstreams=true +# Ensures only views of bitstreams in configured bundles result in a GA4 event. +# Config can contain multiple bundles for which the bitstream views will result in GA4 events, eg: +# google-analytics.bundles = ORIGINAL, CONTENT +# If config is not set or empty, the default fallback is Constants#CONTENT_BUNDLE_NAME bundle ('ORIGINAL'). +# If config contains 'LICENSE' or 'THUMBNAIL' bundles, it may cause inflated bitstream view numbers. +# Set config to 'none' to disable GA4 bitstream events, eg: +# google-analytics.bundles = none +google-analytics.bundles = ORIGINAL #################################################################### #---------------------------------------------------------------# From ca8abddff1230e581501b482623966e64016d609 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Mon, 21 Aug 2023 23:35:23 +0200 Subject: [PATCH 394/510] README.md: Fix typo --- dspace-server-webapp/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-server-webapp/README.md b/dspace-server-webapp/README.md index 8d3853e8ccc7..d418124ea171 100644 --- a/dspace-server-webapp/README.md +++ b/dspace-server-webapp/README.md @@ -10,7 +10,7 @@ This webapp uses the following technologies: We don't use Spring Data REST as we haven't a spring data layer and we want to provide clear separation between the persistence representation and the REST representation ## How to contribute -Check the infomation available on the DSpace Official Wiki page for the [DSpace 7 Working Group](https://wiki.duraspace.org/display/DSPACE/DSpace+7+UI+Working+Group) +Check the information available on the DSpace Official Wiki page for the [DSpace 7 Working Group](https://wiki.duraspace.org/display/DSPACE/DSpace+7+UI+Working+Group) [DSpace 7 REST: Coding DSpace Objects](https://wiki.duraspace.org/display/DSPACE/DSpace+7+REST%3A+Coding+DSpace+Objects) From 940183411ea6ea87aa5de9193794c553178637d4 Mon Sep 17 00:00:00 2001 From: Hrafn Malmquist Date: Tue, 22 Aug 2023 21:24:24 +0100 Subject: [PATCH 395/510] Bump up versions of buildnumber-maven-plugin & build-helper-maven-plugin. add configuration for SCM failure (#9016) --- Dockerfile | 2 +- dspace-api/pom.xml | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 664cba89faea..dd633def28d9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -51,7 +51,7 @@ RUN ant init_installation update_configs update_code update_webapps FROM tomcat:9-jdk${JDK_VERSION} # NOTE: DSPACE_INSTALL must align with the "dspace.dir" default configuration. ENV DSPACE_INSTALL=/dspace -# Copy the /dspace directory from 'ant_build' containger to /dspace in this container +# Copy the /dspace directory from 'ant_build' container to /dspace in this container COPY --from=ant_build /dspace $DSPACE_INSTALL # Expose Tomcat port and AJP port EXPOSE 8080 8009 diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index ee8c21cb64bd..45aee2227ac9 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -102,7 +102,7 @@ org.codehaus.mojo build-helper-maven-plugin - 3.0.0 + 3.4.0 validate @@ -116,7 +116,10 @@ org.codehaus.mojo buildnumber-maven-plugin - 1.4 + 3.2.0 + + UNKNOWN_REVISION + validate From 86285d78aa0844b9811dcebdefa897ceeb944226 Mon Sep 17 00:00:00 2001 From: Christian Bethge Date: Wed, 23 Aug 2023 10:33:51 +0200 Subject: [PATCH 396/510] add DSpaceSkipUnknownArgumentsParser as parser to ignore/skip unknown arguments in cli by help, fix not necessary ParseException in help --- .../cli/DSpaceSkipUnknownArgumentsParser.java | 77 +++++++++++++++++++ .../org/dspace/scripts/DSpaceRunnable.java | 3 +- 2 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 dspace-api/src/main/java/org/dspace/cli/DSpaceSkipUnknownArgumentsParser.java diff --git a/dspace-api/src/main/java/org/dspace/cli/DSpaceSkipUnknownArgumentsParser.java b/dspace-api/src/main/java/org/dspace/cli/DSpaceSkipUnknownArgumentsParser.java new file mode 100644 index 000000000000..afd74a588d17 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/cli/DSpaceSkipUnknownArgumentsParser.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.cli; + +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.DefaultParser; +import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; + +/** + * Extended version of the DefaultParser. This parser skip/ignore unknown arguments. + */ +public class DSpaceSkipUnknownArgumentsParser extends DefaultParser { + + + @Override + public CommandLine parse(Options options, String[] arguments) throws ParseException { + return super.parse(options, getOnlyKnownArguments(options, arguments)); + } + + @Override + public CommandLine parse(Options options, String[] arguments, Properties properties) throws ParseException { + return super.parse(options, getOnlyKnownArguments(options, arguments), properties); + } + + /** + * Parse the arguments according to the specified options and properties. + * @param options the specified Options + * @param arguments the command line arguments + * @param stopAtNonOption can be ignored - an unrecognized argument is ignored, an unrecognized argument doesn't + * stop the parsing and doesn't trigger a ParseException + * + * @return the list of atomic option and value tokens + * @throws ParseException if there are any problems encountered while parsing the command line tokens. + */ + @Override + public CommandLine parse(Options options, String[] arguments, boolean stopAtNonOption) throws ParseException { + return super.parse(options, getOnlyKnownArguments(options, arguments), stopAtNonOption); + } + + /** + * Parse the arguments according to the specified options and properties. + * @param options the specified Options + * @param arguments the command line arguments + * @param properties command line option name-value pairs + * @param stopAtNonOption can be ignored - an unrecognized argument is ignored, an unrecognized argument doesn't + * stop the parsing and doesn't trigger a ParseException + * + * @return the list of atomic option and value tokens + * @throws ParseException if there are any problems encountered while parsing the command line tokens. + */ + @Override + public CommandLine parse(Options options, String[] arguments, Properties properties, boolean stopAtNonOption) + throws ParseException { + return super.parse(options, getOnlyKnownArguments(options, arguments), properties, stopAtNonOption); + } + + + private String[] getOnlyKnownArguments(Options options, String[] arguments) { + List knownArguments = new ArrayList<>(); + for (String arg : arguments) { + if (options.hasOption(arg)) { + knownArguments.add(arg); + } + } + return knownArguments.toArray(new String[0]); + } +} diff --git a/dspace-api/src/main/java/org/dspace/scripts/DSpaceRunnable.java b/dspace-api/src/main/java/org/dspace/scripts/DSpaceRunnable.java index 5e16fea5aecd..2ea0a52d6e34 100644 --- a/dspace-api/src/main/java/org/dspace/scripts/DSpaceRunnable.java +++ b/dspace-api/src/main/java/org/dspace/scripts/DSpaceRunnable.java @@ -18,6 +18,7 @@ import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; import org.apache.commons.lang3.StringUtils; +import org.dspace.cli.DSpaceSkipUnknownArgumentsParser; import org.dspace.eperson.EPerson; import org.dspace.scripts.configuration.ScriptConfiguration; import org.dspace.scripts.handler.DSpaceRunnableHandler; @@ -123,7 +124,7 @@ private StepResult parse(String[] args) throws ParseException { } private StepResult parseForHelp(String[] args) throws ParseException { - helpCommandLine = new DefaultParser().parse(getScriptConfiguration().getHelpOptions(), args, true); + helpCommandLine = new DSpaceSkipUnknownArgumentsParser().parse(getScriptConfiguration().getHelpOptions(), args); if (helpCommandLine.getOptions() != null && helpCommandLine.getOptions().length > 0) { return StepResult.Exit; } From 064e2caa37dfa283c3c08dee0e7321e36073bfa2 Mon Sep 17 00:00:00 2001 From: Christian Bethge Date: Wed, 23 Aug 2023 10:41:44 +0200 Subject: [PATCH 397/510] remove not necessary else --- .../main/java/org/dspace/app/launcher/ScriptLauncher.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 bcb61a48ee76..89a416bfa883 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 @@ -147,11 +147,11 @@ private static int executeScript(String[] args, DSpaceRunnableHandler dSpaceRunn DSpaceRunnable script) { try { StepResult result = script.initialize(args, dSpaceRunnableHandler, null); + // check the StepResult, only run the script if the result is Continue; + // otherwise - for example the script is started with the help as argument, nothing is to do if (StepResult.Continue.equals(result)) { - // only run the script, if the normal initialize is successful + // runs the script, the normal initialization is successful script.run(); - } else { - // otherwise - for example the script is started with the help argument } return 0; } catch (ParseException e) { From c23bc8a33867759919fd8fce0c28118a8630427e Mon Sep 17 00:00:00 2001 From: Martin Walk Date: Thu, 3 Aug 2023 13:39:43 +0200 Subject: [PATCH 398/510] Fix #8963: Remove deletion constraint from Groomer (cherry picked from commit e07763b021c009e421b97dc5b6404c284c9de168) --- .../main/java/org/dspace/eperson/Groomer.java | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/eperson/Groomer.java b/dspace-api/src/main/java/org/dspace/eperson/Groomer.java index 2a828cdc12b4..5485bb1d0ca9 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/Groomer.java +++ b/dspace-api/src/main/java/org/dspace/eperson/Groomer.java @@ -141,20 +141,10 @@ private static void aging(CommandLine command) throws SQLException { System.out.println(); if (delete) { - List whyNot = ePersonService.getDeleteConstraints(myContext, account); - if (!whyNot.isEmpty()) { - System.out.print("\tCannot be deleted; referenced in"); - for (String table : whyNot) { - System.out.print(' '); - System.out.print(table); - } - System.out.println(); - } else { - try { - ePersonService.delete(myContext, account); - } catch (AuthorizeException | IOException ex) { - System.err.println(ex.getMessage()); - } + try { + ePersonService.delete(myContext, account); + } catch (AuthorizeException | IOException ex) { + System.err.println(ex.getMessage()); } } } From b1d3471995ce7687b4ca256b90d6c1487e9719dd Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 23 Aug 2023 16:51:26 -0500 Subject: [PATCH 399/510] Enable new skip merge commit feature --- .github/workflows/port_merged_pull_request.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/port_merged_pull_request.yml b/.github/workflows/port_merged_pull_request.yml index 50faf3f88679..109835d14d3c 100644 --- a/.github/workflows/port_merged_pull_request.yml +++ b/.github/workflows/port_merged_pull_request.yml @@ -39,6 +39,8 @@ jobs: # Copy all labels from original PR to (newly created) port PR # NOTE: The labels matching 'label_pattern' are automatically excluded copy_labels_pattern: '.*' + # Skip any merge commits in the ported PR. This means only non-merge commits are cherry-picked to the new PR + merge_commits: 'skip' # Use a personal access token (PAT) to create PR as 'dspace-bot' user. # A PAT is required in order for the new PR to trigger its own actions (for CI checks) github_token: ${{ secrets.PR_PORT_TOKEN }} \ No newline at end of file From eae4463eaa5916bd9b20f4e4132398aceeba1f02 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Thu, 24 Aug 2023 13:03:26 -0400 Subject: [PATCH 400/510] Avoid double slashes in sitemap paths. --- .../org/dspace/app/sitemap/GenerateSitemaps.java | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 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 d65447d311ee..400b5ecb87cb 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 @@ -189,7 +189,10 @@ public static void deleteSitemaps() throws IOException { */ public static void generateSitemaps(boolean makeHTMLMap, boolean makeSitemapOrg) throws SQLException, IOException { String uiURLStem = configurationService.getProperty("dspace.ui.url"); - String sitemapStem = uiURLStem + "/sitemap"; + if (!uiURLStem.endsWith("/")) { + uiURLStem = uiURLStem + '/'; + } + String sitemapStem = uiURLStem + "sitemap"; File outputDir = new File(configurationService.getProperty("sitemap.dir")); if (!outputDir.exists() && !outputDir.mkdir()) { @@ -212,7 +215,7 @@ public static void generateSitemaps(boolean makeHTMLMap, boolean makeSitemapOrg) List comms = communityService.findAll(c); for (Community comm : comms) { - String url = uiURLStem + "/communities/" + comm.getID(); + String url = uiURLStem + "communities/" + comm.getID(); if (makeHTMLMap) { html.addURL(url, null); @@ -227,7 +230,7 @@ public static void generateSitemaps(boolean makeHTMLMap, boolean makeSitemapOrg) List colls = collectionService.findAll(c); for (Collection coll : colls) { - String url = uiURLStem + "/collections/" + coll.getID(); + String url = uiURLStem + "collections/" + coll.getID(); if (makeHTMLMap) { html.addURL(url, null); @@ -259,11 +262,11 @@ public static void generateSitemaps(boolean makeHTMLMap, boolean makeSitemapOrg) && StringUtils.isNotBlank(discoverResult.getSearchDocument( discoverResult.getIndexableObjects().get(0)).get(0).getSearchFieldValues("entityType").get(0)) ) { - url = uiURLStem + "/entities/" + StringUtils.lowerCase(discoverResult.getSearchDocument( + url = uiURLStem + "entities/" + StringUtils.lowerCase(discoverResult.getSearchDocument( discoverResult.getIndexableObjects().get(0)) .get(0).getSearchFieldValues("entityType").get(0)) + "/" + i.getID(); } else { - url = uiURLStem + "/items/" + i.getID(); + url = uiURLStem + "items/" + i.getID(); } Date lastMod = i.getLastModified(); From 1160341cb2a2c163c8fddc04ddab46de9041e1b8 Mon Sep 17 00:00:00 2001 From: Christian Bethge Date: Tue, 29 Aug 2023 15:28:21 +0200 Subject: [PATCH 401/510] 9043 use Templates for compiled XSLT instead of Transformer - use Templates are thread-safe and NOT Transformer --- dspace-oai/pom.xml | 2 +- .../services/impl/resources/DSpaceResourceResolver.java | 7 +++---- .../dspace/xoai/tests/integration/xoai/PipelineTest.java | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/dspace-oai/pom.xml b/dspace-oai/pom.xml index 808940eb7b88..59cee2829360 100644 --- a/dspace-oai/pom.xml +++ b/dspace-oai/pom.xml @@ -15,7 +15,7 @@ ${basedir}/.. - 3.3.0 + 3.3.1-SNAPSHOT 5.87.0.RELEASE diff --git a/dspace-oai/src/main/java/org/dspace/xoai/services/impl/resources/DSpaceResourceResolver.java b/dspace-oai/src/main/java/org/dspace/xoai/services/impl/resources/DSpaceResourceResolver.java index e67e9c56bd7a..83c4486f7134 100644 --- a/dspace-oai/src/main/java/org/dspace/xoai/services/impl/resources/DSpaceResourceResolver.java +++ b/dspace-oai/src/main/java/org/dspace/xoai/services/impl/resources/DSpaceResourceResolver.java @@ -12,7 +12,7 @@ import java.io.IOException; import java.io.InputStream; import javax.xml.transform.Source; -import javax.xml.transform.Transformer; +import javax.xml.transform.Templates; import javax.xml.transform.TransformerConfigurationException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.stream.StreamSource; @@ -40,8 +40,7 @@ public InputStream getResource(String path) throws IOException { } @Override - public Transformer getTransformer(String path) throws IOException, - TransformerConfigurationException { + public Templates getTemplates(String path) throws IOException, TransformerConfigurationException { // construct a Source that reads from an InputStream Source mySrc = new StreamSource(getResource(path)); // specify a system ID (the path to the XSLT-file on the filesystem) @@ -49,6 +48,6 @@ public Transformer getTransformer(String path) throws IOException, // XSLT-files (like ) String systemId = basePath + "/" + path; mySrc.setSystemId(systemId); - return transformerFactory.newTransformer(mySrc); + return transformerFactory.newTemplates(mySrc); } } diff --git a/dspace-oai/src/test/java/org/dspace/xoai/tests/integration/xoai/PipelineTest.java b/dspace-oai/src/test/java/org/dspace/xoai/tests/integration/xoai/PipelineTest.java index de76c992458c..0f48824159c2 100644 --- a/dspace-oai/src/test/java/org/dspace/xoai/tests/integration/xoai/PipelineTest.java +++ b/dspace-oai/src/test/java/org/dspace/xoai/tests/integration/xoai/PipelineTest.java @@ -29,7 +29,7 @@ public void pipelineTest() throws Exception { InputStream input = PipelineTest.class.getClassLoader().getResourceAsStream("item.xml"); InputStream xslt = PipelineTest.class.getClassLoader().getResourceAsStream("oai_dc.xsl"); String output = FileUtils.readAllText(new XSLPipeline(input, true) - .apply(factory.newTransformer(new StreamSource(xslt))) + .apply(factory.newTemplates(new StreamSource(xslt))) .getTransformed()); assertThat(output, oai_dc().withXPath("/oai_dc:dc/dc:title", equalTo("Teste"))); From 2bcc0b38a9436b0abc4c54e419f0fa6ae194269c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 Sep 2023 22:40:56 +0000 Subject: [PATCH 402/510] Bump org.eclipse.jetty:jetty-xml Bumps [org.eclipse.jetty:jetty-xml](https://github.com/eclipse/jetty.project) from 9.4.51.v20230217 to 9.4.52.v20230823. - [Release notes](https://github.com/eclipse/jetty.project/releases) - [Commits](https://github.com/eclipse/jetty.project/compare/jetty-9.4.51.v20230217...jetty-9.4.52.v20230823) --- updated-dependencies: - dependency-name: org.eclipse.jetty:jetty-xml dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 7822d4310998..aeb15fbefa8e 100644 --- a/pom.xml +++ b/pom.xml @@ -37,7 +37,7 @@ 2.3.8 1.1.1 - 9.4.51.v20230217 + 9.4.52.v20230823 2.20.0 2.0.29 1.19.0 From 997057f8e4c3b48092e41534dd99535b109b214a Mon Sep 17 00:00:00 2001 From: Nona Luypaert Date: Wed, 6 Sep 2023 19:48:55 +0200 Subject: [PATCH 403/510] 103837: Fix isContentBitstream() in GoogleAsyncEventListener --- .../main/java/org/dspace/google/GoogleAsyncEventListener.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/google/GoogleAsyncEventListener.java b/dspace-api/src/main/java/org/dspace/google/GoogleAsyncEventListener.java index cb9a120fd08f..c1c59acf4a63 100644 --- a/dspace-api/src/main/java/org/dspace/google/GoogleAsyncEventListener.java +++ b/dspace-api/src/main/java/org/dspace/google/GoogleAsyncEventListener.java @@ -182,7 +182,7 @@ private String getDocumentPath(HttpServletRequest request) { private boolean isContentBitstream(UsageEvent usageEvent) { // check if event is a VIEW event and object is a Bitstream if (usageEvent.getAction() == UsageEvent.Action.VIEW - || usageEvent.getObject().getType() == Constants.BITSTREAM) { + && usageEvent.getObject().getType() == Constants.BITSTREAM) { // check if bitstream belongs to a configured bundle List allowedBundles = List.of(configurationService .getArrayProperty("google-analytics.bundles", new String[]{Constants.CONTENT_BUNDLE_NAME})); From 934ebc890ea45e9f34471dd9f27c9d45baaecef3 Mon Sep 17 00:00:00 2001 From: Toni Prieto Date: Mon, 10 Jul 2023 11:29:33 +0200 Subject: [PATCH 404/510] Add a configuration key to disable hierarchical browse indexes (cherry picked from commit e55bc87c1aee8806befcd9dede575b258dfdfa9c) --- .../content/authority/ChoiceAuthorityServiceImpl.java | 10 ++++++++++ dspace/config/dspace.cfg | 5 +++++ 2 files changed, 15 insertions(+) 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 4cac1da31490..f2bc4f0be0f5 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 @@ -17,6 +17,7 @@ import java.util.Set; import java.util.stream.Collectors; +import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; import org.dspace.app.util.DCInput; @@ -557,6 +558,15 @@ public DSpaceControlledVocabularyIndex getVocabularyIndex(String nameVocab) { init(); ChoiceAuthority source = this.getChoiceAuthorityByAuthorityName(nameVocab); if (source != null && source instanceof DSpaceControlledVocabulary) { + + // First, check if this vocabulary index is disabled + String[] vocabulariesDisabled = configurationService + .getArrayProperty("webui.browse.vocabularies.disabled"); + if (vocabulariesDisabled != null && ArrayUtils.contains(vocabulariesDisabled, nameVocab)) { + // Discard this vocabulary browse index + return null; + } + Set metadataFields = new HashSet<>(); Map> formsToFields = this.authoritiesFormDefinitions.get(nameVocab); for (Map.Entry> formToField : formsToFields.entrySet()) { diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index d9eb1dfa7ed0..3a3d3cc6320d 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -1158,6 +1158,11 @@ webui.browse.index.4 = subject:metadata:dc.subject.*:text ## example of authority-controlled browse category - see authority control config #webui.browse.index.5 = lcAuthor:metadataAuthority:dc.contributor.author:authority +# By default, browse hierarchical indexes are created based on the used controlled +# vocabularies in the submission forms. These could be disabled adding the name of +# the vocabularies to exclude in this comma-separated property: +# webui.browse.vocabularies.disabled = srsc + # Enable/Disable tag cloud in browsing. # webui.browse.index.tagcloud. = true | false # where n is the index number from the above options From 06f4d8df81e7731cba0496e7387572e229de92cc Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 8 Sep 2023 14:01:25 -0500 Subject: [PATCH 405/510] Add note that rebooting Tomcat required (cherry picked from commit ff393fe72d9b218734e4efb0ac8266f222d8da62) --- 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 3a3d3cc6320d..61027c555079 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -1160,7 +1160,8 @@ webui.browse.index.4 = subject:metadata:dc.subject.*:text # By default, browse hierarchical indexes are created based on the used controlled # vocabularies in the submission forms. These could be disabled adding the name of -# the vocabularies to exclude in this comma-separated property: +# the vocabularies to exclude in this comma-separated property. +# (Requires reboot of servlet container, e.g. Tomcat, to reload) # webui.browse.vocabularies.disabled = srsc # Enable/Disable tag cloud in browsing. From 80b35c9650ec2f40bea3b497b65ce77d0c97bfcf Mon Sep 17 00:00:00 2001 From: Hrafn Malmquist Date: Sat, 9 Sep 2023 00:53:11 +0100 Subject: [PATCH 406/510] Add websvc.opensearch.autolink and websvc.opensearch.shortname to exposed REST configuration properties --- dspace/config/modules/rest.cfg | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dspace/config/modules/rest.cfg b/dspace/config/modules/rest.cfg index 657e02b58de7..b08f8d514536 100644 --- a/dspace/config/modules/rest.cfg +++ b/dspace/config/modules/rest.cfg @@ -36,7 +36,6 @@ rest.patch.operations.limit = 1000 # (Requires reboot of servlet container, e.g. Tomcat, to reload) rest.properties.exposed = plugin.named.org.dspace.curate.CurationTask rest.properties.exposed = google.analytics.key -rest.properties.exposed = websvc.opensearch.enable rest.properties.exposed = versioning.item.history.include.submitter rest.properties.exposed = researcher-profile.entity-type rest.properties.exposed = orcid.application-client-id @@ -44,7 +43,10 @@ rest.properties.exposed = orcid.authorize-url rest.properties.exposed = orcid.scope rest.properties.exposed = orcid.disconnection.allowed-users rest.properties.exposed = registration.verification.enabled +rest.properties.exposed = websvc.opensearch.enable rest.properties.exposed = websvc.opensearch.svccontext +rest.properties.exposed = websvc.opensearch.shortname +rest.properties.exposed = websvc.opensearch.autolink rest.properties.exposed = submit.type-bind.field rest.properties.exposed = google.recaptcha.key.site rest.properties.exposed = google.recaptcha.version From b6d20eef71ab40b622cc4808efa5c677e9ecce21 Mon Sep 17 00:00:00 2001 From: Mark Cooper Date: Tue, 12 Sep 2023 18:33:37 -0700 Subject: [PATCH 407/510] Add a "container friendly" log4j2 cfg and output compose dspace log to console (#8828) Co-authored-by: Tim Donohue --- docker-compose.yml | 1 + dspace/config/log4j2-container.xml | 65 ++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 dspace/config/log4j2-container.xml diff --git a/docker-compose.yml b/docker-compose.yml index e623d9607931..38007908c6ec 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -28,6 +28,7 @@ services: # proxies.trusted.ipranges: This setting is required for a REST API running in Docker to trust requests # from the host machine. This IP range MUST correspond to the 'dspacenet' subnet defined above. proxies__P__trusted__P__ipranges: '172.23.0' + LOGGING_CONFIG: /dspace/config/log4j2-container.xml image: "${DOCKER_OWNER:-dspace}/dspace:${DSPACE_VER:-latest-test}" build: context: . diff --git a/dspace/config/log4j2-container.xml b/dspace/config/log4j2-container.xml new file mode 100644 index 000000000000..9fd358c72a1f --- /dev/null +++ b/dspace/config/log4j2-container.xml @@ -0,0 +1,65 @@ + + + + + + INFO + INFO + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 14223bd712ce91cf97096f2201924baea8456814 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 13 Sep 2023 10:15:07 -0500 Subject: [PATCH 408/510] Remove 'cross join' from count query. Updates "countHandlesByPrefix" to use a query similar to existing "findByPrefix" --- .../main/java/org/dspace/handle/dao/impl/HandleDAOImpl.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/handle/dao/impl/HandleDAOImpl.java b/dspace-api/src/main/java/org/dspace/handle/dao/impl/HandleDAOImpl.java index 3bd702bf809c..71bb798ae387 100644 --- a/dspace-api/src/main/java/org/dspace/handle/dao/impl/HandleDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/handle/dao/impl/HandleDAOImpl.java @@ -90,13 +90,11 @@ public List findByPrefix(Context context, String prefix) throws SQLExcep @Override public long countHandlesByPrefix(Context context, String prefix) throws SQLException { - - CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); - CriteriaQuery criteriaQuery = criteriaBuilder.createQuery(Long.class); + CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, Handle.class); Root handleRoot = criteriaQuery.from(Handle.class); - criteriaQuery.select(criteriaBuilder.count(criteriaQuery.from(Handle.class))); + criteriaQuery.select(handleRoot); criteriaQuery.where(criteriaBuilder.like(handleRoot.get(Handle_.handle), prefix + "%")); return countLong(context, criteriaQuery, criteriaBuilder, handleRoot); } From 1271374d37a3f7d9cec270e54ec106895aa934bc Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 13 Sep 2023 11:47:55 -0500 Subject: [PATCH 409/510] Fix ClassCastException (Collection cannot be cast to Item) in Handle identifier classes --- .../identifier/HandleIdentifierProvider.java | 3 +- ...dentifierProviderWithCanonicalHandles.java | 65 ++++++++++--------- 2 files changed, 34 insertions(+), 34 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/identifier/HandleIdentifierProvider.java b/dspace-api/src/main/java/org/dspace/identifier/HandleIdentifierProvider.java index 1ded40c8f8a4..59a1e13a2166 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/HandleIdentifierProvider.java +++ b/dspace-api/src/main/java/org/dspace/identifier/HandleIdentifierProvider.java @@ -88,8 +88,7 @@ public void register(Context context, DSpaceObject dso, String identifier) { try { handleService.createHandle(context, dso, identifier); if (dso instanceof Item || dso instanceof Collection || dso instanceof Community) { - Item item = (Item) dso; - populateHandleMetadata(context, item, identifier); + populateHandleMetadata(context, dso, identifier); } } catch (IOException | IllegalStateException | SQLException | AuthorizeException e) { log.error(LogHelper.getHeader(context, 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 78ad6b7b79bb..bfa331319911 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/VersionedHandleIdentifierProviderWithCanonicalHandles.java +++ b/dspace-api/src/main/java/org/dspace/identifier/VersionedHandleIdentifierProviderWithCanonicalHandles.java @@ -180,45 +180,46 @@ public String register(Context context, DSpaceObject dso) { @Override public void register(Context context, DSpaceObject dso, String identifier) { try { - - Item item = (Item) dso; - - // if for this identifier is already present a record in the Handle table and the corresponding item - // has an history someone is trying to restore the latest version for the item. When - // trying to restore the latest version the identifier in input doesn't have the for 1234/123.latestVersion - // it is the canonical 1234/123 - VersionHistory itemHistory = getHistory(context, identifier); - if (!identifier.matches(".*/.*\\.\\d+") && itemHistory != null) { - - int newVersionNumber = versionHistoryService.getLatestVersion(context, itemHistory) - .getVersionNumber() + 1; - String canonical = identifier; - identifier = identifier.concat(".").concat("" + newVersionNumber); - restoreItAsVersion(context, dso, identifier, item, canonical, itemHistory); - } else if (identifier.matches(".*/.*\\.\\d+")) { - // if identifier == 1234.5/100.4 reinstate the version 4 in the version table if absent - - // if it is a version of an item is needed to put back the record - // in the versionitem table - String canonical = getCanonical(identifier); - DSpaceObject canonicalItem = this.resolve(context, canonical); - if (canonicalItem == null) { - restoreItAsCanonical(context, dso, identifier, item, canonical); - } else { - VersionHistory history = versionHistoryService.findByItem(context, (Item) canonicalItem); - if (history == null) { + if (dso instanceof Item) { + Item item = (Item) dso; + // if for this identifier is already present a record in the Handle table and the corresponding item + // has an history someone is trying to restore the latest version for the item. When + // trying to restore the latest version the identifier in input doesn't have the for 1234/123.latestVersion + // it is the canonical 1234/123 + VersionHistory itemHistory = getHistory(context, identifier); + if (!identifier.matches(".*/.*\\.\\d+") && itemHistory != null) { + + int newVersionNumber = versionHistoryService.getLatestVersion(context, itemHistory) + .getVersionNumber() + 1; + String canonical = identifier; + identifier = identifier.concat(".").concat("" + newVersionNumber); + restoreItAsVersion(context, dso, identifier, item, canonical, itemHistory); + } else if (identifier.matches(".*/.*\\.\\d+")) { + // if identifier == 1234.5/100.4 reinstate the version 4 in the version table if absent + + // if it is a version of an item is needed to put back the record + // in the versionitem table + String canonical = getCanonical(identifier); + DSpaceObject canonicalItem = this.resolve(context, canonical); + if (canonicalItem == null) { restoreItAsCanonical(context, dso, identifier, item, canonical); } else { - restoreItAsVersion(context, dso, identifier, item, canonical, history); + VersionHistory history = versionHistoryService.findByItem(context, (Item) canonicalItem); + if (history == null) { + restoreItAsCanonical(context, dso, identifier, item, canonical); + } else { + restoreItAsVersion(context, dso, identifier, item, canonical, history); + } } + } else { + // A regular handle to create for an Item + createNewIdentifier(context, dso, identifier); + modifyHandleMetadata(context, item, getCanonical(identifier)); } } else { - //A regular handle + // Handle being registered for a different type of object (e.g. Collection or Community) createNewIdentifier(context, dso, identifier); - if (dso instanceof Item) { - modifyHandleMetadata(context, item, getCanonical(identifier)); - } } } catch (IOException | SQLException | AuthorizeException e) { log.error(LogHelper.getHeader(context, From ffa2683c632cfab59b0432c203434ac15a6eb85f Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 13 Sep 2023 12:28:55 -0500 Subject: [PATCH 410/510] Fix checkstyle. Correct grammar of comment while doing so. --- ...ionedHandleIdentifierProviderWithCanonicalHandles.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 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 bfa331319911..e6a092c47284 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/VersionedHandleIdentifierProviderWithCanonicalHandles.java +++ b/dspace-api/src/main/java/org/dspace/identifier/VersionedHandleIdentifierProviderWithCanonicalHandles.java @@ -182,10 +182,10 @@ public void register(Context context, DSpaceObject dso, String identifier) { try { if (dso instanceof Item) { Item item = (Item) dso; - // if for this identifier is already present a record in the Handle table and the corresponding item - // has an history someone is trying to restore the latest version for the item. When - // trying to restore the latest version the identifier in input doesn't have the for 1234/123.latestVersion - // it is the canonical 1234/123 + // if this identifier is already present in the Handle table and the corresponding item + // has a history, then someone is trying to restore the latest version for the item. When + // trying to restore the latest version, the identifier in input doesn't have the + // 1234/123.latestVersion. Instead, it is the canonical 1234/123 VersionHistory itemHistory = getHistory(context, identifier); if (!identifier.matches(".*/.*\\.\\d+") && itemHistory != null) { From ef7f02fe81bc570353c0bf6a43706c77909e30e3 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 13 Sep 2023 16:56:29 -0500 Subject: [PATCH 411/510] Fix "Site cannot be indexed" error by ignoring ADD/REMOVE events on Site object --- .../main/java/org/dspace/discovery/IndexEventConsumer.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/discovery/IndexEventConsumer.java b/dspace-api/src/main/java/org/dspace/discovery/IndexEventConsumer.java index 4ff1f3134484..bf1c7da4e150 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/IndexEventConsumer.java +++ b/dspace-api/src/main/java/org/dspace/discovery/IndexEventConsumer.java @@ -154,7 +154,11 @@ public void consume(Context ctx, Event event) throws Exception { case Event.REMOVE: case Event.ADD: - if (object == null) { + // At this time, ADD and REMOVE actions are ignored on SITE object. They are only triggered for + // top-level communities. No action is necessary as Community itself is indexed (or deleted) separately. + if (event.getSubjectType() == Constants.SITE) { + log.debug(event.getEventTypeAsString() + " event triggered for Site object. Skipping it."); + } else if (object == null) { log.warn(event.getEventTypeAsString() + " event, could not get object for " + event.getObjectTypeAsString() + " id=" + event.getObjectID() From d17ef09082aa237cffdc928d9560667487c2c976 Mon Sep 17 00:00:00 2001 From: Agustina Martinez Date: Thu, 14 Sep 2023 10:02:24 +0100 Subject: [PATCH 412/510] DefaultAccessStatusHelper: fix logic to take shortest embargo --- .../access/status/DefaultAccessStatusHelper.java | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/access/status/DefaultAccessStatusHelper.java b/dspace-api/src/main/java/org/dspace/access/status/DefaultAccessStatusHelper.java index 9b5227491bc0..05f0757060ab 100644 --- a/dspace-api/src/main/java/org/dspace/access/status/DefaultAccessStatusHelper.java +++ b/dspace-api/src/main/java/org/dspace/access/status/DefaultAccessStatusHelper.java @@ -175,7 +175,7 @@ private String calculateAccessStatusForDso(Context context, DSpaceObject dso, Da @Override public String getEmbargoFromItem(Context context, Item item) throws SQLException { - Date embargoDate; + Date embargoedDate; if (item == null) { return null; @@ -202,15 +202,15 @@ public String getEmbargoFromItem(Context context, Item item) return null; } - embargoDate = this.retrieveLongestEmbargo(context, bitstream); + embargoedDate = this.retrieveShortestEmbargo(context, bitstream); - return embargoDate != null ? embargoDate.toString() : null; + return embargoedDate != null ? embargoedDate.toString() : null; } /** * */ - private Date retrieveLongestEmbargo(Context context, Bitstream bitstream) throws SQLException { + private Date retrieveShortestEmbargo(Context context, Bitstream bitstream) throws SQLException { Date embargoDate = null; // Only consider read policies. List policies = authorizeService @@ -228,11 +228,12 @@ private Date retrieveLongestEmbargo(Context context, Bitstream bitstream) throws Date startDate = policy.getStartDate(); if (startDate != null && !startDate.before(LocalDate.now().toDate())) { - // There is an active embargo: aim to take the longest embargo + // There is an active embargo: aim to take the shortest embargo (account for rare cases where + // more than one resource policy exists) if (embargoDate == null) { embargoDate = startDate; } else { - embargoDate = startDate.after(embargoDate) ? startDate : embargoDate; + embargoDate = startDate.before(embargoDate) ? startDate : embargoDate; } } } From 490a982e8055991a6b8cbacece22b924466e22df Mon Sep 17 00:00:00 2001 From: Agustina Martinez Date: Thu, 14 Sep 2023 16:39:39 +0100 Subject: [PATCH 413/510] Remove currently unused customisation of ItemUtils --- .../java/org/dspace/xoai/util/ItemUtils.java | 54 ------------------- 1 file changed, 54 deletions(-) diff --git a/dspace-oai/src/main/java/org/dspace/xoai/util/ItemUtils.java b/dspace-oai/src/main/java/org/dspace/xoai/util/ItemUtils.java index 80eb67a2b994..35bef8c8d77f 100644 --- a/dspace-oai/src/main/java/org/dspace/xoai/util/ItemUtils.java +++ b/dspace-oai/src/main/java/org/dspace/xoai/util/ItemUtils.java @@ -11,8 +11,6 @@ import java.io.IOException; import java.io.InputStream; import java.sql.SQLException; -import java.text.SimpleDateFormat; -import java.util.Date; import java.util.List; import com.lyncode.xoai.dataprovider.xml.xoai.Element; @@ -23,9 +21,6 @@ import org.dspace.app.util.factory.UtilServiceFactory; import org.dspace.app.util.service.MetadataExposureService; import org.dspace.authorize.AuthorizeException; -import org.dspace.authorize.ResourcePolicy; -import org.dspace.authorize.factory.AuthorizeServiceFactory; -import org.dspace.authorize.service.AuthorizeService; import org.dspace.content.Bitstream; import org.dspace.content.Bundle; import org.dspace.content.Item; @@ -39,9 +34,6 @@ import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.core.Utils; -import org.dspace.eperson.Group; -import org.dspace.eperson.factory.EPersonServiceFactory; -import org.dspace.eperson.service.GroupService; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; import org.dspace.xoai.data.DSpaceItem; @@ -65,9 +57,6 @@ public class ItemUtils { private static final BitstreamService bitstreamService = ContentServiceFactory.getInstance().getBitstreamService(); - private static final AuthorizeService authorizeService = - AuthorizeServiceFactory.getInstance().getAuthorizeService(); - private static final ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); /** @@ -152,9 +141,6 @@ private static Element createBundlesElement(Context context, Item item) throws S if (description != null) { bitstream.getField().add(createValue("description", description)); } - // Add bitstream embargo information (READ policy present, for Anonymous group with a start date) - addEmbargoField(context, bit, bitstream); - bitstream.getField().add(createValue("format", bit.getFormat(context).getMIMEType())); bitstream.getField().add(createValue("size", "" + bit.getSizeBytes())); bitstream.getField().add(createValue("url", url)); @@ -167,46 +153,6 @@ private static Element createBundlesElement(Context context, Item item) throws S return bundles; } - /** - * This method will add embargo metadata for a give bitstream with an active embargo. - * It will parse of relevant policies and select the longest active embargo - * @param context - * @param bitstream the bitstream object - * @param bitstreamEl the bitstream metadata object to add embargo value to - * @throws SQLException - */ - private static void addEmbargoField(Context context, Bitstream bitstream, Element bitstreamEl) throws SQLException { - GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); - Group anonymousGroup = groupService.findByName(context, Group.ANONYMOUS); - List policies = authorizeService.findPoliciesByDSOAndType(context, - bitstream, - ResourcePolicy.TYPE_CUSTOM); - - Date embargoDate = null; - - // Account for cases where there could be more than one embargo policy - for (ResourcePolicy policy : policies) { - if (policy.getGroup() == anonymousGroup && policy.getAction() == Constants.READ) { - Date startDate = policy.getStartDate(); - if (startDate != null && startDate.after(new Date())) { - // There is an active embargo: aim to take the longest embargo - if (embargoDate == null) { - embargoDate = startDate; - } else { - embargoDate = startDate.after(embargoDate) ? startDate : embargoDate; - } - } - } - } - - if (embargoDate != null) { - // Sort array of dates to extract the longest embargo - SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd"); - bitstreamEl.getField().add( - createValue("embargo", formatter.format(embargoDate))); - } - } - private static Element createLicenseElement(Context context, Item item) throws SQLException, AuthorizeException, IOException { Element license = create("license"); From bc505d7cae03ee4c0a7c909bb3d9e9140587e4f7 Mon Sep 17 00:00:00 2001 From: Agustina Martinez Date: Fri, 15 Sep 2023 14:22:56 +0100 Subject: [PATCH 414/510] XmlWorkflowCuratorServiceImpl: add check to queue task if configured; Curation: remove obsolete code preventing curation running on workflow tasks as #3157 is now implemented --- .../main/java/org/dspace/curate/Curation.java | 13 +--- .../curate/XmlWorkflowCuratorServiceImpl.java | 76 ++++++++++--------- 2 files changed, 45 insertions(+), 44 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 b3af072a32cd..4d70286e79e0 100644 --- a/dspace-api/src/main/java/org/dspace/curate/Curation.java +++ b/dspace-api/src/main/java/org/dspace/curate/Curation.java @@ -152,17 +152,10 @@ private long runQueue(TaskQueue queue, Curator curator) throws SQLException, Aut super.handler.logInfo("Curating id: " + entry.getObjectId()); } curator.clear(); - // does entry relate to a DSO or workflow object? - if (entry.getObjectId().indexOf('/') > 0) { - for (String taskName : entry.getTaskNames()) { - curator.addTask(taskName); - } - curator.curate(context, entry.getObjectId()); - } else { - // TODO: Remove this exception once curation tasks are supported by configurable workflow - // e.g. see https://github.com/DSpace/DSpace/pull/3157 - throw new IllegalArgumentException("curation for workflow items is no longer supported"); + for (String taskName : entry.getTaskNames()) { + curator.addTask(taskName); } + curator.curate(context, entry.getObjectId()); } queue.release(this.queue, ticket, true); return ticket; diff --git a/dspace-api/src/main/java/org/dspace/curate/XmlWorkflowCuratorServiceImpl.java b/dspace-api/src/main/java/org/dspace/curate/XmlWorkflowCuratorServiceImpl.java index 05c7a8d99930..f45f4a17b6cb 100644 --- a/dspace-api/src/main/java/org/dspace/curate/XmlWorkflowCuratorServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/curate/XmlWorkflowCuratorServiceImpl.java @@ -13,6 +13,7 @@ import java.util.ArrayList; 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; @@ -123,40 +124,47 @@ public boolean curate(Curator curator, Context c, XmlWorkflowItem wfi) item.setOwningCollection(wfi.getCollection()); for (Task task : step.tasks) { curator.addTask(task.name); - curator.curate(item); - int status = curator.getStatus(task.name); - String result = curator.getResult(task.name); - String action = "none"; - switch (status) { - case Curator.CURATE_FAIL: - // task failed - notify any contacts the task has assigned - if (task.powers.contains("reject")) { - action = "reject"; - } - notifyContacts(c, wfi, task, "fail", action, result); - // if task so empowered, reject submission and terminate - if ("reject".equals(action)) { - workflowService.sendWorkflowItemBackSubmission(c, wfi, - c.getCurrentUser(), null, - task.name + ": " + result); - return false; - } - break; - case Curator.CURATE_SUCCESS: - if (task.powers.contains("approve")) { - action = "approve"; - } - notifyContacts(c, wfi, task, "success", action, result); - if ("approve".equals(action)) { - // cease further task processing and advance submission - return true; - } - break; - case Curator.CURATE_ERROR: - notifyContacts(c, wfi, task, "error", action, result); - break; - default: - break; + // Check whether the task is configured to be queued rather than automatically run + if (StringUtils.isNotEmpty(step.queue)) { + // queue attribute has been set in the FlowStep configuration: add task to configured queue + curator.queue(c, item.getID().toString(), step.queue); + } else { + // Task is configured to be run automatically + curator.curate(item); + int status = curator.getStatus(task.name); + String result = curator.getResult(task.name); + String action = "none"; + switch (status) { + case Curator.CURATE_FAIL: + // task failed - notify any contacts the task has assigned + if (task.powers.contains("reject")) { + action = "reject"; + } + notifyContacts(c, wfi, task, "fail", action, result); + // if task so empowered, reject submission and terminate + if ("reject".equals(action)) { + workflowService.sendWorkflowItemBackSubmission(c, wfi, + c.getCurrentUser(), null, + task.name + ": " + result); + return false; + } + break; + case Curator.CURATE_SUCCESS: + if (task.powers.contains("approve")) { + action = "approve"; + } + notifyContacts(c, wfi, task, "success", action, result); + if ("approve".equals(action)) { + // cease further task processing and advance submission + return true; + } + break; + case Curator.CURATE_ERROR: + notifyContacts(c, wfi, task, "error", action, result); + break; + default: + break; + } } curator.clear(); } From 4917badcebd70e430bc25a381c70a0aac0b845d6 Mon Sep 17 00:00:00 2001 From: nwoodward Date: Mon, 18 Sep 2023 16:19:52 -0500 Subject: [PATCH 415/510] added authorization check for license bitstream in OAI import --- .../java/org/dspace/xoai/util/ItemUtils.java | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/dspace-oai/src/main/java/org/dspace/xoai/util/ItemUtils.java b/dspace-oai/src/main/java/org/dspace/xoai/util/ItemUtils.java index 35bef8c8d77f..938cf0d64a5b 100644 --- a/dspace-oai/src/main/java/org/dspace/xoai/util/ItemUtils.java +++ b/dspace-oai/src/main/java/org/dspace/xoai/util/ItemUtils.java @@ -21,6 +21,8 @@ import org.dspace.app.util.factory.UtilServiceFactory; import org.dspace.app.util.service.MetadataExposureService; import org.dspace.authorize.AuthorizeException; +import org.dspace.authorize.factory.AuthorizeServiceFactory; +import org.dspace.authorize.service.AuthorizeService; import org.dspace.content.Bitstream; import org.dspace.content.Bundle; import org.dspace.content.Item; @@ -59,6 +61,10 @@ public class ItemUtils { private static final ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); + + private static final AuthorizeService authorizeService + = AuthorizeServiceFactory.getInstance().getAuthorizeService(); + /** * Default constructor */ @@ -163,13 +169,17 @@ private static Element createLicenseElement(Context context, Item item) List licBits = licBundle.getBitstreams(); if (!licBits.isEmpty()) { Bitstream licBit = licBits.get(0); - InputStream in; - - in = bitstreamService.retrieve(context, licBit); - ByteArrayOutputStream out = new ByteArrayOutputStream(); - Utils.bufferedCopy(in, out); - license.getField().add(createValue("bin", Base64Utils.encode(out.toString()))); - + if (authorizeService.authorizeActionBoolean(context, licBit, Constants.READ)) { + InputStream in; + + in = bitstreamService.retrieve(context, licBit); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + Utils.bufferedCopy(in, out); + license.getField().add(createValue("bin", Base64Utils.encode(out.toString()))); + } else { + log.info("Missing READ rights for license bitstream. Did not include license bitstream for item: " + + item.getID() + "."); + } } } return license; From 51d8a7d9979b81ab42f712d4a37c1f8abdf9b039 Mon Sep 17 00:00:00 2001 From: nwoodward Date: Tue, 19 Sep 2023 15:08:42 -0500 Subject: [PATCH 416/510] remove optimize option from oai import --- .../src/main/java/org/dspace/xoai/app/XOAI.java | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/dspace-oai/src/main/java/org/dspace/xoai/app/XOAI.java b/dspace-oai/src/main/java/org/dspace/xoai/app/XOAI.java index e27a3ee947cb..4f842b8e944c 100644 --- a/dspace-oai/src/main/java/org/dspace/xoai/app/XOAI.java +++ b/dspace-oai/src/main/java/org/dspace/xoai/app/XOAI.java @@ -85,7 +85,6 @@ public class XOAI { // needed because the solr query only returns 10 rows by default private final Context context; - private boolean optimize; private final boolean verbose; private boolean clean; @@ -122,9 +121,8 @@ private List getFileFormats(Item item) { return formats; } - public XOAI(Context context, boolean optimize, boolean clean, boolean verbose) { + public XOAI(Context context, boolean clean, boolean verbose) { this.context = context; - this.optimize = optimize; this.clean = clean; this.verbose = verbose; @@ -173,12 +171,6 @@ public int index() throws DSpaceSolrIndexerException { } solrServerResolver.getServer().commit(); - if (optimize) { - println("Optimizing Index"); - solrServerResolver.getServer().optimize(); - println("Index optimized"); - } - // Set last compilation date xoaiLastCompilationCacheService.put(new Date()); return result; @@ -586,7 +578,6 @@ public static void main(String[] argv) throws IOException, ConfigurationExceptio CommandLineParser parser = new DefaultParser(); Options options = new Options(); options.addOption("c", "clear", false, "Clear index before indexing"); - options.addOption("o", "optimize", false, "Optimize index at the end"); options.addOption("v", "verbose", false, "Verbose output"); options.addOption("h", "help", false, "Shows some help"); options.addOption("n", "number", true, "FOR DEVELOPMENT MUST DELETE"); @@ -620,7 +611,7 @@ public static void main(String[] argv) throws IOException, ConfigurationExceptio if (COMMAND_IMPORT.equals(command)) { ctx = new Context(Context.Mode.READ_ONLY); - XOAI indexer = new XOAI(ctx, line.hasOption('o'), line.hasOption('c'), line.hasOption('v')); + XOAI indexer = new XOAI(ctx, line.hasOption('c'), line.hasOption('v')); applicationContext.getAutowireCapableBeanFactory().autowireBean(indexer); @@ -706,7 +697,6 @@ private static void usage() { System.out.println(" " + COMMAND_IMPORT + " - To import DSpace items into OAI index and cache system"); System.out.println(" " + COMMAND_CLEAN_CACHE + " - Cleans the OAI cached responses"); System.out.println("> Parameters:"); - System.out.println(" -o Optimize index after indexing (" + COMMAND_IMPORT + " only)"); System.out.println(" -c Clear index (" + COMMAND_IMPORT + " only)"); System.out.println(" -v Verbose output"); System.out.println(" -h Shows this text"); From e05e73a112ce60bd0689ce68af442382712bd5fc Mon Sep 17 00:00:00 2001 From: Agustina Martinez Date: Wed, 20 Sep 2023 16:26:14 +0100 Subject: [PATCH 417/510] DefaultAccessStatusHelper: getEmbargoFromItem return null embargo if status than embargo --- .../dspace/access/status/AccessStatusHelper.java | 4 +++- .../access/status/AccessStatusServiceImpl.java | 2 +- .../access/status/DefaultAccessStatusHelper.java | 13 ++++++++----- .../status/DefaultAccessStatusHelperTest.java | 6 +++--- 4 files changed, 15 insertions(+), 10 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/access/status/AccessStatusHelper.java b/dspace-api/src/main/java/org/dspace/access/status/AccessStatusHelper.java index d847e907b403..2d782dc3b82a 100644 --- a/dspace-api/src/main/java/org/dspace/access/status/AccessStatusHelper.java +++ b/dspace-api/src/main/java/org/dspace/access/status/AccessStatusHelper.java @@ -22,6 +22,7 @@ public interface AccessStatusHelper { * * @param context the DSpace context * @param item the item + * @param threshold the embargo threshold date * @return an access status value * @throws SQLException An exception that provides information on a database access error or other errors. */ @@ -33,8 +34,9 @@ public String getAccessStatusFromItem(Context context, Item item, Date threshold * * @param context the DSpace context * @param item the item to check for embargo information + * @param threshold the embargo threshold date * @return an embargo date * @throws SQLException An exception that provides information on a database access error or other errors. */ - public String getEmbargoFromItem(Context context, Item item) throws SQLException; + public String getEmbargoFromItem(Context context, Item item, Date threshold) throws SQLException; } diff --git a/dspace-api/src/main/java/org/dspace/access/status/AccessStatusServiceImpl.java b/dspace-api/src/main/java/org/dspace/access/status/AccessStatusServiceImpl.java index 1d4dc6088c3a..01b370747932 100644 --- a/dspace-api/src/main/java/org/dspace/access/status/AccessStatusServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/access/status/AccessStatusServiceImpl.java @@ -70,6 +70,6 @@ public String getAccessStatus(Context context, Item item) throws SQLException { @Override public String getEmbargoFromItem(Context context, Item item) throws SQLException { - return helper.getEmbargoFromItem(context, item); + return helper.getEmbargoFromItem(context, item, forever_date); } } diff --git a/dspace-api/src/main/java/org/dspace/access/status/DefaultAccessStatusHelper.java b/dspace-api/src/main/java/org/dspace/access/status/DefaultAccessStatusHelper.java index 05f0757060ab..5f0e6d8b259b 100644 --- a/dspace-api/src/main/java/org/dspace/access/status/DefaultAccessStatusHelper.java +++ b/dspace-api/src/main/java/org/dspace/access/status/DefaultAccessStatusHelper.java @@ -173,11 +173,14 @@ private String calculateAccessStatusForDso(Context context, DSpaceObject dso, Da * @return an access status value */ @Override - public String getEmbargoFromItem(Context context, Item item) + public String getEmbargoFromItem(Context context, Item item, Date threshold) throws SQLException { - Date embargoedDate; + Date embargoDate; - if (item == null) { + // If Item status is not "embargo" then return a null embargo date. + String accessStatus = getAccessStatusFromItem(context, item, threshold); + + if (item == null || !accessStatus.equals(EMBARGO)) { return null; } // Consider only the original bundles. @@ -202,9 +205,9 @@ public String getEmbargoFromItem(Context context, Item item) return null; } - embargoedDate = this.retrieveShortestEmbargo(context, bitstream); + embargoDate = this.retrieveShortestEmbargo(context, bitstream); - return embargoedDate != null ? embargoedDate.toString() : null; + return embargoDate != null ? embargoDate.toString() : null; } /** diff --git a/dspace-api/src/test/java/org/dspace/access/status/DefaultAccessStatusHelperTest.java b/dspace-api/src/test/java/org/dspace/access/status/DefaultAccessStatusHelperTest.java index c97349ac7c77..51291ee9850d 100644 --- a/dspace-api/src/test/java/org/dspace/access/status/DefaultAccessStatusHelperTest.java +++ b/dspace-api/src/test/java/org/dspace/access/status/DefaultAccessStatusHelperTest.java @@ -274,7 +274,7 @@ public void testWithEmbargo() throws Exception { context.restoreAuthSystemState(); String status = helper.getAccessStatusFromItem(context, itemWithEmbargo, threshold); assertThat("testWithEmbargo 0", status, equalTo(DefaultAccessStatusHelper.EMBARGO)); - String embargoDate = helper.getEmbargoFromItem(context, itemWithEmbargo); + String embargoDate = helper.getEmbargoFromItem(context, itemWithEmbargo, threshold); assertThat("testWithEmbargo 1", embargoDate, equalTo(policy.getStartDate().toString())); } @@ -393,7 +393,7 @@ public void testWithPrimaryAndMultipleBitstreams() throws Exception { context.restoreAuthSystemState(); String status = helper.getAccessStatusFromItem(context, itemWithPrimaryAndMultipleBitstreams, threshold); assertThat("testWithPrimaryAndMultipleBitstreams 0", status, equalTo(DefaultAccessStatusHelper.EMBARGO)); - String embargoDate = helper.getEmbargoFromItem(context, itemWithPrimaryAndMultipleBitstreams); + String embargoDate = helper.getEmbargoFromItem(context, itemWithPrimaryAndMultipleBitstreams, threshold); assertThat("testWithPrimaryAndMultipleBitstreams 1", embargoDate, equalTo(policy.getStartDate().toString())); } @@ -424,7 +424,7 @@ public void testWithNoPrimaryAndMultipleBitstreams() throws Exception { context.restoreAuthSystemState(); String status = helper.getAccessStatusFromItem(context, itemWithoutPrimaryAndMultipleBitstreams, threshold); assertThat("testWithNoPrimaryAndMultipleBitstreams 0", status, equalTo(DefaultAccessStatusHelper.OPEN_ACCESS)); - String embargoDate = helper.getEmbargoFromItem(context, itemWithEmbargo); + String embargoDate = helper.getEmbargoFromItem(context, itemWithEmbargo, threshold); assertThat("testWithNoPrimaryAndMultipleBitstreams 1", embargoDate, equalTo(null)); } From f51975bea191ee9c06073f8a31a9107703770479 Mon Sep 17 00:00:00 2001 From: frabacche Date: Mon, 25 Sep 2023 11:53:49 +0200 Subject: [PATCH 418/510] CST-5249 add qaevents to docker-compose.yml configuration --- docker-compose.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docker-compose.yml b/docker-compose.yml index e623d9607931..d67c9ae1d81c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -120,6 +120,8 @@ services: cp -r /opt/solr/server/solr/configsets/search/* search precreate-core statistics /opt/solr/server/solr/configsets/statistics cp -r /opt/solr/server/solr/configsets/statistics/* statistics + precreate-core qaevents /opt/solr/server/solr/configsets/qaevents + cp -r /opt/solr/server/solr/configsets/qaevents/* qaevents exec solr -f volumes: assetstore: From ae0ecb3dc14b1987a6ad4e9b431693ed19b6f5f0 Mon Sep 17 00:00:00 2001 From: frabacche Date: Wed, 27 Sep 2023 10:04:08 +0200 Subject: [PATCH 419/510] Docker solr configuration qaevents new solr collection --- dspace/src/main/docker/dspace-solr/Dockerfile | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/dspace/src/main/docker/dspace-solr/Dockerfile b/dspace/src/main/docker/dspace-solr/Dockerfile index 9fe9adf9440f..5f9b266999eb 100644 --- a/dspace/src/main/docker/dspace-solr/Dockerfile +++ b/dspace/src/main/docker/dspace-solr/Dockerfile @@ -17,19 +17,22 @@ FROM solr:${SOLR_VERSION}-slim ENV AUTHORITY_CONFIGSET_PATH=/opt/solr/server/solr/configsets/authority/conf \ OAI_CONFIGSET_PATH=/opt/solr/server/solr/configsets/oai/conf \ SEARCH_CONFIGSET_PATH=/opt/solr/server/solr/configsets/search/conf \ - STATISTICS_CONFIGSET_PATH=/opt/solr/server/solr/configsets/statistics/conf - + STATISTICS_CONFIGSET_PATH=/opt/solr/server/solr/configsets/statistics/conf \ + QAEVENTS_CONFIGSET_PATH=/opt/solr/server/solr/configsets/qaevents/conf + USER root RUN mkdir -p $AUTHORITY_CONFIGSET_PATH && \ mkdir -p $OAI_CONFIGSET_PATH && \ mkdir -p $SEARCH_CONFIGSET_PATH && \ - mkdir -p $STATISTICS_CONFIGSET_PATH + mkdir -p $STATISTICS_CONFIGSET_PATH && \ + mkdir -p $QAEVENTS_CONFIGSET_PATH COPY dspace/solr/authority/conf/* $AUTHORITY_CONFIGSET_PATH/ COPY dspace/solr/oai/conf/* $OAI_CONFIGSET_PATH/ COPY dspace/solr/search/conf/* $SEARCH_CONFIGSET_PATH/ COPY dspace/solr/statistics/conf/* $STATISTICS_CONFIGSET_PATH/ +COPY dspace/solr/qaevents/conf/* $QAEVENTS_CONFIGSET_PATH/ RUN chown -R solr:solr /opt/solr/server/solr/configsets From d377f314ff38ece314354c6eac3ac2a15b53fc4f Mon Sep 17 00:00:00 2001 From: frabacche Date: Wed, 27 Sep 2023 10:26:05 +0200 Subject: [PATCH 420/510] Docker solr configuration qaevents new solr collection (typo) --- dspace/src/main/docker/dspace-solr/Dockerfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dspace/src/main/docker/dspace-solr/Dockerfile b/dspace/src/main/docker/dspace-solr/Dockerfile index 5f9b266999eb..5d02d0db811f 100644 --- a/dspace/src/main/docker/dspace-solr/Dockerfile +++ b/dspace/src/main/docker/dspace-solr/Dockerfile @@ -18,7 +18,7 @@ ENV AUTHORITY_CONFIGSET_PATH=/opt/solr/server/solr/configsets/authority/conf \ OAI_CONFIGSET_PATH=/opt/solr/server/solr/configsets/oai/conf \ SEARCH_CONFIGSET_PATH=/opt/solr/server/solr/configsets/search/conf \ STATISTICS_CONFIGSET_PATH=/opt/solr/server/solr/configsets/statistics/conf \ - QAEVENTS_CONFIGSET_PATH=/opt/solr/server/solr/configsets/qaevents/conf + QAEVENT_CONFIGSET_PATH=/opt/solr/server/solr/configsets/qaevent/conf USER root @@ -26,13 +26,13 @@ RUN mkdir -p $AUTHORITY_CONFIGSET_PATH && \ mkdir -p $OAI_CONFIGSET_PATH && \ mkdir -p $SEARCH_CONFIGSET_PATH && \ mkdir -p $STATISTICS_CONFIGSET_PATH && \ - mkdir -p $QAEVENTS_CONFIGSET_PATH + mkdir -p $QAEVENT_CONFIGSET_PATH COPY dspace/solr/authority/conf/* $AUTHORITY_CONFIGSET_PATH/ COPY dspace/solr/oai/conf/* $OAI_CONFIGSET_PATH/ COPY dspace/solr/search/conf/* $SEARCH_CONFIGSET_PATH/ COPY dspace/solr/statistics/conf/* $STATISTICS_CONFIGSET_PATH/ -COPY dspace/solr/qaevents/conf/* $QAEVENTS_CONFIGSET_PATH/ +COPY dspace/solr/qaevent/conf/* $QAEVENT_CONFIGSET_PATH/ RUN chown -R solr:solr /opt/solr/server/solr/configsets From b2dddca6fb47874bfe6ceef862c07568e456deb0 Mon Sep 17 00:00:00 2001 From: frabacche Date: Wed, 27 Sep 2023 10:27:35 +0200 Subject: [PATCH 421/510] docker-compose.yml typo --- docker-compose.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index d67c9ae1d81c..a9aa161e9ec2 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -120,8 +120,8 @@ services: cp -r /opt/solr/server/solr/configsets/search/* search precreate-core statistics /opt/solr/server/solr/configsets/statistics cp -r /opt/solr/server/solr/configsets/statistics/* statistics - precreate-core qaevents /opt/solr/server/solr/configsets/qaevents - cp -r /opt/solr/server/solr/configsets/qaevents/* qaevents + precreate-core qaevent /opt/solr/server/solr/configsets/qaevent + cp -r /opt/solr/server/solr/configsets/qaevent/* qaevent exec solr -f volumes: assetstore: From 68cdb108e9becf35c2435f46f22a0045549684cc Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Mon, 2 Oct 2023 17:44:50 +1300 Subject: [PATCH 422/510] Additional Item class cast fixes in handle providers DSOs were not properly checked if they were instanceof Item before attempting the cast in HandleIdentifierProvider and VersionedHandleIdentifierProviderWithCanonicalHandles --- .../org/dspace/identifier/HandleIdentifierProvider.java | 5 ++--- ...rsionedHandleIdentifierProviderWithCanonicalHandles.java | 6 +++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/identifier/HandleIdentifierProvider.java b/dspace-api/src/main/java/org/dspace/identifier/HandleIdentifierProvider.java index 59a1e13a2166..82358362da85 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/HandleIdentifierProvider.java +++ b/dspace-api/src/main/java/org/dspace/identifier/HandleIdentifierProvider.java @@ -68,10 +68,9 @@ public String register(Context context, DSpaceObject dso) { try { String id = mint(context, dso); - // move canonical to point the latest version + // Populate metadata if (dso instanceof Item || dso instanceof Collection || dso instanceof Community) { - Item item = (Item) dso; - populateHandleMetadata(context, item, id); + populateHandleMetadata(context, dso, id); } return id; diff --git a/dspace-api/src/main/java/org/dspace/identifier/VersionedHandleIdentifierProviderWithCanonicalHandles.java b/dspace-api/src/main/java/org/dspace/identifier/VersionedHandleIdentifierProviderWithCanonicalHandles.java index e6a092c47284..9993f78b4dd5 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/VersionedHandleIdentifierProviderWithCanonicalHandles.java +++ b/dspace-api/src/main/java/org/dspace/identifier/VersionedHandleIdentifierProviderWithCanonicalHandles.java @@ -95,11 +95,11 @@ public String register(Context context, DSpaceObject dso) { String id = mint(context, dso); // move canonical to point the latest version - if (dso != null && dso.getType() == Constants.ITEM) { + if (dso.getType() == Constants.ITEM && dso instanceof Item) { Item item = (Item) dso; - VersionHistory history = null; + VersionHistory history; try { - history = versionHistoryService.findByItem(context, (Item) dso); + history = versionHistoryService.findByItem(context, item); } catch (SQLException ex) { throw new RuntimeException("A problem with the database connection occured.", ex); } From 5e04edf41e452cd383597680da9c3101211156b8 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 2 Oct 2023 10:55:43 -0500 Subject: [PATCH 423/510] Remove Oracle script that accidentally made it in via #8800 --- .../V7.6_2023.04.19__process_parameters_to_text_type.sql | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.6_2023.04.19__process_parameters_to_text_type.sql diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.6_2023.04.19__process_parameters_to_text_type.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.6_2023.04.19__process_parameters_to_text_type.sql deleted file mode 100644 index 6b2dd705ea68..000000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.6_2023.04.19__process_parameters_to_text_type.sql +++ /dev/null @@ -1,9 +0,0 @@ --- --- The contents of this file are subject to the license and copyright --- detailed in the LICENSE and NOTICE files at the root of the source --- tree and available online at --- --- http://www.dspace.org/license/ --- - -ALTER TABLE process MODIFY (parameters CLOB); From db36d5eeae3e76b61178c2c7ac4243bc2fc20a97 Mon Sep 17 00:00:00 2001 From: aroman-arvo Date: Mon, 2 Oct 2023 18:00:09 +0200 Subject: [PATCH 424/510] 8968 - request-a-copy email: non ASCII characters are encoded as HTML character entity references --- .../dspace/app/rest/repository/RequestItemRepository.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RequestItemRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RequestItemRepository.java index 7c0694c52f26..8a60867f9e99 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RequestItemRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RequestItemRepository.java @@ -173,11 +173,10 @@ public RequestItemRest createAndReturn(Context ctx) username = user.getFullName(); } else { // An anonymous session may provide a name. // Escape username to evade nasty XSS attempts - username = StringEscapeUtils.escapeHtml4(rir.getRequestName()); + username = rir.getRequestName(); } - // Requester's message text, escaped to evade nasty XSS attempts - String message = StringEscapeUtils.escapeHtml4(rir.getRequestMessage()); + String message = rir.getRequestMessage(); // Create the request. String token; From bf6e042085140e305d43d61ddce564fbfe819c7f Mon Sep 17 00:00:00 2001 From: aroman-arvo Date: Mon, 2 Oct 2023 18:38:33 +0200 Subject: [PATCH 425/510] unused import --- .../org/dspace/app/rest/repository/RequestItemRepository.java | 1 - 1 file changed, 1 deletion(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RequestItemRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RequestItemRepository.java index 8a60867f9e99..bc276d73d5d6 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RequestItemRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RequestItemRepository.java @@ -21,7 +21,6 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import org.apache.commons.text.StringEscapeUtils; import org.apache.commons.validator.routines.EmailValidator; import org.apache.http.client.utils.URIBuilder; import org.apache.logging.log4j.LogManager; From 48b0b71c6301b6eb46c387c47b71d0729cc2f889 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paulo=20Gra=C3=A7a?= Date: Tue, 3 Oct 2023 16:52:15 +0100 Subject: [PATCH 426/510] add test and fix --- .../main/java/org/dspace/content/Bundle.java | 2 +- .../java/org/dspace/content/BundleTest.java | 32 +++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/content/Bundle.java b/dspace-api/src/main/java/org/dspace/content/Bundle.java index 6c62c3dc9139..e5cbdb6ff244 100644 --- a/dspace-api/src/main/java/org/dspace/content/Bundle.java +++ b/dspace-api/src/main/java/org/dspace/content/Bundle.java @@ -126,7 +126,7 @@ public void setPrimaryBitstreamID(Bitstream bitstream) { * Unset the primary bitstream ID of the bundle */ public void unsetPrimaryBitstreamID() { - primaryBitstream = null; + setPrimaryBitstreamID(null); } /** diff --git a/dspace-api/src/test/java/org/dspace/content/BundleTest.java b/dspace-api/src/test/java/org/dspace/content/BundleTest.java index 4ff35f5b4df8..13b943b4d6b4 100644 --- a/dspace-api/src/test/java/org/dspace/content/BundleTest.java +++ b/dspace-api/src/test/java/org/dspace/content/BundleTest.java @@ -513,6 +513,38 @@ public void testRemoveBitstreamAuth() throws SQLException, AuthorizeException, I } + /** + * Test removeBitstream method and also the unsetPrimaryBitstreamID method, of class Bundle. + */ + @Test + public void testRemoveBitstreamAuthAndUnsetPrimaryBitstreamID() throws IOException, SQLException, AuthorizeException { + // Allow Item WRITE permissions + doNothing().when(authorizeServiceSpy).authorizeAction(context, item, Constants.WRITE); + // Allow Bundle ADD permissions + doNothing().when(authorizeServiceSpy).authorizeAction(context, b, Constants.ADD); + // Allow Bitstream WRITE permissions + doNothing().when(authorizeServiceSpy) + .authorizeAction(any(Context.class), any(Bitstream.class), eq(Constants.WRITE)); + // Allow Bitstream DELETE permissions + doNothing().when(authorizeServiceSpy) + .authorizeAction(any(Context.class), any(Bitstream.class), eq(Constants.DELETE)); + + + context.turnOffAuthorisationSystem(); + //set a value different than default + File f = new File(testProps.get("test.bitstream").toString()); + Bitstream bs = bitstreamService.create(context, new FileInputStream(f)); + bundleService.addBitstream(context, b, bs); + b.setPrimaryBitstreamID(bs); + context.restoreAuthSystemState(); + + assertThat("testRemoveBitstreamAuthAndUnsetPrimaryBitstreamID 0", b.getPrimaryBitstream(), equalTo(bs)); + //remove bitstream + bundleService.removeBitstream(context, b, bs); + //is -1 when not set + assertThat("testRemoveBitstreamAuthAndUnsetPrimaryBitstreamID 1", b.getPrimaryBitstream(), equalTo(null)); + } + /** * Test of update method, of class Bundle. */ From c15ac0eb4a3d39a0de47adbfa5260a6f3b396837 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ad=C3=A1n=20Rom=C3=A1n=20Ruiz?= Date: Fri, 6 Oct 2023 10:04:41 +0200 Subject: [PATCH 427/510] #8585 Add submitter information to provenance metadata --- .../xmlworkflow/XmlWorkflowServiceImpl.java | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/XmlWorkflowServiceImpl.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/XmlWorkflowServiceImpl.java index da7910da29f2..51292fd4773a 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/XmlWorkflowServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/XmlWorkflowServiceImpl.java @@ -221,6 +221,8 @@ public XmlWorkflowItem start(Context context, WorkspaceItem wsi) //Get our next step, if none is found, archive our item firstStep = wf.getNextStep(context, wfi, firstStep, ActionResult.OUTCOME_COMPLETE); if (firstStep == null) { + // record the submitted provenance message + recordStart(context, wfi.getItem(),null); archive(context, wfi); } else { activateFirstStep(context, wf, firstStep, wfi); @@ -334,7 +336,7 @@ protected void activateFirstStep(Context context, Workflow wf, Step firstStep, X + "item_id=" + wfi.getItem().getID() + "collection_id=" + wfi.getCollection().getID())); - // record the start of the workflow w/provenance message +// record the start of the workflow w/provenance message recordStart(context, wfi.getItem(), firstActionConfig.getProcessingAction()); //Fire an event ! @@ -1187,25 +1189,30 @@ protected void recordStart(Context context, Item myitem, Action action) DCDate now = DCDate.getCurrent(); // Create provenance description - String provmessage = ""; + StringBuffer provmessage = new StringBuffer(); if (myitem.getSubmitter() != null) { - provmessage = "Submitted by " + myitem.getSubmitter().getFullName() - + " (" + myitem.getSubmitter().getEmail() + ") on " - + now.toString() + " workflow start=" + action.getProvenanceStartId() + "\n"; + provmessage.append("Submitted by ").append(myitem.getSubmitter().getFullName()) + .append(" (").append(myitem.getSubmitter().getEmail()).append(") on ") + .append(now.toString()); } else { // else, null submitter - provmessage = "Submitted by unknown (probably automated) on" - + now.toString() + " workflow start=" + action.getProvenanceStartId() + "\n"; + provmessage.append("Submitted by unknown (probably automated) on") + .append(now.toString()); + } + if (action != null) { + provmessage.append(" workflow start=").append(action.getProvenanceStartId()).append("\n"); + } else { + provmessage.append("\n"); } // add sizes and checksums of bitstreams - provmessage += installItemService.getBitstreamProvenanceMessage(context, myitem); + provmessage.append(installItemService.getBitstreamProvenanceMessage(context, myitem)); // Add message to the DC itemService .addMetadata(context, myitem, MetadataSchemaEnum.DC.getName(), - "description", "provenance", "en", provmessage); + "description", "provenance", "en", provmessage.toString()); itemService.update(context, myitem); } From 103c8ee75771d3d9e58e530b8855d07cc14598c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ad=C3=A1n=20Rom=C3=A1n=20Ruiz?= Date: Fri, 6 Oct 2023 17:27:53 +0200 Subject: [PATCH 428/510] 8968 - added custom StringEscapper --- .../org/dspace/util/StringEscapeUtils.java | 49 +++++++++++++++++++ .../repository/RequestItemRepository.java | 6 ++- 2 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 dspace-api/src/main/java/org/dspace/util/StringEscapeUtils.java diff --git a/dspace-api/src/main/java/org/dspace/util/StringEscapeUtils.java b/dspace-api/src/main/java/org/dspace/util/StringEscapeUtils.java new file mode 100644 index 000000000000..86010a5c19f1 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/util/StringEscapeUtils.java @@ -0,0 +1,49 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.util; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.apache.commons.text.translate.AggregateTranslator; +import org.apache.commons.text.translate.CharSequenceTranslator; +import org.apache.commons.text.translate.EntityArrays; +import org.apache.commons.text.translate.LookupTranslator; + +public class StringEscapeUtils extends org.apache.commons.text.StringEscapeUtils { + public static final CharSequenceTranslator ESCAPE_MAIL; + static { + final Map escapeMailMap = new HashMap<>(); + escapeMailMap.put("#", "#"); + ESCAPE_MAIL = new AggregateTranslator( + new LookupTranslator(EntityArrays.BASIC_ESCAPE), + new LookupTranslator(EntityArrays.APOS_ESCAPE), + new LookupTranslator(Collections.unmodifiableMap(escapeMailMap)) + ); + } + + /** + * Escapes the characters in a {@code String} using custom rules to avoid XSS attacks. + * + *

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

    + * + *

    Example:

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

    Example:

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

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

    - * - *

    Example:

    - *
    -     * input string: 
    lá lé lí ló LÚ pingüino & yo #
    !!" - * output string: <div attr="*x" onblur="alert(1)*"> lá lé lí ló LÚ - * pingüino & yo # </div>!! - *
    - * - * @param input String to escape values in, may be null - * @return String with escaped values, {@code null} if null string input - */ - public static final String escapeMail(final String input) { - return ESCAPE_MAIL.translate(input); - } -} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RequestItemRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RequestItemRepository.java index 863a5c414666..945afe16e82c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RequestItemRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RequestItemRepository.java @@ -42,13 +42,13 @@ import org.dspace.core.Context; import org.dspace.eperson.EPerson; import org.dspace.services.ConfigurationService; -import org.dspace.util.StringEscapeUtils; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Component; - +import org.springframework.web.util.HtmlUtils; /** * Component to expose item requests. * @@ -173,11 +173,11 @@ public RequestItemRest createAndReturn(Context ctx) username = user.getFullName(); } else { // An anonymous session may provide a name. // Escape username to evade nasty XSS attempts - username = StringEscapeUtils.escapeMail(rir.getRequestName()); + username = HtmlUtils.htmlEscape(rir.getRequestName(),"UTF-8"); } // Requester's message text, escaped to evade nasty XSS attempts - String message = StringEscapeUtils.escapeMail(rir.getRequestMessage()); + String message = HtmlUtils.htmlEscape(rir.getRequestMessage(),"UTF-8"); // Create the request. String token; From d12fbe2c340e18e42dba4380ee9976bccb4ca421 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ad=C3=A1n=20Rom=C3=A1n=20Ruiz?= Date: Mon, 9 Oct 2023 18:18:35 +0200 Subject: [PATCH 431/510] checkstiye --- .../org/dspace/app/rest/repository/RequestItemRepository.java | 1 - 1 file changed, 1 deletion(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RequestItemRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RequestItemRepository.java index 945afe16e82c..f45dbee66f34 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RequestItemRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RequestItemRepository.java @@ -42,7 +42,6 @@ import org.dspace.core.Context; import org.dspace.eperson.EPerson; import org.dspace.services.ConfigurationService; - import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; From a1248074681a7bc4603176fb3e7d989b91edcbcd Mon Sep 17 00:00:00 2001 From: "Gantner, Florian Klaus" Date: Tue, 10 Oct 2023 16:19:11 +0200 Subject: [PATCH 432/510] quote Pattern for thumbnail resolution constructed from bitstream filename --- .../dspace/content/BitstreamServiceImpl.java | 2 +- .../app/rest/BitstreamRestRepositoryIT.java | 47 +++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/content/BitstreamServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/BitstreamServiceImpl.java index cc89cea33a25..7433338ad94c 100644 --- a/dspace-api/src/main/java/org/dspace/content/BitstreamServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/BitstreamServiceImpl.java @@ -403,7 +403,7 @@ public Bitstream getFirstBitstream(Item item, String bundleName) throws SQLExcep @Override public Bitstream getThumbnail(Context context, Bitstream bitstream) throws SQLException { - Pattern pattern = Pattern.compile("^" + bitstream.getName() + ".([^.]+)$"); + Pattern pattern = Pattern.compile("^" + Pattern.quote(bitstream.getName()) + ".([^.]+)$"); for (Bundle bundle : bitstream.getBundles()) { for (Item item : bundle.getItems()) { diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java index bc276557dfb3..864acf1a56f5 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java @@ -1695,6 +1695,53 @@ public void thumbnailEndpointTest() throws Exception { .andExpect(jsonPath("$.type", is("bitstream"))); } + @Test + public void thumbnailEndpointTestWithSpecialCharactersInFileName() throws Exception { + // Given an Item + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1").build(); + + Item item = ItemBuilder.createItem(context, col1) + .withTitle("Test item -- thumbnail") + .withIssueDate("2017-10-17") + .withAuthor("Smith, Donald").withAuthor("Doe, John") + .build(); + + Bundle originalBundle = BundleBuilder.createBundle(context, item) + .withName(Constants.DEFAULT_BUNDLE_NAME) + .build(); + Bundle thumbnailBundle = BundleBuilder.createBundle(context, item) + .withName("THUMBNAIL") + .build(); + + InputStream is = IOUtils.toInputStream("dummy", "utf-8"); + + // With an ORIGINAL Bitstream & matching THUMBNAIL Bitstream containing special characters in filenames + Bitstream bitstream = BitstreamBuilder.createBitstream(context, originalBundle, is) + .withName("test (2023) file.pdf") + .withMimeType("application/pdf") + .build(); + Bitstream thumbnail = BitstreamBuilder.createBitstream(context, thumbnailBundle, is) + .withName("test (2023) file.pdf.jpg") + .withMimeType("image/jpeg") + .build(); + + context.restoreAuthSystemState(); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + + getClient(tokenAdmin).perform(get("/api/core/bitstreams/" + bitstream.getID() + "/thumbnail")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.uuid", Matchers.is(thumbnail.getID().toString()))) + .andExpect(jsonPath("$.type", is("bitstream"))); + } + @Test public void thumbnailEndpointMultipleThumbnailsWithPrimaryBitstreamTest() throws Exception { // Given an Item From 94ee9d04033c6f15abdea035a0650177af1a52af Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 10 Oct 2023 21:19:14 +0000 Subject: [PATCH 433/510] Bump org.eclipse.jetty:jetty-http Bumps [org.eclipse.jetty:jetty-http](https://github.com/eclipse/jetty.project) from 9.4.52.v20230823 to 9.4.53.v20231009. - [Release notes](https://github.com/eclipse/jetty.project/releases) - [Commits](https://github.com/eclipse/jetty.project/compare/jetty-9.4.52.v20230823...jetty-9.4.53.v20231009) --- updated-dependencies: - dependency-name: org.eclipse.jetty:jetty-http dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index aeb15fbefa8e..6feb81c8844a 100644 --- a/pom.xml +++ b/pom.xml @@ -37,7 +37,7 @@ 2.3.8 1.1.1 - 9.4.52.v20230823 + 9.4.53.v20231009 2.20.0 2.0.29 1.19.0 From 669ff343503539aa6fc8b23600989ab958a403b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marie-H=C3=A9l=C3=A8ne=20V=C3=A9zina?= Date: Wed, 11 Oct 2023 09:49:35 -0400 Subject: [PATCH 434/510] oai_openaire.xsl : change resourceTypeGeneral for thesis Thesis are "Literature" resource type (resourceTypeGeneral), not "other research product" ref: https://github.com/openaire/guidelines-literature-repositories/issues/43#issuecomment-1318262914 and https://api.openaire.eu/vocabularies/dnet:result_typologies/publication --- .../crosswalks/oai/metadataFormats/oai_openaire.xsl | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/dspace/config/crosswalks/oai/metadataFormats/oai_openaire.xsl b/dspace/config/crosswalks/oai/metadataFormats/oai_openaire.xsl index 3a1d75eb56c6..16c63c9c1a13 100644 --- a/dspace/config/crosswalks/oai/metadataFormats/oai_openaire.xsl +++ b/dspace/config/crosswalks/oai/metadataFormats/oai_openaire.xsl @@ -1432,6 +1432,18 @@ literature + + literature + + + literature + + + literature + + + literature + dataset From a9bcc0c223d0219f464d986d7b7c66b3c4cbc39c Mon Sep 17 00:00:00 2001 From: "Gantner, Florian Klaus" Date: Thu, 12 Oct 2023 17:58:13 +0200 Subject: [PATCH 435/510] check null value of bitstream name before quoting name for regex --- .../main/java/org/dspace/content/BitstreamServiceImpl.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/content/BitstreamServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/BitstreamServiceImpl.java index 7433338ad94c..92acce676584 100644 --- a/dspace-api/src/main/java/org/dspace/content/BitstreamServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/BitstreamServiceImpl.java @@ -403,7 +403,9 @@ public Bitstream getFirstBitstream(Item item, String bundleName) throws SQLExcep @Override public Bitstream getThumbnail(Context context, Bitstream bitstream) throws SQLException { - Pattern pattern = Pattern.compile("^" + Pattern.quote(bitstream.getName()) + ".([^.]+)$"); + Pattern pattern = Pattern.compile("^" + + (bitstream.getName() != null ? Pattern.quote(bitstream.getName()) : bitstream.getName()) + + ".([^.]+)$"); for (Bundle bundle : bitstream.getBundles()) { for (Item item : bundle.getItems()) { From f25b6d479bad15bbd2353c877286a2245c5d6543 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Thu, 12 Oct 2023 16:13:28 -0400 Subject: [PATCH 436/510] Define required _version_ field and its fieldType. --- dspace/solr/authority/conf/schema.xml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/dspace/solr/authority/conf/schema.xml b/dspace/solr/authority/conf/schema.xml index 6c32819302d0..511dbabd477c 100644 --- a/dspace/solr/authority/conf/schema.xml +++ b/dspace/solr/authority/conf/schema.xml @@ -87,9 +87,20 @@ + + + From 65a17d4390aeab69c191fb75559646aec9dda512 Mon Sep 17 00:00:00 2001 From: Toni Prieto Date: Wed, 27 Sep 2023 20:46:39 +0200 Subject: [PATCH 437/510] Allow users with write permission to view hidden metadata --- .../java/org/dspace/app/rest/converter/ItemConverter.java | 4 ++-- 1 file changed, 2 insertions(+), 2 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 77532249ad41..fc64b66e8a16 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 @@ -67,7 +67,7 @@ public ItemRest convert(Item obj, Projection projection) { * Overrides the parent method to include virtual metadata * @param context The context * @param obj The object of which the filtered metadata will be retrieved - * @return A list of object metadata (including virtual metadata) filtered based on the the hidden metadata + * @return A list of object metadata (including virtual metadata) filtered based on the hidden metadata * configuration */ @Override @@ -79,7 +79,7 @@ public MetadataValueList getPermissionFilteredMetadata(Context context, Item obj Objects.isNull(context.getCurrentUser()) || !authorizeService.isAdmin(context))) { return new MetadataValueList(new ArrayList()); } - if (context != null && authorizeService.isAdmin(context)) { + if (context != null && (authorizeService.isAdmin(context) || itemService.canEdit(context, obj))) { return new MetadataValueList(fullList); } for (MetadataValue mv : fullList) { From df7f6e9f4082e5aef3392932f8a87177ac202655 Mon Sep 17 00:00:00 2001 From: Toni Prieto Date: Fri, 13 Oct 2023 11:15:19 +0200 Subject: [PATCH 438/510] Test modification: allow users with write rights to see hidden metadata --- .../java/org/dspace/app/rest/ItemRestRepositoryIT.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java index 08463fb22280..62917b2cde14 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 @@ -3021,10 +3021,10 @@ public void testHiddenMetadataForUserWithWriteRights() throws Exception { String token = getAuthToken(eperson.getEmail(), password); getClient(token).perform(get("/api/core/items/" + item.getID())) - .andExpect(status().isOk()) - .andExpect(jsonPath("$", ItemMatcher.matchItemProperties(item))) - .andExpect(jsonPath("$.metadata", matchMetadata("dc.title", "Public item 1"))) - .andExpect(jsonPath("$.metadata", matchMetadataDoesNotExist("dc.description.provenance"))); + .andExpect(status().isOk()) + .andExpect(jsonPath("$", ItemMatcher.matchItemProperties(item))) + .andExpect(jsonPath("$.metadata", matchMetadata("dc.title", "Public item 1"))) + .andExpect(jsonPath("$.metadata", matchMetadata("dc.description.provenance", "Provenance data"))); } From 94822b50af4098d990d63e27bb3906cfa9c0ec37 Mon Sep 17 00:00:00 2001 From: Toni Prieto Date: Wed, 26 Jul 2023 12:31:43 +0200 Subject: [PATCH 439/510] Change the database mode to READ_ONLY during the indexing by discovery consumer (IndexEventConsumer) --- .../main/java/org/dspace/discovery/IndexEventConsumer.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/dspace-api/src/main/java/org/dspace/discovery/IndexEventConsumer.java b/dspace-api/src/main/java/org/dspace/discovery/IndexEventConsumer.java index 4ff1f3134484..d670f5b339ec 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/IndexEventConsumer.java +++ b/dspace-api/src/main/java/org/dspace/discovery/IndexEventConsumer.java @@ -201,6 +201,10 @@ public void consume(Context ctx, Event event) throws Exception { @Override public void end(Context ctx) throws Exception { + // Change the mode to readonly to improve the performance + Context.Mode originalMode = ctx.getCurrentMode(); + ctx.setMode(Context.Mode.READ_ONLY); + try { for (String uid : uniqueIdsToDelete) { try { @@ -231,6 +235,8 @@ public void end(Context ctx) throws Exception { createdItemsToUpdate.clear(); } } + + ctx.setMode(originalMode); } private void indexObject(Context ctx, IndexableObject iu, boolean preDb) throws SQLException { From c33d3fa87d6c29533d379939bd23b29ff3d9b5c9 Mon Sep 17 00:00:00 2001 From: Toni Prieto Date: Fri, 28 Jul 2023 09:19:37 +0200 Subject: [PATCH 440/510] Add functions to do a manual flush of the db session and call flush before change to READ_ONLY mode to be sure we index the current object --- .../src/main/java/org/dspace/core/Context.java | 10 ++++++++++ .../src/main/java/org/dspace/core/DBConnection.java | 8 ++++++++ .../java/org/dspace/core/HibernateDBConnection.java | 13 +++++++++++++ .../org/dspace/discovery/IndexEventConsumer.java | 10 +++++++--- 4 files changed, 38 insertions(+), 3 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/core/Context.java b/dspace-api/src/main/java/org/dspace/core/Context.java index 82b39dd2dfc7..6382e724301d 100644 --- a/dspace-api/src/main/java/org/dspace/core/Context.java +++ b/dspace-api/src/main/java/org/dspace/core/Context.java @@ -880,6 +880,16 @@ public void uncacheEntity(E entity) throws SQLExcep dbConnection.uncacheEntity(entity); } + /** + * Flush the current Session to synchronizes the in-memory state of the Session + * with the database (write changes to the database) + * + * @throws SQLException passed through. + */ + public void flushDBChanges() throws SQLException { + dbConnection.flushSession(); + } + public Boolean getCachedAuthorizationResult(DSpaceObject dspaceObject, int action, EPerson eperson) { if (isReadOnly()) { return readOnlyCache.getCachedAuthorizationResult(dspaceObject, action, eperson); diff --git a/dspace-api/src/main/java/org/dspace/core/DBConnection.java b/dspace-api/src/main/java/org/dspace/core/DBConnection.java index cb5825eec1d9..66e4a65dbfe1 100644 --- a/dspace-api/src/main/java/org/dspace/core/DBConnection.java +++ b/dspace-api/src/main/java/org/dspace/core/DBConnection.java @@ -148,4 +148,12 @@ public interface DBConnection { * @throws java.sql.SQLException passed through. */ public void uncacheEntity(E entity) throws SQLException; + + /** + * Do a manual flush. This synchronizes the in-memory state of the Session + * with the database (write changes to the database) + * + * @throws SQLException passed through. + */ + public void flushSession() throws SQLException; } diff --git a/dspace-api/src/main/java/org/dspace/core/HibernateDBConnection.java b/dspace-api/src/main/java/org/dspace/core/HibernateDBConnection.java index 3321e4d837e5..b371af80eede 100644 --- a/dspace-api/src/main/java/org/dspace/core/HibernateDBConnection.java +++ b/dspace-api/src/main/java/org/dspace/core/HibernateDBConnection.java @@ -337,4 +337,17 @@ public void uncacheEntity(E entity) throws SQLExcep } } } + + /** + * Do a manual flush. This synchronizes the in-memory state of the Session + * with the database (write changes to the database) + * + * @throws SQLException passed through. + */ + @Override + public void flushSession() throws SQLException { + if (getSession().isDirty()) { + getSession().flush(); + } + } } diff --git a/dspace-api/src/main/java/org/dspace/discovery/IndexEventConsumer.java b/dspace-api/src/main/java/org/dspace/discovery/IndexEventConsumer.java index d670f5b339ec..fd255e9ffc17 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/IndexEventConsumer.java +++ b/dspace-api/src/main/java/org/dspace/discovery/IndexEventConsumer.java @@ -201,7 +201,11 @@ public void consume(Context ctx, Event event) throws Exception { @Override public void end(Context ctx) throws Exception { - // Change the mode to readonly to improve the performance + // Change the mode to readonly to improve performance + // First, we flush the changes to database, if session is dirty, has pending changes + // to synchronize with database, without this flush it could index an old version of + // the object + ctx.flushDBChanges(); Context.Mode originalMode = ctx.getCurrentMode(); ctx.setMode(Context.Mode.READ_ONLY); @@ -234,9 +238,9 @@ public void end(Context ctx) throws Exception { uniqueIdsToDelete.clear(); createdItemsToUpdate.clear(); } - } - ctx.setMode(originalMode); + ctx.setMode(originalMode); + } } private void indexObject(Context ctx, IndexableObject iu, boolean preDb) throws SQLException { From 00a65312ccb52481cd72653b4c5465b7d16c760e Mon Sep 17 00:00:00 2001 From: Toni Prieto Date: Fri, 13 Oct 2023 20:52:08 +0200 Subject: [PATCH 441/510] Flush database changes after switching to READONLY mode --- .../main/java/org/dspace/core/Context.java | 19 +++++++++---------- .../dspace/discovery/IndexEventConsumer.java | 4 ---- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/core/Context.java b/dspace-api/src/main/java/org/dspace/core/Context.java index 6382e724301d..09b9c4a32dd3 100644 --- a/dspace-api/src/main/java/org/dspace/core/Context.java +++ b/dspace-api/src/main/java/org/dspace/core/Context.java @@ -810,6 +810,15 @@ public void setMode(Mode newMode) { readOnlyCache.clear(); } + // When going to READ_ONLY, flush database changes to ensure that the current data is retrieved + if (newMode == Mode.READ_ONLY && mode != Mode.READ_ONLY) { + try { + dbConnection.flushSession(); + } catch (SQLException ex) { + log.warn("Unable to flush database changes after switching to READ_ONLY mode", ex); + } + } + //save the new mode mode = newMode; } @@ -880,16 +889,6 @@ public void uncacheEntity(E entity) throws SQLExcep dbConnection.uncacheEntity(entity); } - /** - * Flush the current Session to synchronizes the in-memory state of the Session - * with the database (write changes to the database) - * - * @throws SQLException passed through. - */ - public void flushDBChanges() throws SQLException { - dbConnection.flushSession(); - } - public Boolean getCachedAuthorizationResult(DSpaceObject dspaceObject, int action, EPerson eperson) { if (isReadOnly()) { return readOnlyCache.getCachedAuthorizationResult(dspaceObject, action, eperson); diff --git a/dspace-api/src/main/java/org/dspace/discovery/IndexEventConsumer.java b/dspace-api/src/main/java/org/dspace/discovery/IndexEventConsumer.java index fd255e9ffc17..847e235fa9fa 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/IndexEventConsumer.java +++ b/dspace-api/src/main/java/org/dspace/discovery/IndexEventConsumer.java @@ -202,10 +202,6 @@ public void consume(Context ctx, Event event) throws Exception { public void end(Context ctx) throws Exception { // Change the mode to readonly to improve performance - // First, we flush the changes to database, if session is dirty, has pending changes - // to synchronize with database, without this flush it could index an old version of - // the object - ctx.flushDBChanges(); Context.Mode originalMode = ctx.getCurrentMode(); ctx.setMode(Context.Mode.READ_ONLY); From 03496c36d4d47138bcd51badf8daca720d4cc484 Mon Sep 17 00:00:00 2001 From: Toni Prieto Date: Fri, 13 Oct 2023 21:21:35 +0200 Subject: [PATCH 442/510] Add test to check that user with read rights can see hidden metadata --- .../dspace/app/rest/ItemRestRepositoryIT.java | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) 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 62917b2cde14..b25b323fd1f0 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 @@ -14,6 +14,7 @@ import static org.dspace.app.rest.matcher.MetadataMatcher.matchMetadataDoesNotExist; import static org.dspace.builder.OrcidHistoryBuilder.createOrcidHistory; import static org.dspace.builder.OrcidQueueBuilder.createOrcidQueue; +import static org.dspace.core.Constants.READ; import static org.dspace.core.Constants.WRITE; import static org.dspace.orcid.OrcidOperation.DELETE; import static org.dspace.profile.OrcidEntitySyncPreference.ALL; @@ -3028,6 +3029,39 @@ public void testHiddenMetadataForUserWithWriteRights() throws Exception { } + @Test + public void testHiddenMetadataForUserWithReadRights() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build(); + + Item item = ItemBuilder.createItem(context, col1) + .withTitle("Public item 1") + .withProvenanceData("Provenance data") + .build(); + + context.restoreAuthSystemState(); + + + ResourcePolicyBuilder.createResourcePolicy(context) + .withUser(eperson) + .withAction(READ) + .withDspaceObject(item) + .build(); + + String token = getAuthToken(eperson.getEmail(), password); + + getClient(token).perform(get("/api/core/items/" + item.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", ItemMatcher.matchItemProperties(item))) + .andExpect(jsonPath("$.metadata", matchMetadata("dc.title", "Public item 1"))) + .andExpect(jsonPath("$.metadata", matchMetadataDoesNotExist("dc.description.provenance"))); + + } + @Test public void testEntityTypePerson() throws Exception { context.turnOffAuthorisationSystem(); From d19a9599b5f08a567c93d2e167e219673518fb78 Mon Sep 17 00:00:00 2001 From: Toni Prieto Date: Fri, 13 Oct 2023 20:59:33 +0200 Subject: [PATCH 443/510] Add test to check retrieving of policies after changing mode to READ_ONLY --- .../java/org/dspace/core/ContextModeIT.java | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 dspace-api/src/test/java/org/dspace/core/ContextModeIT.java diff --git a/dspace-api/src/test/java/org/dspace/core/ContextModeIT.java b/dspace-api/src/test/java/org/dspace/core/ContextModeIT.java new file mode 100644 index 000000000000..f689551f1a8c --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/core/ContextModeIT.java @@ -0,0 +1,42 @@ +/** + * 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.core; + +import static org.junit.Assert.assertEquals; + +import java.util.List; + +import org.dspace.AbstractIntegrationTestWithDatabase; +import org.dspace.authorize.ResourcePolicy; +import org.dspace.authorize.factory.AuthorizeServiceFactory; +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.builder.CommunityBuilder; +import org.junit.Test; + +public class ContextModeIT extends AbstractIntegrationTestWithDatabase { + + AuthorizeService authorizeService = AuthorizeServiceFactory.getInstance().getAuthorizeService(); + + @Test + public void testGetPoliciesNewCommunityAfterReadOnlyModeChange() throws Exception { + + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + context.restoreAuthSystemState(); + + context.setMode(Context.Mode.READ_ONLY); + + List policies = authorizeService.getPoliciesActionFilter(context, parentCommunity, + Constants.READ); + + assertEquals("Should return the default anonymous group read policy", 1, policies.size()); + } + +} From ea6307dcc68a75c935049a02022145691693cff4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ad=C3=A1n=20Rom=C3=A1n=20Ruiz?= Date: Mon, 16 Oct 2023 09:33:54 +0200 Subject: [PATCH 444/510] 8585 - added provenance to metadata-import and itemImport --- .../dspace/app/bulkedit/MetadataImport.java | 4 ++++ .../app/itemimport/ItemImportServiceImpl.java | 4 ++++ .../content/InstallItemServiceImpl.java | 24 +++++++++++++++++++ .../content/service/InstallItemService.java | 11 +++++++++ 4 files changed, 43 insertions(+) diff --git a/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImport.java b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImport.java index 4161bbb4d817..983843a1dc0c 100644 --- a/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImport.java +++ b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImport.java @@ -578,6 +578,10 @@ public List runImport(Context c, boolean change, wfItem = workflowService.startWithoutNotify(c, wsItem); } } else { + // Add provenance info + String provenance = installItemService.getSubmittedByProvenanceMessage(c, wsItem.getItem()); + itemService.addMetadata(c, item, MetadataSchemaEnum.DC.getName(), + "description", "provenance", "en", provenance); // Install the item installItemService.installItem(c, wsItem); } 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 4148232cf3ba..255f4bdcbb15 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 @@ -774,6 +774,10 @@ protected Item addItem(Context c, List mycollections, String path, // put item in system if (!isTest) { try { + // Add provenance info + String provenance = installItemService.getSubmittedByProvenanceMessage(c, wi.getItem()); + itemService.addMetadata(c, wi.getItem(), MetadataSchemaEnum.DC.getName(), + "description", "provenance", "en", provenance); installItemService.installItem(c, wi, myhandle); } catch (Exception e) { workspaceItemService.deleteAll(c, wi); diff --git a/dspace-api/src/main/java/org/dspace/content/InstallItemServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/InstallItemServiceImpl.java index 32c5b92c605b..e3e2025b6188 100644 --- a/dspace-api/src/main/java/org/dspace/content/InstallItemServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/InstallItemServiceImpl.java @@ -271,4 +271,28 @@ public String getBitstreamProvenanceMessage(Context context, Item myitem) return myMessage.toString(); } + + @Override + public String getSubmittedByProvenanceMessage(Context context, Item item) throws SQLException { + // get date + DCDate now = DCDate.getCurrent(); + + // Create provenance description + StringBuffer provmessage = new StringBuffer(); + + if (item.getSubmitter() != null) { + provmessage.append("Submitted by ").append(item.getSubmitter().getFullName()) + .append(" (").append(item.getSubmitter().getEmail()).append(") on ") + .append(now.toString()); + } else { + // else, null submitter + provmessage.append("Submitted by unknown (probably automated) on") + .append(now.toString()); + } + provmessage.append("\n"); + + // add sizes and checksums of bitstreams + provmessage.append(getBitstreamProvenanceMessage(context, item)); + return provmessage.toString(); + } } diff --git a/dspace-api/src/main/java/org/dspace/content/service/InstallItemService.java b/dspace-api/src/main/java/org/dspace/content/service/InstallItemService.java index 67ac2e20499c..d00c62cc91d8 100644 --- a/dspace-api/src/main/java/org/dspace/content/service/InstallItemService.java +++ b/dspace-api/src/main/java/org/dspace/content/service/InstallItemService.java @@ -83,4 +83,15 @@ public Item restoreItem(Context c, InProgressSubmission is, public String getBitstreamProvenanceMessage(Context context, Item myitem) throws SQLException; + /** + * Generate provenance description of direct item submission (not through workflow). + * + * @param context context + * @param item the item to generate description for + * @return provenance description + * @throws SQLException if database error + */ + public String getSubmittedByProvenanceMessage(Context context, Item item) + throws SQLException;; + } From c5e2e4fac755a44ea3761f2d164c67844f25605c Mon Sep 17 00:00:00 2001 From: frabacche Date: Mon, 16 Oct 2023 10:46:32 +0200 Subject: [PATCH 445/510] CST-5249 configuration review --- .../V8.0_2023.08.07__qaevent_processed.sql | 19 ------------------- .../app/rest/matcher/QAEventMatcher.java | 2 +- dspace/config/modules/qaevents.cfg | 3 +-- 3 files changed, 2 insertions(+), 22 deletions(-) delete mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V8.0_2023.08.07__qaevent_processed.sql diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V8.0_2023.08.07__qaevent_processed.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V8.0_2023.08.07__qaevent_processed.sql deleted file mode 100644 index 5c3f0fac73ce..000000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V8.0_2023.08.07__qaevent_processed.sql +++ /dev/null @@ -1,19 +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/ --- - -CREATE TABLE qaevent_processed ( - qaevent_id VARCHAR(255) NOT NULL, - qaevent_timestamp TIMESTAMP NULL, - eperson_uuid UUID NULL, - item_uuid UUID NULL, - CONSTRAINT qaevent_pk PRIMARY KEY (qaevent_id), - CONSTRAINT eperson_uuid_fkey FOREIGN KEY (eperson_uuid) REFERENCES eperson (uuid), - CONSTRAINT item_uuid_fkey FOREIGN KEY (item_uuid) REFERENCES item (uuid) -); - -CREATE INDEX item_uuid_idx ON qaevent_processed(item_uuid); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/QAEventMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/QAEventMatcher.java index 68359023e300..b85746c64c6b 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/QAEventMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/QAEventMatcher.java @@ -99,7 +99,7 @@ private static String calculateOpenairePidHref(String type, String value) { hrefPrefix = "https://arxiv.org/abs/"; break; case "handle": - hrefPrefix = "https://arxiv.org/abs/"; + hrefPrefix = "https://hdl.handle.net/"; break; case "urn": hrefPrefix = ""; diff --git a/dspace/config/modules/qaevents.cfg b/dspace/config/modules/qaevents.cfg index d9a6fba9626e..da5080d58974 100644 --- a/dspace/config/modules/qaevents.cfg +++ b/dspace/config/modules/qaevents.cfg @@ -5,8 +5,7 @@ #---------------------------------------------------------------# qaevents.solr.server = ${solr.server}/${solr.multicorePrefix}qaevent # A POST to these url(s) will be done to notify oaire of decision taken for each qaevents -qaevents.openaire.acknowledge-url = https://beta.api-broker.openaire.eu/feedback/events -#qaevents.openaire.acknowledge-url +# qaevents.openaire.acknowledge-url = https://beta.api-broker.openaire.eu/feedback/events # The list of the supported events incoming from openaire (see also dspace/config/spring/api/qaevents.xml) # add missing abstract suggestion From 4fba787322803cc36ef267f0d6913b92c1eaeca4 Mon Sep 17 00:00:00 2001 From: Alan Orth Date: Tue, 17 Oct 2023 11:34:04 +0300 Subject: [PATCH 446/510] dspace-api: fix misaligned comment --- .../java/org/dspace/xmlworkflow/XmlWorkflowServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/XmlWorkflowServiceImpl.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/XmlWorkflowServiceImpl.java index 51292fd4773a..bc91a1fd9298 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/XmlWorkflowServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/XmlWorkflowServiceImpl.java @@ -336,7 +336,7 @@ protected void activateFirstStep(Context context, Workflow wf, Step firstStep, X + "item_id=" + wfi.getItem().getID() + "collection_id=" + wfi.getCollection().getID())); -// record the start of the workflow w/provenance message + // record the start of the workflow w/provenance message recordStart(context, wfi.getItem(), firstActionConfig.getProcessingAction()); //Fire an event ! From 6504d749b91508096300e4545069a0554eb5934b Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Tue, 17 Oct 2023 16:28:37 +0200 Subject: [PATCH 447/510] [DURACOM-192] Authentication Method related special groups are put in claim set even if a different authentication method is used --- .../authenticate/AuthenticationMethod.java | 16 ++++++++++++++++ .../authenticate/AuthenticationServiceImpl.java | 13 +++++++++---- .../dspace/authenticate/IPAuthentication.java | 5 +++++ 3 files changed, 30 insertions(+), 4 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/authenticate/AuthenticationMethod.java b/dspace-api/src/main/java/org/dspace/authenticate/AuthenticationMethod.java index 274779e92877..500ee04a979b 100644 --- a/dspace-api/src/main/java/org/dspace/authenticate/AuthenticationMethod.java +++ b/dspace-api/src/main/java/org/dspace/authenticate/AuthenticationMethod.java @@ -153,6 +153,22 @@ public boolean allowSetPassword(Context context, public List getSpecialGroups(Context context, HttpServletRequest request) throws SQLException; + /** + * Returns true if the special groups returned by + * {@link org.dspace.authenticate.AuthenticationMethod#getSpecialGroups(Context, HttpServletRequest)} + * should be implicitly be added to the groups related to the current user. By + * default this is true if the authentication method is the actual + * authentication mechanism used by the user. + * @param context A valid DSpace context. + * @param request The request that started this operation, or null if not + * applicable. + * @return true is the special groups must be considered, false + * otherwise + */ + public default boolean areSpecialGroupsApplicable(Context context, HttpServletRequest request) { + return getName().equals(context.getAuthenticationMethod()); + } + /** * Authenticate the given or implicit credentials. * This is the heart of the authentication method: test the diff --git a/dspace-api/src/main/java/org/dspace/authenticate/AuthenticationServiceImpl.java b/dspace-api/src/main/java/org/dspace/authenticate/AuthenticationServiceImpl.java index a9449b87d4e3..1d67da37ecb3 100644 --- a/dspace-api/src/main/java/org/dspace/authenticate/AuthenticationServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/authenticate/AuthenticationServiceImpl.java @@ -179,10 +179,15 @@ public List getSpecialGroups(Context context, int totalLen = 0; for (AuthenticationMethod method : getAuthenticationMethodStack()) { - List gl = method.getSpecialGroups(context, request); - if (gl.size() > 0) { - result.addAll(gl); - totalLen += gl.size(); + + if (method.areSpecialGroupsApplicable(context, request)) { + + List gl = method.getSpecialGroups(context, request); + if (gl.size() > 0) { + result.addAll(gl); + totalLen += gl.size(); + } + } } diff --git a/dspace-api/src/main/java/org/dspace/authenticate/IPAuthentication.java b/dspace-api/src/main/java/org/dspace/authenticate/IPAuthentication.java index 3b2366034489..0c2be211a532 100644 --- a/dspace-api/src/main/java/org/dspace/authenticate/IPAuthentication.java +++ b/dspace-api/src/main/java/org/dspace/authenticate/IPAuthentication.java @@ -252,6 +252,11 @@ public List getSpecialGroups(Context context, HttpServletRequest request) return groups; } + @Override + public boolean areSpecialGroupsApplicable(Context context, HttpServletRequest request) { + return true; + } + @Override public int authenticate(Context context, String username, String password, String realm, HttpServletRequest request) throws SQLException { From fa39251071156a6eeb1030000f50761663e128e2 Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Wed, 18 Oct 2023 12:45:00 +0200 Subject: [PATCH 448/510] [DURACOM-192] Added test --- .../rest/AuthenticationRestControllerIT.java | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthenticationRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthenticationRestControllerIT.java index 69e70dbb087e..1da807ad7180 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthenticationRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthenticationRestControllerIT.java @@ -8,6 +8,7 @@ package org.dspace.app.rest; import static java.lang.Thread.sleep; +import static org.dspace.app.rest.matcher.GroupMatcher.matchGroupWithName; import static org.dspace.app.rest.utils.RegexUtils.REGEX_UUID; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.endsWith; @@ -1641,6 +1642,71 @@ public void testOrcidLoginURL() throws Exception { } } + @Test + public void testAreSpecialGroupsApplicable() throws Exception { + context.turnOffAuthorisationSystem(); + + GroupBuilder.createGroup(context) + .withName("specialGroupPwd") + .build(); + GroupBuilder.createGroup(context) + .withName("specialGroupShib") + .build(); + + configurationService.setProperty("plugin.sequence.org.dspace.authenticate.AuthenticationMethod", SHIB_AND_PASS); + configurationService.setProperty("authentication-password.login.specialgroup", "specialGroupPwd"); + configurationService.setProperty("authentication-shibboleth.role.faculty", "specialGroupShib"); + configurationService.setProperty("authentication-shibboleth.default-roles", "faculty"); + + context.restoreAuthSystemState(); + + String passwordToken = getAuthToken(eperson.getEmail(), password); + + getClient(passwordToken).perform(get("/api/authn/status").param("projection", "full")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", AuthenticationStatusMatcher.matchFullEmbeds())) + .andExpect(jsonPath("$", AuthenticationStatusMatcher.matchLinks())) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$.okay", is(true))) + .andExpect(jsonPath("$.authenticated", is(true))) + .andExpect(jsonPath("$.authenticationMethod", is("password"))) + .andExpect(jsonPath("$.type", is("status"))) + .andExpect(jsonPath("$._links.specialGroups.href", startsWith(REST_SERVER_URL))) + .andExpect(jsonPath("$._embedded.specialGroups._embedded.specialGroups", + Matchers.containsInAnyOrder(matchGroupWithName("specialGroupPwd")))); + + getClient(passwordToken).perform(get("/api/authn/status/specialGroups").param("projection", "full")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.specialGroups", + Matchers.containsInAnyOrder(matchGroupWithName("specialGroupPwd")))); + + String shibToken = getClient().perform(post("/api/authn/login") + .requestAttr("SHIB-MAIL", eperson.getEmail()) + .requestAttr("SHIB-SCOPED-AFFILIATION", "faculty;staff")) + .andExpect(status().isOk()) + .andReturn().getResponse().getHeader(AUTHORIZATION_HEADER).replace(AUTHORIZATION_TYPE, ""); + + getClient(shibToken).perform(get("/api/authn/status").param("projection", "full")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", AuthenticationStatusMatcher.matchFullEmbeds())) + .andExpect(jsonPath("$", AuthenticationStatusMatcher.matchLinks())) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$.okay", is(true))) + .andExpect(jsonPath("$.authenticated", is(true))) + .andExpect(jsonPath("$.authenticationMethod", is("shibboleth"))) + .andExpect(jsonPath("$.type", is("status"))) + .andExpect(jsonPath("$._links.specialGroups.href", startsWith(REST_SERVER_URL))) + .andExpect(jsonPath("$._embedded.specialGroups._embedded.specialGroups", + Matchers.containsInAnyOrder(matchGroupWithName("specialGroupShib")))); + + getClient(shibToken).perform(get("/api/authn/status/specialGroups").param("projection", "full")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.specialGroups", + Matchers.containsInAnyOrder(matchGroupWithName("specialGroupShib")))); + } + // Get a short-lived token based on an active login token private String getShortLivedToken(String loginToken) throws Exception { ObjectMapper mapper = new ObjectMapper(); From 47ca74bc4220249b95de9b8e71186277c9ac31ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paulo=20Gra=C3=A7a?= Date: Thu, 19 Oct 2023 08:58:08 +0100 Subject: [PATCH 449/510] unset primary bitstream on bitstream service --- .../main/java/org/dspace/content/BitstreamServiceImpl.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/dspace-api/src/main/java/org/dspace/content/BitstreamServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/BitstreamServiceImpl.java index cc89cea33a25..77f10880ea30 100644 --- a/dspace-api/src/main/java/org/dspace/content/BitstreamServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/BitstreamServiceImpl.java @@ -276,6 +276,10 @@ public void delete(Context context, Bitstream bitstream) throws SQLException, Au //Remove our bitstream from all our bundles final List bundles = bitstream.getBundles(); for (Bundle bundle : bundles) { + //We also need to remove the bitstream id when it's set as bundle's primary bitstream + if(bitstream.equals(bundle.getPrimaryBitstream())) { + bundle.unsetPrimaryBitstreamID(); + } bundle.removeBitstream(bitstream); } From 8a531ad0c7e8fdf09fa9a3870024687e6708a9a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paulo=20Gra=C3=A7a?= Date: Thu, 19 Oct 2023 09:38:01 +0100 Subject: [PATCH 450/510] adding sql expression to fix deleted primary bitstreams from bundle --- ....10.12__Fix-deleted-primary-bitstreams.sql | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2023.10.12__Fix-deleted-primary-bitstreams.sql diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2023.10.12__Fix-deleted-primary-bitstreams.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2023.10.12__Fix-deleted-primary-bitstreams.sql new file mode 100644 index 000000000000..b1739dbd9600 --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2023.10.12__Fix-deleted-primary-bitstreams.sql @@ -0,0 +1,26 @@ +BEGIN; + +-- Remove all primary bitstreams that are marked as deleted +UPDATE bundle +SET primary_bitstream_id = NULL +WHERE primary_bitstream_id IN + ( SELECT bs.uuid + FROM bitstream AS bs + INNER JOIN bundle as bl ON bs.uuid = bl.primary_bitstream_id + WHERE bs.deleted IS TRUE ); + +-- Remove all primary bitstreams that don't make part on bundle's bitstreams +UPDATE bundle +SET primary_bitstream_id = NULL +WHERE primary_bitstream_id IN + ( SELECT bl.primary_bitstream_id + FROM bundle as bl + WHERE bl.primary_bitstream_id IS NOT NULL + AND bl.primary_bitstream_id NOT IN + ( SELECT bitstream_id + FROM bundle2bitstream AS b2b + WHERE b2b.bundle_id = bl.uuid + ) + ); + +COMMIT; \ No newline at end of file From 3255e073fa110a3354f1265853bbf531c677f6ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paulo=20Gra=C3=A7a?= Date: Thu, 19 Oct 2023 09:58:24 +0100 Subject: [PATCH 451/510] add bundle remove authorization --- .../src/main/java/org/dspace/content/BitstreamServiceImpl.java | 1 + 1 file changed, 1 insertion(+) diff --git a/dspace-api/src/main/java/org/dspace/content/BitstreamServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/BitstreamServiceImpl.java index 77f10880ea30..5391dcf389a0 100644 --- a/dspace-api/src/main/java/org/dspace/content/BitstreamServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/BitstreamServiceImpl.java @@ -276,6 +276,7 @@ public void delete(Context context, Bitstream bitstream) throws SQLException, Au //Remove our bitstream from all our bundles final List bundles = bitstream.getBundles(); for (Bundle bundle : bundles) { + authorizeService.authorizeAction(context, bundle, Constants.REMOVE); //We also need to remove the bitstream id when it's set as bundle's primary bitstream if(bitstream.equals(bundle.getPrimaryBitstream())) { bundle.unsetPrimaryBitstreamID(); From 4a05600194fb9be7e19084f3a9106a0152fd0d80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paulo=20Gra=C3=A7a?= Date: Thu, 19 Oct 2023 10:16:38 +0100 Subject: [PATCH 452/510] adding missing bundle REMOVE authorization --- dspace-api/src/test/java/org/dspace/content/BundleTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dspace-api/src/test/java/org/dspace/content/BundleTest.java b/dspace-api/src/test/java/org/dspace/content/BundleTest.java index 13b943b4d6b4..851d8267ea84 100644 --- a/dspace-api/src/test/java/org/dspace/content/BundleTest.java +++ b/dspace-api/src/test/java/org/dspace/content/BundleTest.java @@ -522,6 +522,8 @@ public void testRemoveBitstreamAuthAndUnsetPrimaryBitstreamID() throws IOExcepti doNothing().when(authorizeServiceSpy).authorizeAction(context, item, Constants.WRITE); // Allow Bundle ADD permissions doNothing().when(authorizeServiceSpy).authorizeAction(context, b, Constants.ADD); + // Allow Bundle REMOVE permissions + doNothing().when(authorizeServiceSpy).authorizeAction(context, b, Constants.REMOVE); // Allow Bitstream WRITE permissions doNothing().when(authorizeServiceSpy) .authorizeAction(any(Context.class), any(Bitstream.class), eq(Constants.WRITE)); From caba4bbb96f56c103c4dd8ac9f9fa5863b40e04c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paulo=20Gra=C3=A7a?= Date: Thu, 19 Oct 2023 11:16:26 +0100 Subject: [PATCH 453/510] add missing head style check --- .../V7.6_2023.10.12__Fix-deleted-primary-bitstreams.sql | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2023.10.12__Fix-deleted-primary-bitstreams.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2023.10.12__Fix-deleted-primary-bitstreams.sql index b1739dbd9600..c97d2246578e 100644 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2023.10.12__Fix-deleted-primary-bitstreams.sql +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2023.10.12__Fix-deleted-primary-bitstreams.sql @@ -1,3 +1,11 @@ +-- +-- 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/ +-- + BEGIN; -- Remove all primary bitstreams that are marked as deleted From 74605f159af5e53a3e890f578732a858cef12e51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paulo=20Gra=C3=A7a?= Date: Thu, 19 Oct 2023 11:42:58 +0100 Subject: [PATCH 454/510] fix style errors --- .../src/main/java/org/dspace/content/BitstreamServiceImpl.java | 2 +- dspace-api/src/test/java/org/dspace/content/BundleTest.java | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/BitstreamServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/BitstreamServiceImpl.java index 5391dcf389a0..2746812c1cd5 100644 --- a/dspace-api/src/main/java/org/dspace/content/BitstreamServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/BitstreamServiceImpl.java @@ -278,7 +278,7 @@ public void delete(Context context, Bitstream bitstream) throws SQLException, Au for (Bundle bundle : bundles) { authorizeService.authorizeAction(context, bundle, Constants.REMOVE); //We also need to remove the bitstream id when it's set as bundle's primary bitstream - if(bitstream.equals(bundle.getPrimaryBitstream())) { + if (bitstream.equals(bundle.getPrimaryBitstream())) { bundle.unsetPrimaryBitstreamID(); } bundle.removeBitstream(bitstream); diff --git a/dspace-api/src/test/java/org/dspace/content/BundleTest.java b/dspace-api/src/test/java/org/dspace/content/BundleTest.java index 851d8267ea84..4af64b81cb0c 100644 --- a/dspace-api/src/test/java/org/dspace/content/BundleTest.java +++ b/dspace-api/src/test/java/org/dspace/content/BundleTest.java @@ -517,7 +517,8 @@ public void testRemoveBitstreamAuth() throws SQLException, AuthorizeException, I * Test removeBitstream method and also the unsetPrimaryBitstreamID method, of class Bundle. */ @Test - public void testRemoveBitstreamAuthAndUnsetPrimaryBitstreamID() throws IOException, SQLException, AuthorizeException { + public void testRemoveBitstreamAuthAndUnsetPrimaryBitstreamID() + throws IOException, SQLException, AuthorizeException { // Allow Item WRITE permissions doNothing().when(authorizeServiceSpy).authorizeAction(context, item, Constants.WRITE); // Allow Bundle ADD permissions From c5466c2249c092f6638a7072b57c934d1d3581b5 Mon Sep 17 00:00:00 2001 From: "Gantner, Florian Klaus" Date: Thu, 19 Oct 2023 15:44:03 +0200 Subject: [PATCH 455/510] extract bitstream thumbnail name pattern into own function --- .../java/org/dspace/content/BitstreamServiceImpl.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/BitstreamServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/BitstreamServiceImpl.java index 92acce676584..16532660561d 100644 --- a/dspace-api/src/main/java/org/dspace/content/BitstreamServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/BitstreamServiceImpl.java @@ -403,9 +403,7 @@ public Bitstream getFirstBitstream(Item item, String bundleName) throws SQLExcep @Override public Bitstream getThumbnail(Context context, Bitstream bitstream) throws SQLException { - Pattern pattern = Pattern.compile("^" + - (bitstream.getName() != null ? Pattern.quote(bitstream.getName()) : bitstream.getName()) - + ".([^.]+)$"); + Pattern pattern = getBitstreamNamePattern(bitstream); for (Bundle bundle : bitstream.getBundles()) { for (Item item : bundle.getItems()) { @@ -422,6 +420,13 @@ public Bitstream getThumbnail(Context context, Bitstream bitstream) throws SQLEx return null; } + protected Pattern getBitstreamNamePattern(Bitstream bitstream) { + if (bitstream.getName() != null) { + return Pattern.compile("^" + Pattern.quote(bitstream.getName()) + ".([^.]+)$"); + } + return Pattern.compile("^" + bitstream.getName() + ".([^.]+)$"); + } + @Override public BitstreamFormat getFormat(Context context, Bitstream bitstream) throws SQLException { if (bitstream.getBitstreamFormat() == null) { From 11a08f1ac0a9b75bf3f2869d3760b2f0e229aefe Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 19 Oct 2023 21:57:32 +0000 Subject: [PATCH 456/510] Bump org.json:json from 20230227 to 20231013 in /dspace-api Bumps [org.json:json](https://github.com/douglascrockford/JSON-java) from 20230227 to 20231013. - [Release notes](https://github.com/douglascrockford/JSON-java/releases) - [Changelog](https://github.com/stleary/JSON-java/blob/master/docs/RELEASES.md) - [Commits](https://github.com/douglascrockford/JSON-java/commits) --- updated-dependencies: - dependency-name: org.json:json dependency-type: direct:production ... 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 546cbf01f324..547be787e4c4 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -769,7 +769,7 @@ org.json json - 20230227 + 20231013 From 534ee3a699937eedd11aa5cb54f97b081bcda621 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 20 Oct 2023 15:08:03 -0500 Subject: [PATCH 457/510] Verify optional message is not missing or a literal "null" value --- .../dspace/app/rest/repository/RequestItemRepository.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RequestItemRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RequestItemRepository.java index f45dbee66f34..6eb631cfa56e 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RequestItemRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RequestItemRepository.java @@ -242,7 +242,10 @@ public RequestItemRest put(Context context, HttpServletRequest request, } JsonNode responseMessageNode = requestBody.findValue("responseMessage"); - String message = responseMessageNode.asText(); + String message = null; + if (responseMessageNode != null && !responseMessageNode.isNull()) { + message = responseMessageNode.asText(); + } ri.setDecision_date(new Date()); requestItemService.update(context, ri); From a5567992bbe456cd33c68f695a2364f507149e7a Mon Sep 17 00:00:00 2001 From: Toni Prieto Date: Fri, 27 Oct 2023 09:11:12 +0200 Subject: [PATCH 458/510] Change class name to ContextIT and correct a test --- .../org/dspace/core/{ContextModeIT.java => ContextIT.java} | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) rename dspace-api/src/test/java/org/dspace/core/{ContextModeIT.java => ContextIT.java} (83%) diff --git a/dspace-api/src/test/java/org/dspace/core/ContextModeIT.java b/dspace-api/src/test/java/org/dspace/core/ContextIT.java similarity index 83% rename from dspace-api/src/test/java/org/dspace/core/ContextModeIT.java rename to dspace-api/src/test/java/org/dspace/core/ContextIT.java index f689551f1a8c..6cf8336171f2 100644 --- a/dspace-api/src/test/java/org/dspace/core/ContextModeIT.java +++ b/dspace-api/src/test/java/org/dspace/core/ContextIT.java @@ -18,7 +18,7 @@ import org.dspace.builder.CommunityBuilder; import org.junit.Test; -public class ContextModeIT extends AbstractIntegrationTestWithDatabase { +public class ContextIT extends AbstractIntegrationTestWithDatabase { AuthorizeService authorizeService = AuthorizeServiceFactory.getInstance().getAuthorizeService(); @@ -26,6 +26,11 @@ public class ContextModeIT extends AbstractIntegrationTestWithDatabase { public void testGetPoliciesNewCommunityAfterReadOnlyModeChange() throws Exception { context.turnOffAuthorisationSystem(); + + // First disable the index consumer. The indexing process calls the authorizeService + // function used in this test and may affect the test + context.setDispatcher("noindex"); + parentCommunity = CommunityBuilder.createCommunity(context) .withName("Parent Community") .build(); From 56b7cbf4dbcc4a1ec201518f291c119470cc4e93 Mon Sep 17 00:00:00 2001 From: wwuck Date: Thu, 26 Oct 2023 23:16:29 +1100 Subject: [PATCH 459/510] Return both user and operational LDAP attributes Explicitly request both user and operation attributes for LDAP group search as the default searching does not include operational attributes. This is required to fetch the memberOf attribute when checking LDAP group membership. Fixes #9151 --- .../java/org/dspace/authenticate/LDAPAuthentication.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/authenticate/LDAPAuthentication.java b/dspace-api/src/main/java/org/dspace/authenticate/LDAPAuthentication.java index afd82db863ba..4dcba5c1d493 100644 --- a/dspace-api/src/main/java/org/dspace/authenticate/LDAPAuthentication.java +++ b/dspace-api/src/main/java/org/dspace/authenticate/LDAPAuthentication.java @@ -494,6 +494,8 @@ protected String getDNOfUser(String adminUser, String adminPassword, Context con try { SearchControls ctrls = new SearchControls(); ctrls.setSearchScope(ldap_search_scope_value); + // Fetch both user attributes '*' (eg. uid, cn) and operational attributes '+' (eg. memberOf) + ctrls.setReturningAttributes(new String[] {"*", "+"}); String searchName; if (useTLS) { @@ -700,13 +702,13 @@ public String getName() { /* * Add authenticated users to the group defined in dspace.cfg by * the authentication-ldap.login.groupmap.* key. - * + * * @param dn * The string containing distinguished name of the user - * + * * @param group * List of strings with LDAP dn of groups - * + * * @param context * DSpace context */ From bb6498ed5e4696201d3e45bd377faa407dca277f Mon Sep 17 00:00:00 2001 From: wwuck Date: Sat, 28 Oct 2023 00:32:54 +1100 Subject: [PATCH 460/510] Add a null check when assigning ldap groups Prevent NullReferenceException by checking if the group list is null Fixes #8920 --- .../authenticate/LDAPAuthentication.java | 97 ++++++++++++------- 1 file changed, 60 insertions(+), 37 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/authenticate/LDAPAuthentication.java b/dspace-api/src/main/java/org/dspace/authenticate/LDAPAuthentication.java index afd82db863ba..aced16876db0 100644 --- a/dspace-api/src/main/java/org/dspace/authenticate/LDAPAuthentication.java +++ b/dspace-api/src/main/java/org/dspace/authenticate/LDAPAuthentication.java @@ -713,8 +713,8 @@ public String getName() { private void assignGroups(String dn, ArrayList group, Context context) { if (StringUtils.isNotBlank(dn)) { System.out.println("dn:" + dn); - int i = 1; - String groupMap = configurationService.getProperty("authentication-ldap.login.groupmap." + i); + int groupmapIndex = 1; + String groupMap = configurationService.getProperty("authentication-ldap.login.groupmap." + groupmapIndex); boolean cmp; @@ -725,49 +725,72 @@ private void assignGroups(String dn, ArrayList group, Context context) { String ldapSearchString = t[0]; String dspaceGroupName = t[1]; - // list of strings with dn from LDAP groups - // inner loop - Iterator groupIterator = group.iterator(); - while (groupIterator.hasNext()) { + if (group == null) { + cmp = StringUtils.containsIgnoreCase(dn, ldapSearchString + ","); - // save the current entry from iterator for further use - String currentGroup = groupIterator.next(); - - // very much the old code from DSpace <= 7.5 - if (currentGroup == null) { - cmp = StringUtils.containsIgnoreCase(dn, ldapSearchString + ","); - } else { - cmp = StringUtils.equalsIgnoreCase(currentGroup, ldapSearchString); + if (cmp) { + assignGroup(context, groupmapIndex, dspaceGroupName); } + } else { + // list of strings with dn from LDAP groups + // inner loop + Iterator groupIterator = group.iterator(); + while (groupIterator.hasNext()) { - if (cmp) { - // assign user to this group - try { - Group ldapGroup = groupService.findByName(context, dspaceGroupName); - if (ldapGroup != null) { - groupService.addMember(context, ldapGroup, context.getCurrentUser()); - groupService.update(context, ldapGroup); - } else { - // The group does not exist - log.warn(LogHelper.getHeader(context, - "ldap_assignGroupsBasedOnLdapDn", - "Group defined in authentication-ldap.login.groupmap." + i - + " does not exist :: " + dspaceGroupName)); - } - } catch (AuthorizeException ae) { - log.debug(LogHelper.getHeader(context, - "assignGroupsBasedOnLdapDn could not authorize addition to " + - "group", - dspaceGroupName)); - } catch (SQLException e) { - log.debug(LogHelper.getHeader(context, "assignGroupsBasedOnLdapDn could not find group", - dspaceGroupName)); + // save the current entry from iterator for further use + String currentGroup = groupIterator.next(); + + // very much the old code from DSpace <= 7.5 + if (currentGroup == null) { + cmp = StringUtils.containsIgnoreCase(dn, ldapSearchString + ","); + } else { + cmp = StringUtils.equalsIgnoreCase(currentGroup, ldapSearchString); + } + + if (cmp) { + assignGroup(context, groupmapIndex, dspaceGroupName); } } } - groupMap = configurationService.getProperty("authentication-ldap.login.groupmap." + ++i); + groupMap = configurationService.getProperty("authentication-ldap.login.groupmap." + ++groupmapIndex); + } + } + } + + /** + * Add the current authenticated user to the specified group + * + * @param context + * DSpace context + * + * @param groupmapIndex + * authentication-ldap.login.groupmap.* key index defined in dspace.cfg + * + * @param dspaceGroupName + * The DSpace group to add the user to + */ + private void assignGroup(Context context, int groupmapIndex, String dspaceGroupName) { + try { + Group ldapGroup = groupService.findByName(context, dspaceGroupName); + if (ldapGroup != null) { + groupService.addMember(context, ldapGroup, context.getCurrentUser()); + groupService.update(context, ldapGroup); + } else { + // The group does not exist + log.warn(LogHelper.getHeader(context, + "ldap_assignGroupsBasedOnLdapDn", + "Group defined in authentication-ldap.login.groupmap." + groupmapIndex + + " does not exist :: " + dspaceGroupName)); } + } catch (AuthorizeException ae) { + log.debug(LogHelper.getHeader(context, + "assignGroupsBasedOnLdapDn could not authorize addition to " + + "group", + dspaceGroupName)); + } catch (SQLException e) { + log.debug(LogHelper.getHeader(context, "assignGroupsBasedOnLdapDn could not find group", + dspaceGroupName)); } } From 1e82ca7998c45bd628cd84cefce9ae3f0a0ce046 Mon Sep 17 00:00:00 2001 From: Jens Vannerum Date: Fri, 27 Oct 2023 15:50:26 +0200 Subject: [PATCH 461/510] 107891: Cache administrator group --- .../dspace/authorize/AuthorizeServiceImpl.java | 2 +- .../src/main/java/org/dspace/core/Context.java | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java b/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java index fc438c234cda..5dffe5fdfc1f 100644 --- a/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java @@ -451,7 +451,7 @@ public boolean isAdmin(Context c, EPerson e) throws SQLException { if (e == null) { return false; // anonymous users can't be admins.... } else { - return groupService.isMember(c, e, Group.ADMIN); + return groupService.isMember(c, e, c.getAdminGroup()); } } diff --git a/dspace-api/src/main/java/org/dspace/core/Context.java b/dspace-api/src/main/java/org/dspace/core/Context.java index 82b39dd2dfc7..a068ee0cf931 100644 --- a/dspace-api/src/main/java/org/dspace/core/Context.java +++ b/dspace-api/src/main/java/org/dspace/core/Context.java @@ -128,6 +128,11 @@ public class Context implements AutoCloseable { private DBConnection dbConnection; + /** + * The default administrator group + */ + private Group adminGroup; + public enum Mode { READ_ONLY, READ_WRITE, @@ -951,4 +956,15 @@ public void setAuthenticationMethod(final String authenticationMethod) { public boolean isContextUserSwitched() { return currentUserPreviousState != null; } + + /** + * Returns the default "Administrator" group for DSpace administrators. + * The result is cached in the 'adminGroup' field, so it is only looked up once. + * This is done to improve performance, as this method is called quite often. + */ + public Group getAdminGroup() throws SQLException { + return (adminGroup == null) ? EPersonServiceFactory.getInstance() + .getGroupService() + .findByName(this, Group.ADMIN) : adminGroup; + } } From 3a9560ee15c5e36afae261c8df1f1d6e890d558a Mon Sep 17 00:00:00 2001 From: Sean Kalynuk Date: Wed, 26 Jul 2023 11:27:32 -0500 Subject: [PATCH 462/510] Fixes #8558 - set Solr UTC timezone Set the timezone of the Solr date formatter to UTC (cherry picked from commit 4c329b43193a3bea151bdf9af27b663affcf7246) --- dspace-api/src/main/java/org/dspace/util/SolrUtils.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/util/SolrUtils.java b/dspace-api/src/main/java/org/dspace/util/SolrUtils.java index f62feba29886..7b11d73834bb 100644 --- a/dspace-api/src/main/java/org/dspace/util/SolrUtils.java +++ b/dspace-api/src/main/java/org/dspace/util/SolrUtils.java @@ -35,6 +35,8 @@ private SolrUtils() { } * @return date formatter compatible with Solr. */ public static DateFormat getDateFormatter() { - return new SimpleDateFormat(SolrUtils.SOLR_DATE_FORMAT); + DateFormat formatter = new SimpleDateFormat(SolrUtils.SOLR_DATE_FORMAT); + formatter.setTimeZone(SOLR_TIME_ZONE); + return formatter; } } From 7566a79d906b5050bef01d22c5f4b3e4ab6e4b58 Mon Sep 17 00:00:00 2001 From: Alan Orth Date: Mon, 30 Oct 2023 09:05:36 +0300 Subject: [PATCH 463/510] dspace/config: update spider agent list Update list of spider user agents from the COUNTER-Robots project. See: https://github.com/atmire/COUNTER-Robots --- dspace/config/spiders/agents/example | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/dspace/config/spiders/agents/example b/dspace/config/spiders/agents/example index f206558d81f6..998431d92a19 100644 --- a/dspace/config/spiders/agents/example +++ b/dspace/config/spiders/agents/example @@ -27,6 +27,7 @@ arks ^Array$ asterias atomz +axios\/\d BDFetch Betsie baidu @@ -45,6 +46,7 @@ BUbiNG bwh3_user_agent CakePHP celestial +centuryb cfnetwork checklink checkprivacy @@ -89,6 +91,7 @@ Embedly EThOS\+\(British\+Library\) facebookexternalhit\/ favorg +Faveeo\/\d FDM(\s|\+)\d Feedbin feedburner @@ -113,6 +116,7 @@ GLMSLinkAnalysis Goldfire(\s|\+)Server google Grammarly +GroupHigh\/\d grub gulliver gvfs\/ @@ -121,16 +125,19 @@ heritrix holmes htdig htmlparser +HeadlessChrome HttpComponents\/1.1 HTTPFetcher http.?client httpget +httpx httrack ia_archiver ichiro iktomi ilse Indy Library +insomnia ^integrity\/\d internetseer intute @@ -140,6 +147,7 @@ iskanie jeeves Jersey\/\d jobo +Koha kyluka larbin libcurl @@ -161,10 +169,12 @@ LongURL.API ltx71 lwp lycos[_+] +MaCoCu mail\.ru MarcEdit mediapartners-google megite +MetaInspector MetaURI[\+\s]API\/\d\.\d Microsoft(\s|\+)URL(\s|\+)Control Microsoft Office Existence Discovery @@ -190,6 +200,7 @@ nagios ^NetAnts\/\d netcraft netluchs +nettle newspaper\/\d ng\/2\. ^Ning\/\d @@ -225,6 +236,7 @@ rambler ReactorNetty\/\d Readpaper redalert +RestSharp Riddler robozilla rss @@ -252,7 +264,7 @@ T\-H\-U\-N\-D\-E\-R\-S\-T\-O\-N\-E tailrank Teleport(\s|\+)Pro Teoma -The\+Knowledge\+AI +The[\+\s]Knowledge[\+\s]AI titan ^Traackr\.com$ Trello @@ -302,6 +314,8 @@ yacy yahoo yandex Yeti\/\d +Zabbix +ZoteroTranslationServer zeus zyborg 7siters From e6d108a94e41e58d6d701f3ef0429fda438e6555 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paulo=20Gra=C3=A7a?= Date: Mon, 30 Oct 2023 11:27:18 +0000 Subject: [PATCH 464/510] new testDeleteBitstreamAndUnsetPrimaryBitstreamID test for primary bitstream verification --- .../org/dspace/content/BitstreamTest.java | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/dspace-api/src/test/java/org/dspace/content/BitstreamTest.java b/dspace-api/src/test/java/org/dspace/content/BitstreamTest.java index 921e4efcc7d8..30ef5f37fb58 100644 --- a/dspace-api/src/test/java/org/dspace/content/BitstreamTest.java +++ b/dspace-api/src/test/java/org/dspace/content/BitstreamTest.java @@ -432,6 +432,55 @@ public void testDeleteAndExpunge() throws IOException, SQLException, AuthorizeEx assertThat("testExpunge 0", bitstreamService.find(context, bitstreamId), nullValue()); } + /** + * Test of delete method, of class Bitstream. + */ + @Test + public void testDeleteBitstreamAndUnsetPrimaryBitstreamID() + throws IOException, SQLException, AuthorizeException { + + context.turnOffAuthorisationSystem(); + + Community owningCommunity = communityService.create(null, context); + Collection collection = collectionService.create(context, owningCommunity); + WorkspaceItem workspaceItem = workspaceItemService.create(context, collection, false); + Item item = installItemService.installItem(context, workspaceItem); + Bundle b = bundleService.create(context, item, "TESTBUNDLE"); + + // Allow Item WRITE permissions + doNothing().when(authorizeServiceSpy).authorizeAction(context, item, Constants.WRITE); + // Allow Bundle ADD permissions + doNothing().when(authorizeServiceSpy).authorizeAction(context, b, Constants.ADD); + // Allow Bundle REMOVE permissions + doNothing().when(authorizeServiceSpy).authorizeAction(context, b, Constants.REMOVE); + // Allow Bitstream WRITE permissions + doNothing().when(authorizeServiceSpy) + .authorizeAction(any(Context.class), any(Bitstream.class), eq(Constants.WRITE)); + // Allow Bitstream DELETE permissions + doNothing().when(authorizeServiceSpy) + .authorizeAction(any(Context.class), any(Bitstream.class), eq(Constants.DELETE)); + + //set a value different than default + File f = new File(testProps.get("test.bitstream").toString()); + + // Create a new bitstream, which we can delete. + Bitstream bs = bitstreamService.create(context, new FileInputStream(f)); + bundleService.addBitstream(context, b, bs); + // set primary bitstream + b.setPrimaryBitstreamID(bs); + context.restoreAuthSystemState(); + + // Test that delete will flag the bitstream as deleted + assertFalse("testDeleteBitstreamAndUnsetPrimaryBitstreamID 0", bs.isDeleted()); + assertThat("testDeleteBitstreamAndUnsetPrimaryBitstreamID 1", b.getPrimaryBitstream(), equalTo(bs)); + // Delete bitstream + bitstreamService.delete(context, bs); + assertTrue("testDeleteBitstreamAndUnsetPrimaryBitstreamID 2", bs.isDeleted()); + + // Now test if the primary bitstream was unset from bundle + assertThat("testDeleteBitstreamAndUnsetPrimaryBitstreamID 3", b.getPrimaryBitstream(), equalTo(null)); + } + /** * Test of retrieve method, of class Bitstream. */ From ad0d22a13a35a2167557efeb5ddea7a3a504424d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paulo=20Gra=C3=A7a?= Date: Mon, 30 Oct 2023 11:45:12 +0000 Subject: [PATCH 465/510] new testDeleteBitstreamAndUnsetPrimaryBitstreamID test for primary bitstream verification --- .../java/org/dspace/content/BitstreamTest.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/dspace-api/src/test/java/org/dspace/content/BitstreamTest.java b/dspace-api/src/test/java/org/dspace/content/BitstreamTest.java index 30ef5f37fb58..eb3de96d2fd1 100644 --- a/dspace-api/src/test/java/org/dspace/content/BitstreamTest.java +++ b/dspace-api/src/test/java/org/dspace/content/BitstreamTest.java @@ -464,18 +464,18 @@ public void testDeleteBitstreamAndUnsetPrimaryBitstreamID() File f = new File(testProps.get("test.bitstream").toString()); // Create a new bitstream, which we can delete. - Bitstream bs = bitstreamService.create(context, new FileInputStream(f)); - bundleService.addBitstream(context, b, bs); + Bitstream delBS = bitstreamService.create(context, new FileInputStream(f)); + bundleService.addBitstream(context, b, delBS); // set primary bitstream - b.setPrimaryBitstreamID(bs); + b.setPrimaryBitstreamID(delBS); context.restoreAuthSystemState(); // Test that delete will flag the bitstream as deleted - assertFalse("testDeleteBitstreamAndUnsetPrimaryBitstreamID 0", bs.isDeleted()); - assertThat("testDeleteBitstreamAndUnsetPrimaryBitstreamID 1", b.getPrimaryBitstream(), equalTo(bs)); + assertFalse("testDeleteBitstreamAndUnsetPrimaryBitstreamID 0", delBS.isDeleted()); + assertThat("testDeleteBitstreamAndUnsetPrimaryBitstreamID 1", b.getPrimaryBitstream(), equalTo(delBS)); // Delete bitstream - bitstreamService.delete(context, bs); - assertTrue("testDeleteBitstreamAndUnsetPrimaryBitstreamID 2", bs.isDeleted()); + bitstreamService.delete(context, delBS); + assertTrue("testDeleteBitstreamAndUnsetPrimaryBitstreamID 2", delBS.isDeleted()); // Now test if the primary bitstream was unset from bundle assertThat("testDeleteBitstreamAndUnsetPrimaryBitstreamID 3", b.getPrimaryBitstream(), equalTo(null)); From a3e506c7f452133e3cc973705d671dba61a469d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paulo=20Gra=C3=A7a?= Date: Mon, 30 Oct 2023 13:08:53 +0000 Subject: [PATCH 466/510] new testDeleteBitstreamAndUnsetPrimaryBitstreamID remove unnecessary stubs --- .../src/test/java/org/dspace/content/BitstreamTest.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/dspace-api/src/test/java/org/dspace/content/BitstreamTest.java b/dspace-api/src/test/java/org/dspace/content/BitstreamTest.java index eb3de96d2fd1..e85a0fc7b78d 100644 --- a/dspace-api/src/test/java/org/dspace/content/BitstreamTest.java +++ b/dspace-api/src/test/java/org/dspace/content/BitstreamTest.java @@ -447,10 +447,6 @@ public void testDeleteBitstreamAndUnsetPrimaryBitstreamID() Item item = installItemService.installItem(context, workspaceItem); Bundle b = bundleService.create(context, item, "TESTBUNDLE"); - // Allow Item WRITE permissions - doNothing().when(authorizeServiceSpy).authorizeAction(context, item, Constants.WRITE); - // Allow Bundle ADD permissions - doNothing().when(authorizeServiceSpy).authorizeAction(context, b, Constants.ADD); // Allow Bundle REMOVE permissions doNothing().when(authorizeServiceSpy).authorizeAction(context, b, Constants.REMOVE); // Allow Bitstream WRITE permissions From 160ebbd791c0545db6516403da40cb191a2c8b99 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 30 Oct 2023 15:13:39 -0500 Subject: [PATCH 467/510] Update to newly released XOAI 3.4.0 --- dspace-oai/pom.xml | 39 ++++----------------------------------- 1 file changed, 4 insertions(+), 35 deletions(-) diff --git a/dspace-oai/pom.xml b/dspace-oai/pom.xml index 59cee2829360..b900ebe88ded 100644 --- a/dspace-oai/pom.xml +++ b/dspace-oai/pom.xml @@ -15,7 +15,7 @@ ${basedir}/.. - 3.3.1-SNAPSHOT + 3.4.0 5.87.0.RELEASE @@ -55,41 +55,10 @@ xoai ${xoai.version} + - org.hamcrest - hamcrest-all - - - - org.mockito - mockito-all - - - org.apache.commons - commons-lang3 - - - log4j - log4j - - - org.slf4j - slf4j-log4j12 - - - - org.codehaus.woodstox - wstx-asl - - - - org.dom4j - dom4j - - - - com.lyncode - test-support + com.fasterxml.woodstox + woodstox-core From c0bbd9d91f894fbe26f8cf7c4f166da8ba1cefd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paulo=20Gra=C3=A7a?= Date: Mon, 30 Oct 2023 22:48:49 +0000 Subject: [PATCH 468/510] make comments more clear to understand --- .../V7.6_2023.10.12__Fix-deleted-primary-bitstreams.sql | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2023.10.12__Fix-deleted-primary-bitstreams.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2023.10.12__Fix-deleted-primary-bitstreams.sql index c97d2246578e..7a0bae1582d4 100644 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2023.10.12__Fix-deleted-primary-bitstreams.sql +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2023.10.12__Fix-deleted-primary-bitstreams.sql @@ -8,7 +8,7 @@ BEGIN; --- Remove all primary bitstreams that are marked as deleted +-- Unset any primary bitstream that is marked as deleted UPDATE bundle SET primary_bitstream_id = NULL WHERE primary_bitstream_id IN @@ -17,7 +17,7 @@ WHERE primary_bitstream_id IN INNER JOIN bundle as bl ON bs.uuid = bl.primary_bitstream_id WHERE bs.deleted IS TRUE ); --- Remove all primary bitstreams that don't make part on bundle's bitstreams +-- Unset any primary bitstream that don't belong to bundle's bitstreams list UPDATE bundle SET primary_bitstream_id = NULL WHERE primary_bitstream_id IN @@ -31,4 +31,4 @@ WHERE primary_bitstream_id IN ) ); -COMMIT; \ No newline at end of file +COMMIT; From 74cce86afcc163c52502892556679e6175fa1948 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paulo=20Gra=C3=A7a?= Date: Mon, 30 Oct 2023 22:49:31 +0000 Subject: [PATCH 469/510] typo --- .../V7.6_2023.10.12__Fix-deleted-primary-bitstreams.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2023.10.12__Fix-deleted-primary-bitstreams.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2023.10.12__Fix-deleted-primary-bitstreams.sql index 7a0bae1582d4..9dd2f54a43eb 100644 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2023.10.12__Fix-deleted-primary-bitstreams.sql +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2023.10.12__Fix-deleted-primary-bitstreams.sql @@ -17,7 +17,7 @@ WHERE primary_bitstream_id IN INNER JOIN bundle as bl ON bs.uuid = bl.primary_bitstream_id WHERE bs.deleted IS TRUE ); --- Unset any primary bitstream that don't belong to bundle's bitstreams list +-- Unset any primary bitstream that don't belong to bundle's bitstream list UPDATE bundle SET primary_bitstream_id = NULL WHERE primary_bitstream_id IN From 74c72354b405ed266b65cdd50b594d25bea0e87f Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Thu, 14 Sep 2023 16:08:25 -0500 Subject: [PATCH 470/510] Add basic pagination to /groups/[uuid]/epersons endpoint --- .../dspace/eperson/EPersonServiceImpl.java | 17 +++++++++- .../org/dspace/eperson/dao/EPersonDAO.java | 24 +++++++++++++- .../eperson/dao/impl/EPersonDAOImpl.java | 21 ++++++++++-- .../eperson/service/EPersonService.java | 33 +++++++++++++++++-- .../GroupEPersonLinkRepository.java | 13 +++++++- 5 files changed, 101 insertions(+), 7 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/eperson/EPersonServiceImpl.java b/dspace-api/src/main/java/org/dspace/eperson/EPersonServiceImpl.java index 2d0574a6301d..5f17051dbbb0 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/EPersonServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/EPersonServiceImpl.java @@ -567,14 +567,29 @@ public List getDeleteConstraints(Context context, EPerson ePerson) throw @Override public List findByGroups(Context c, Set groups) throws SQLException { + return findByGroups(c, groups, -1, -1); + } + + @Override + public List findByGroups(Context c, Set groups, int pageSize, int offset) throws SQLException { //Make sure we at least have one group, if not don't even bother searching. if (CollectionUtils.isNotEmpty(groups)) { - return ePersonDAO.findByGroups(c, groups); + return ePersonDAO.findByGroups(c, groups, pageSize, offset); } else { return new ArrayList<>(); } } + @Override + public int countByGroups(Context c, Set groups) throws SQLException { + //Make sure we at least have one group, if not don't even bother counting. + if (CollectionUtils.isNotEmpty(groups)) { + return ePersonDAO.countByGroups(c, groups); + } else { + return 0; + } + } + @Override public List findEPeopleWithSubscription(Context context) throws SQLException { return ePersonDAO.findAllSubscribers(context); diff --git a/dspace-api/src/main/java/org/dspace/eperson/dao/EPersonDAO.java b/dspace-api/src/main/java/org/dspace/eperson/dao/EPersonDAO.java index 51ab89ef7e8f..573103f86ad3 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/dao/EPersonDAO.java +++ b/dspace-api/src/main/java/org/dspace/eperson/dao/EPersonDAO.java @@ -38,7 +38,29 @@ public List search(Context context, String query, List q public int searchResultCount(Context context, String query, List queryFields) throws SQLException; - public List findByGroups(Context context, Set groups) throws SQLException; + /** + * Find all EPersons who are a member of one or more of the listed groups in a paginated fashion. Order is + * indeterminate. + * + * @param context current Context + * @param groups Set of group(s) to check membership in + * @param pageSize number of EPerson objects to load at one time. Set to <=0 to disable pagination + * @param offset number of page to load (starting with 1). Set to <=0 to disable pagination + * @return List of all EPersons who are a member of one or more groups. + * @throws SQLException + */ + List findByGroups(Context context, Set groups, int pageSize, int offset) throws SQLException; + + /** + * Count total number of EPersons who are a member of one or more of the listed groups. This provides the total + * number of results to expect from corresponding findByGroups() for pagination purposes. + * + * @param context current Context + * @param groups Set of group(s) to check membership in + * @return total number of (unique) EPersons who are a member of one or more groups. + * @throws SQLException + */ + int countByGroups(Context context, Set groups) throws SQLException; public List findWithPasswordWithoutDigestAlgorithm(Context context) throws SQLException; diff --git a/dspace-api/src/main/java/org/dspace/eperson/dao/impl/EPersonDAOImpl.java b/dspace-api/src/main/java/org/dspace/eperson/dao/impl/EPersonDAOImpl.java index 50547a500745..14b44d77c0cc 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/dao/impl/EPersonDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/dao/impl/EPersonDAOImpl.java @@ -112,7 +112,7 @@ public List findAll(Context context, MetadataField metadataSortField, S } @Override - public List findByGroups(Context context, Set groups) throws SQLException { + public List findByGroups(Context context, Set groups, int pageSize, int offset) throws SQLException { Query query = createQuery(context, "SELECT DISTINCT e FROM EPerson e " + "JOIN e.groups g " + @@ -125,7 +125,24 @@ public List findByGroups(Context context, Set groups) throws SQL query.setParameter("idList", idList); - return list(query); + return list(query, pageSize, offset); + } + + @Override + public int countByGroups(Context context, Set groups) throws SQLException { + Query query = createQuery(context, + "SELECT count(DISTINCT e) FROM EPerson e " + + "JOIN e.groups g " + + "WHERE g.id IN (:idList) "); + + List idList = new ArrayList<>(groups.size()); + for (Group group : groups) { + idList.add(group.getID()); + } + + query.setParameter("idList", idList); + + return count(query); } @Override diff --git a/dspace-api/src/main/java/org/dspace/eperson/service/EPersonService.java b/dspace-api/src/main/java/org/dspace/eperson/service/EPersonService.java index 47be942e97e9..b60247ef5498 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/service/EPersonService.java +++ b/dspace-api/src/main/java/org/dspace/eperson/service/EPersonService.java @@ -252,14 +252,43 @@ public EPerson create(Context context) throws SQLException, public List getDeleteConstraints(Context context, EPerson ePerson) throws SQLException; /** - * Retrieve all accounts which belong to at least one of the specified groups. + * Retrieve all EPerson accounts which belong to at least one of the specified groups. + *

    + * WARNING: This method should be used sparingly, as it could have performance issues for Groups with very large + * lists of members. In that situation, a very large number of EPerson objects will be loaded into memory. + * See https://github.com/DSpace/DSpace/issues/9052 + *

    + * For better performance, use the paginated version of this method. * * @param c The relevant DSpace Context. * @param groups set of eperson groups * @return a list of epeople * @throws SQLException An exception that provides information on a database access error or other errors. */ - public List findByGroups(Context c, Set groups) throws SQLException; + List findByGroups(Context c, Set groups) throws SQLException; + + /** + * Retrieve all EPerson accounts which belong to at least one of the specified groups, in a paginated fashion. + * + * @param c The relevant DSpace Context. + * @param groups Set of group(s) to check membership in + * @param pageSize number of EPerson objects to load at one time. Set to <=0 to disable pagination + * @param offset number of page to load (starting with 1). Set to <=0 to disable pagination + * @return a list of epeople + * @throws SQLException An exception that provides information on a database access error or other errors. + */ + List findByGroups(Context c, Set groups, int pageSize, int offset) throws SQLException; + + /** + * Count all EPerson accounts which belong to at least one of the specified groups. This provides the total + * number of results to expect from corresponding findByGroups() for pagination purposes. + * + * @param c The relevant DSpace Context. + * @param groups Set of group(s) to check membership in + * @return total number of (unique) EPersons who are a member of one or more groups. + * @throws SQLException An exception that provides information on a database access error or other errors. + */ + int countByGroups(Context c, Set groups) throws SQLException; /** * Retrieve all accounts which are subscribed to receive information about new items. diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/GroupEPersonLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/GroupEPersonLinkRepository.java index b1cdc401f22f..1ce278893d17 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/GroupEPersonLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/GroupEPersonLinkRepository.java @@ -8,6 +8,8 @@ package org.dspace.app.rest.repository; import java.sql.SQLException; +import java.util.List; +import java.util.Set; import java.util.UUID; import javax.annotation.Nullable; import javax.servlet.http.HttpServletRequest; @@ -15,7 +17,9 @@ import org.dspace.app.rest.model.GroupRest; import org.dspace.app.rest.projection.Projection; import org.dspace.core.Context; +import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; +import org.dspace.eperson.service.EPersonService; import org.dspace.eperson.service.GroupService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; @@ -31,6 +35,9 @@ public class GroupEPersonLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { + @Autowired + EPersonService epersonService; + @Autowired GroupService groupService; @@ -45,7 +52,11 @@ public Page getMembers(@Nullable HttpServletRequest request, if (group == null) { throw new ResourceNotFoundException("No such group: " + groupId); } - return converter.toRestPage(group.getMembers(), optionalPageable, projection); + int total = epersonService.countByGroups(context, Set.of(group)); + Pageable pageable = utils.getPageable(optionalPageable); + List members = epersonService.findByGroups(context, Set.of(group), pageable.getPageSize(), + Math.toIntExact(pageable.getOffset())); + return converter.toRestPage(members, pageable, total, projection); } catch (SQLException e) { throw new RuntimeException(e); } From 15de2d0074b56f421b3bbb9f3955814497985aef Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Thu, 14 Sep 2023 16:26:09 -0500 Subject: [PATCH 471/510] Bug fix. Only use pageSize and offset if >0 --- .../org/dspace/eperson/dao/impl/EPersonDAOImpl.java | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/eperson/dao/impl/EPersonDAOImpl.java b/dspace-api/src/main/java/org/dspace/eperson/dao/impl/EPersonDAOImpl.java index 14b44d77c0cc..bd68a7f399d9 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/dao/impl/EPersonDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/dao/impl/EPersonDAOImpl.java @@ -112,7 +112,8 @@ public List findAll(Context context, MetadataField metadataSortField, S } @Override - public List findByGroups(Context context, Set groups, int pageSize, int offset) throws SQLException { + public List findByGroups(Context context, Set groups, int pageSize, int offset) + throws SQLException { Query query = createQuery(context, "SELECT DISTINCT e FROM EPerson e " + "JOIN e.groups g " + @@ -122,10 +123,16 @@ public List findByGroups(Context context, Set groups, int pageSi for (Group group : groups) { idList.add(group.getID()); } - query.setParameter("idList", idList); - return list(query, pageSize, offset); + if (pageSize > 0) { + query.setMaxResults(pageSize); + } + if (offset > 0) { + query.setFirstResult(offset); + } + + return list(query); } @Override From 457dd9ae441fa084ff7cc3eaf9213e5497a2b298 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Thu, 14 Sep 2023 16:33:59 -0500 Subject: [PATCH 472/510] Add missing pagination test for /groups/[uuid]/epersons --- .../app/rest/GroupRestRepositoryIT.java | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/GroupRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/GroupRestRepositoryIT.java index fda8b15effa3..4d68652e249a 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/GroupRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/GroupRestRepositoryIT.java @@ -3091,6 +3091,84 @@ public void findByMetadataPaginationTest() throws Exception { } + // Test of /groups/[uuid]/epersons pagination + @Test + public void epersonMemberPaginationTest() throws Exception { + context.turnOffAuthorisationSystem(); + + EPerson eperson1 = EPersonBuilder.createEPerson(context) + .withEmail("test1@example.com") + .withNameInMetadata("Test1", "User") + .build(); + EPerson eperson2 = EPersonBuilder.createEPerson(context) + .withEmail("test2@example.com") + .withNameInMetadata("Test2", "User") + .build(); + EPerson eperson3 = EPersonBuilder.createEPerson(context) + .withEmail("test3@example.com") + .withNameInMetadata("Test3", "User") + .build(); + EPerson eperson4 = EPersonBuilder.createEPerson(context) + .withEmail("test4@example.com") + .withNameInMetadata("Test4", "User") + .build(); + EPerson eperson5 = EPersonBuilder.createEPerson(context) + .withEmail("test5@example.com") + .withNameInMetadata("Test5", "User") + .build(); + + Group group = GroupBuilder.createGroup(context) + .withName("Test group") + .addMember(eperson1) + .addMember(eperson2) + .addMember(eperson3) + .addMember(eperson4) + .addMember(eperson5) + .build(); + + context.restoreAuthSystemState(); + + String authTokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(authTokenAdmin).perform(get("/api/eperson/groups/" + group.getID() + "/epersons") + .param("page", "0") + .param("size", "2")) + .andExpect(status().isOk()).andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.epersons", Matchers.everyItem( + hasJsonPath("$.type", is("eperson"))) + )) + .andExpect(jsonPath("$._embedded.epersons").value(Matchers.hasSize(2))) + .andExpect(jsonPath("$.page.size", is(2))) + .andExpect(jsonPath("$.page.number", is(0))) + .andExpect(jsonPath("$.page.totalPages", is(3))) + .andExpect(jsonPath("$.page.totalElements", is(5))); + + getClient(authTokenAdmin).perform(get("/api/eperson/groups/" + group.getID() + "/epersons") + .param("page", "1") + .param("size", "2")) + .andExpect(status().isOk()).andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.epersons", Matchers.everyItem( + hasJsonPath("$.type", is("eperson"))) + )) + .andExpect(jsonPath("$._embedded.epersons").value(Matchers.hasSize(2))) + .andExpect(jsonPath("$.page.size", is(2))) + .andExpect(jsonPath("$.page.number", is(1))) + .andExpect(jsonPath("$.page.totalPages", is(3))) + .andExpect(jsonPath("$.page.totalElements", is(5))); + + getClient(authTokenAdmin).perform(get("/api/eperson/groups/" + group.getID() + "/epersons") + .param("page", "2") + .param("size", "2")) + .andExpect(status().isOk()).andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.epersons", Matchers.everyItem( + hasJsonPath("$.type", is("eperson"))) + )) + .andExpect(jsonPath("$._embedded.epersons").value(Matchers.hasSize(1))) + .andExpect(jsonPath("$.page.size", is(2))) + .andExpect(jsonPath("$.page.number", is(2))) + .andExpect(jsonPath("$.page.totalPages", is(3))) + .andExpect(jsonPath("$.page.totalElements", is(5))); + } + @Test public void commAdminAndColAdminCannotExploitItemReadGroupTest() throws Exception { From e7c4b9eba2d8148e07543c3b6c61dde359018da2 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 15 Sep 2023 16:56:18 -0500 Subject: [PATCH 473/510] Add pagination to /groups/[uuid]/subgroups endpoint, along with tests --- .../main/java/org/dspace/eperson/Group.java | 14 +++- .../org/dspace/eperson/GroupServiceImpl.java | 16 ++++ .../java/org/dspace/eperson/dao/GroupDAO.java | 24 ++++++ .../dspace/eperson/dao/impl/GroupDAOImpl.java | 23 ++++++ .../eperson/service/EPersonService.java | 5 +- .../dspace/eperson/service/GroupService.java | 25 +++++++ .../java/org/dspace/eperson/GroupTest.java | 27 +++++++ .../repository/GroupGroupLinkRepository.java | 7 +- .../app/rest/GroupRestRepositoryIT.java | 73 +++++++++++++++++++ 9 files changed, 207 insertions(+), 7 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/eperson/Group.java b/dspace-api/src/main/java/org/dspace/eperson/Group.java index 6cb534146b25..67655e0e0aaf 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/Group.java +++ b/dspace-api/src/main/java/org/dspace/eperson/Group.java @@ -98,7 +98,11 @@ void addMember(EPerson e) { } /** - * Return EPerson members of a Group + * Return EPerson members of a Group. + *

    + * WARNING: This method may have bad performance for Groups with large numbers of EPerson members. + * Therefore, only use this when you need to access every EPerson member. Instead, consider using + * EPersonService.findByGroups() for a paginated list of EPersons. * * @return list of EPersons */ @@ -143,9 +147,13 @@ List getParentGroups() { } /** - * Return Group members of a Group. + * Return Group members (i.e. direct subgroups) of a Group. + *

    + * WARNING: This method may have bad performance for Groups with large numbers of Subgroups. + * Therefore, only use this when you need to access every Subgroup. Instead, consider using + * GroupService.findByParent() for a paginated list of Subgroups. * - * @return list of groups + * @return list of subgroups */ public List getMemberGroups() { return groups; diff --git a/dspace-api/src/main/java/org/dspace/eperson/GroupServiceImpl.java b/dspace-api/src/main/java/org/dspace/eperson/GroupServiceImpl.java index 607e57af0b2c..4fdd1a3ba384 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/GroupServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/GroupServiceImpl.java @@ -829,4 +829,20 @@ public List findByMetadataField(final Context context, final String searc public String getName(Group dso) { return dso.getName(); } + + @Override + public List findByParent(Context context, Group parent, int pageSize, int offset) throws SQLException { + if (parent == null) { + return null; + } + return groupDAO.findByParent(context, parent, pageSize, offset); + } + + @Override + public int countByParent(Context context, Group parent) throws SQLException { + if (parent == null) { + return 0; + } + return groupDAO.countByParent(context, parent); + } } diff --git a/dspace-api/src/main/java/org/dspace/eperson/dao/GroupDAO.java b/dspace-api/src/main/java/org/dspace/eperson/dao/GroupDAO.java index 2cc77129f038..fd56fe9bd1d0 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/dao/GroupDAO.java +++ b/dspace-api/src/main/java/org/dspace/eperson/dao/GroupDAO.java @@ -146,4 +146,28 @@ List findAll(Context context, List metadataSortFields, int */ Group findByIdAndMembership(Context context, UUID id, EPerson ePerson) throws SQLException; + /** + * Find all groups which are members of a given parent group. + * This provides the same behavior as group.getMemberGroups(), but in a paginated fashion. + * + * @param context The DSpace context + * @param parent Parent Group to search within + * @param pageSize how many results return + * @param offset the position of the first result to return + * @return Groups matching the query + * @throws SQLException if database error + */ + List findByParent(Context context, Group parent, int pageSize, int offset) throws SQLException; + + /** + * Returns the number of groups which are members of a given parent group. + * This provides the same behavior as group.getMemberGroups().size(), but with better performance for large groups. + * This method may be used with findByParent() to perform pagination. + * + * @param context The DSpace context + * @param parent Parent Group to search within + * @return Number of Groups matching the query + * @throws SQLException if database error + */ + int countByParent(Context context, Group parent) throws SQLException; } diff --git a/dspace-api/src/main/java/org/dspace/eperson/dao/impl/GroupDAOImpl.java b/dspace-api/src/main/java/org/dspace/eperson/dao/impl/GroupDAOImpl.java index edc2ab749bfa..f071a1bc754b 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/dao/impl/GroupDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/dao/impl/GroupDAOImpl.java @@ -196,4 +196,27 @@ public int countRows(Context context) throws SQLException { return count(createQuery(context, "SELECT count(*) FROM Group")); } + @Override + public List findByParent(Context context, Group parent, int pageSize, int offset) throws SQLException { + Query query = createQuery(context, + "from Group where (from Group g where g.id = :parent_id) in elements (parentGroups)"); + query.setParameter("parent_id", parent.getID()); + if (pageSize > 0) { + query.setMaxResults(pageSize); + } + if (offset > 0) { + query.setFirstResult(offset); + } + query.setHint("org.hibernate.cacheable", Boolean.TRUE); + + return list(query); + } + + public int countByParent(Context context, Group parent) throws SQLException { + Query query = createQuery(context, "SELECT count(*) from Group " + + "where (from Group g where g.id = :parent_id) in elements (parentGroups)"); + query.setParameter("parent_id", parent.getID()); + + return count(query); + } } diff --git a/dspace-api/src/main/java/org/dspace/eperson/service/EPersonService.java b/dspace-api/src/main/java/org/dspace/eperson/service/EPersonService.java index b60247ef5498..5b10ea539b33 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/service/EPersonService.java +++ b/dspace-api/src/main/java/org/dspace/eperson/service/EPersonService.java @@ -254,9 +254,8 @@ public EPerson create(Context context) throws SQLException, /** * Retrieve all EPerson accounts which belong to at least one of the specified groups. *

    - * WARNING: This method should be used sparingly, as it could have performance issues for Groups with very large - * lists of members. In that situation, a very large number of EPerson objects will be loaded into memory. - * See https://github.com/DSpace/DSpace/issues/9052 + * WARNING: This method may have bad performance issues for Groups with a very large number of members, + * as it will load all member EPerson objects into memory. *

    * For better performance, use the paginated version of this method. * diff --git a/dspace-api/src/main/java/org/dspace/eperson/service/GroupService.java b/dspace-api/src/main/java/org/dspace/eperson/service/GroupService.java index 8979bcc4457a..634fd0aca20c 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/service/GroupService.java +++ b/dspace-api/src/main/java/org/dspace/eperson/service/GroupService.java @@ -327,4 +327,29 @@ public List findAll(Context context, List metadataSortFiel */ List findByMetadataField(Context context, String searchValue, MetadataField metadataField) throws SQLException; + + /** + * Find all groups which are a member of the given Parent group + * + * @param context The relevant DSpace Context. + * @param parent The parent Group to search on + * @param pageSize how many results return + * @param offset the position of the first result to return + * @return List of all groups which are members of the parent group + * @throws SQLException database exception if error + */ + List findByParent(Context context, Group parent, int pageSize, int offset) + throws SQLException; + + /** + * Return number of groups which are a member of the given Parent group. + * Can be used with findByParent() for pagination of all groups within a given Parent group. + * + * @param context The relevant DSpace Context. + * @param parent The parent Group to search on + * @return number of groups which are members of the parent group + * @throws SQLException database exception if error + */ + int countByParent(Context context, Group parent) + throws SQLException; } diff --git a/dspace-api/src/test/java/org/dspace/eperson/GroupTest.java b/dspace-api/src/test/java/org/dspace/eperson/GroupTest.java index ee9c883f1be6..7666fcfe5431 100644 --- a/dspace-api/src/test/java/org/dspace/eperson/GroupTest.java +++ b/dspace-api/src/test/java/org/dspace/eperson/GroupTest.java @@ -10,6 +10,7 @@ import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -24,6 +25,7 @@ import org.apache.logging.log4j.Logger; import org.dspace.AbstractUnitTest; import org.dspace.authorize.AuthorizeException; +import org.dspace.builder.GroupBuilder; import org.dspace.eperson.factory.EPersonServiceFactory; import org.dspace.eperson.service.EPersonService; import org.dspace.eperson.service.GroupService; @@ -620,6 +622,31 @@ public void isEmpty() throws SQLException, AuthorizeException, EPersonDeletionEx assertTrue(groupService.isEmpty(level2Group)); } + @Test + public void findAndCountByParent() throws SQLException, AuthorizeException, IOException { + // Create a parent group with 3 child groups + Group parentGroup = createGroup("parentGroup"); + Group childGroup = createGroup("childGroup"); + Group child2Group = createGroup("child2Group"); + Group child3Group = createGroup("child3Group"); + groupService.addMember(context, parentGroup, childGroup); + groupService.addMember(context, parentGroup, child2Group); + groupService.addMember(context, parentGroup, child3Group); + groupService.update(context, parentGroup); + + // Assert that findByParent is the same list of groups as getMemberGroups() when pagination is ignored + // (NOTE: Pagination is tested in GroupRestRepositoryIT) + assertEquals(parentGroup.getMemberGroups(), groupService.findByParent(context, parentGroup, -1, -1)); + // Assert countBy parent is the same as the size of group members + assertEquals(parentGroup.getMemberGroups().size(), groupService.countByParent(context, parentGroup)); + + // Clean up our data + groupService.delete(context, parentGroup); + groupService.delete(context, childGroup); + groupService.delete(context, child2Group); + groupService.delete(context, child3Group); + } + protected Group createGroup(String name) throws SQLException, AuthorizeException { context.turnOffAuthorisationSystem(); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/GroupGroupLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/GroupGroupLinkRepository.java index 37cf9083b39a..564e941d45cc 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/GroupGroupLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/GroupGroupLinkRepository.java @@ -8,6 +8,7 @@ package org.dspace.app.rest.repository; import java.sql.SQLException; +import java.util.List; import java.util.UUID; import javax.annotation.Nullable; import javax.servlet.http.HttpServletRequest; @@ -45,7 +46,11 @@ public Page getGroups(@Nullable HttpServletRequest request, if (group == null) { throw new ResourceNotFoundException("No such group: " + groupId); } - return converter.toRestPage(group.getMemberGroups(), optionalPageable, projection); + int total = groupService.countByParent(context, group); + Pageable pageable = utils.getPageable(optionalPageable); + List memberGroups = groupService.findByParent(context, group, pageable.getPageSize(), + Math.toIntExact(pageable.getOffset())); + return converter.toRestPage(memberGroups, pageable, total, projection); } catch (SQLException e) { throw new RuntimeException(e); } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/GroupRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/GroupRestRepositoryIT.java index 4d68652e249a..797657794a6e 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/GroupRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/GroupRestRepositoryIT.java @@ -3169,6 +3169,79 @@ public void epersonMemberPaginationTest() throws Exception { .andExpect(jsonPath("$.page.totalElements", is(5))); } + // Test of /groups/[uuid]/subgroups pagination + @Test + public void subgroupPaginationTest() throws Exception { + context.turnOffAuthorisationSystem(); + + Group group = GroupBuilder.createGroup(context) + .withName("Test group") + .build(); + + GroupBuilder.createGroup(context) + .withParent(group) + .withName("Test subgroup 1") + .build(); + GroupBuilder.createGroup(context) + .withParent(group) + .withName("Test subgroup 2") + .build(); + GroupBuilder.createGroup(context) + .withParent(group) + .withName("Test subgroup 3") + .build(); + GroupBuilder.createGroup(context) + .withParent(group) + .withName("Test subgroup 4") + .build(); + GroupBuilder.createGroup(context) + .withParent(group) + .withName("Test subgroup 5") + .build(); + + context.restoreAuthSystemState(); + + String authTokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(authTokenAdmin).perform(get("/api/eperson/groups/" + group.getID() + "/subgroups") + .param("page", "0") + .param("size", "2")) + .andExpect(status().isOk()).andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.subgroups", Matchers.everyItem( + hasJsonPath("$.type", is("group"))) + )) + .andExpect(jsonPath("$._embedded.subgroups").value(Matchers.hasSize(2))) + .andExpect(jsonPath("$.page.size", is(2))) + .andExpect(jsonPath("$.page.number", is(0))) + .andExpect(jsonPath("$.page.totalPages", is(3))) + .andExpect(jsonPath("$.page.totalElements", is(5))); + + getClient(authTokenAdmin).perform(get("/api/eperson/groups/" + group.getID() + "/subgroups") + .param("page", "1") + .param("size", "2")) + .andExpect(status().isOk()).andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.subgroups", Matchers.everyItem( + hasJsonPath("$.type", is("group"))) + )) + .andExpect(jsonPath("$._embedded.subgroups").value(Matchers.hasSize(2))) + .andExpect(jsonPath("$.page.size", is(2))) + .andExpect(jsonPath("$.page.number", is(1))) + .andExpect(jsonPath("$.page.totalPages", is(3))) + .andExpect(jsonPath("$.page.totalElements", is(5))); + + getClient(authTokenAdmin).perform(get("/api/eperson/groups/" + group.getID() + "/subgroups") + .param("page", "2") + .param("size", "2")) + .andExpect(status().isOk()).andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.subgroups", Matchers.everyItem( + hasJsonPath("$.type", is("group"))) + )) + .andExpect(jsonPath("$._embedded.subgroups").value(Matchers.hasSize(1))) + .andExpect(jsonPath("$.page.size", is(2))) + .andExpect(jsonPath("$.page.number", is(2))) + .andExpect(jsonPath("$.page.totalPages", is(3))) + .andExpect(jsonPath("$.page.totalElements", is(5))); + } + @Test public void commAdminAndColAdminCannotExploitItemReadGroupTest() throws Exception { From c000e54116498030261d988f87a496beef7d21d1 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 15 Sep 2023 17:08:03 -0500 Subject: [PATCH 474/510] Add basic unit test for new EpersonService methods --- .../java/org/dspace/eperson/EPersonTest.java | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/dspace-api/src/test/java/org/dspace/eperson/EPersonTest.java b/dspace-api/src/test/java/org/dspace/eperson/EPersonTest.java index b98db573566d..07f0fa4cd5ae 100644 --- a/dspace-api/src/test/java/org/dspace/eperson/EPersonTest.java +++ b/dspace-api/src/test/java/org/dspace/eperson/EPersonTest.java @@ -16,6 +16,7 @@ import java.sql.SQLException; import java.util.Iterator; import java.util.List; +import java.util.Set; import javax.mail.MessagingException; import org.apache.commons.codec.DecoderException; @@ -1029,6 +1030,42 @@ public void testCascadingDeleteSubmitterPreservesWorkflowItems() wfi.getSubmitter()); } + @Test + public void findAndCountByGroups() throws SQLException, AuthorizeException, IOException { + // Create a group with 3 EPerson members + Group group = createGroup("parentGroup"); + EPerson eperson1 = createEPersonAndAddToGroup("test1@example.com", group); + EPerson eperson2 = createEPersonAndAddToGroup("test2@example.com", group); + EPerson eperson3 = createEPersonAndAddToGroup("test3@example.com", group); + groupService.update(context, group); + + // Assert that findByGroup is the same list of EPersons as getMembers() when pagination is ignored + // (NOTE: Pagination is tested in GroupRestRepositoryIT) + assertEquals(group.getMembers(), ePersonService.findByGroups(context, Set.of(group), -1, -1)); + // Assert countByGroups is the same as the size of members + assertEquals(group.getMembers().size(), ePersonService.countByGroups(context, Set.of(group))); + + // Add another group with duplicate EPerson + Group group2 = createGroup("anotherGroup"); + groupService.addMember(context, group2, eperson1); + groupService.update(context, group2); + + // Verify countByGroups is still 3 (existing person should not be counted twice) + assertEquals(3, ePersonService.countByGroups(context, Set.of(group, group2))); + + // Add a new EPerson to new group, verify count goes up by one + EPerson eperson4 = createEPersonAndAddToGroup("test4@example.com", group2); + assertEquals(4, ePersonService.countByGroups(context, Set.of(group, group2))); + + // Clean up our data + groupService.delete(context, group); + groupService.delete(context, group2); + ePersonService.delete(context, eperson1); + ePersonService.delete(context, eperson2); + ePersonService.delete(context, eperson3); + ePersonService.delete(context, eperson4); + } + /** * Creates an item, sets the specified submitter. * @@ -1075,4 +1112,32 @@ private WorkspaceItem prepareWorkspaceItem(EPerson submitter) context.restoreAuthSystemState(); return wsi; } + + protected Group createGroup(String name) throws SQLException, AuthorizeException { + context.turnOffAuthorisationSystem(); + Group group = groupService.create(context); + group.setName(name); + groupService.update(context, group); + context.restoreAuthSystemState(); + return group; + } + + protected EPerson createEPersonAndAddToGroup(String email, Group group) throws SQLException, AuthorizeException { + context.turnOffAuthorisationSystem(); + EPerson ePerson = createEPerson(email); + groupService.addMember(context, group, ePerson); + groupService.update(context, group); + ePersonService.update(context, ePerson); + context.restoreAuthSystemState(); + return ePerson; + } + + protected EPerson createEPerson(String email) throws SQLException, AuthorizeException { + context.turnOffAuthorisationSystem(); + EPerson ePerson = ePersonService.create(context); + ePerson.setEmail(email); + ePersonService.update(context, ePerson); + context.restoreAuthSystemState(); + return ePerson; + } } From cdb68a6fdc925fcbb76f9265e64771497b3f78bc Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 18 Sep 2023 09:58:59 -0500 Subject: [PATCH 475/510] Minor unit test fix. Use isEqualCollection to compare list with Hibernate results --- .../src/test/java/org/dspace/eperson/EPersonTest.java | 9 ++++++++- .../src/test/java/org/dspace/eperson/GroupTest.java | 9 +++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/dspace-api/src/test/java/org/dspace/eperson/EPersonTest.java b/dspace-api/src/test/java/org/dspace/eperson/EPersonTest.java index 07f0fa4cd5ae..fb62edec0931 100644 --- a/dspace-api/src/test/java/org/dspace/eperson/EPersonTest.java +++ b/dspace-api/src/test/java/org/dspace/eperson/EPersonTest.java @@ -10,6 +10,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.io.IOException; @@ -20,6 +21,7 @@ import javax.mail.MessagingException; import org.apache.commons.codec.DecoderException; +import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; import org.dspace.AbstractUnitTest; @@ -1041,7 +1043,10 @@ public void findAndCountByGroups() throws SQLException, AuthorizeException, IOEx // Assert that findByGroup is the same list of EPersons as getMembers() when pagination is ignored // (NOTE: Pagination is tested in GroupRestRepositoryIT) - assertEquals(group.getMembers(), ePersonService.findByGroups(context, Set.of(group), -1, -1)); + // NOTE: isEqualCollection() must be used for comparison because Hibernate's "PersistentBag" cannot be compared + // directly to a List. See https://stackoverflow.com/a/57399383/3750035 + assertTrue(CollectionUtils.isEqualCollection(group.getMembers(), + ePersonService.findByGroups(context, Set.of(group), -1, -1))); // Assert countByGroups is the same as the size of members assertEquals(group.getMembers().size(), ePersonService.countByGroups(context, Set.of(group))); @@ -1058,12 +1063,14 @@ public void findAndCountByGroups() throws SQLException, AuthorizeException, IOEx assertEquals(4, ePersonService.countByGroups(context, Set.of(group, group2))); // Clean up our data + context.turnOffAuthorisationSystem(); groupService.delete(context, group); groupService.delete(context, group2); ePersonService.delete(context, eperson1); ePersonService.delete(context, eperson2); ePersonService.delete(context, eperson3); ePersonService.delete(context, eperson4); + context.restoreAuthSystemState(); } /** diff --git a/dspace-api/src/test/java/org/dspace/eperson/GroupTest.java b/dspace-api/src/test/java/org/dspace/eperson/GroupTest.java index 7666fcfe5431..a056c8061e54 100644 --- a/dspace-api/src/test/java/org/dspace/eperson/GroupTest.java +++ b/dspace-api/src/test/java/org/dspace/eperson/GroupTest.java @@ -22,10 +22,10 @@ import java.util.Collections; import java.util.List; +import org.apache.commons.collections4.CollectionUtils; import org.apache.logging.log4j.Logger; import org.dspace.AbstractUnitTest; import org.dspace.authorize.AuthorizeException; -import org.dspace.builder.GroupBuilder; import org.dspace.eperson.factory.EPersonServiceFactory; import org.dspace.eperson.service.EPersonService; import org.dspace.eperson.service.GroupService; @@ -636,15 +636,20 @@ public void findAndCountByParent() throws SQLException, AuthorizeException, IOEx // Assert that findByParent is the same list of groups as getMemberGroups() when pagination is ignored // (NOTE: Pagination is tested in GroupRestRepositoryIT) - assertEquals(parentGroup.getMemberGroups(), groupService.findByParent(context, parentGroup, -1, -1)); + // NOTE: isEqualCollection() must be used for comparison because Hibernate's "PersistentBag" cannot be compared + // directly to a List. See https://stackoverflow.com/a/57399383/3750035 + assertTrue(CollectionUtils.isEqualCollection(parentGroup.getMemberGroups(), + groupService.findByParent(context, parentGroup, -1, -1))); // Assert countBy parent is the same as the size of group members assertEquals(parentGroup.getMemberGroups().size(), groupService.countByParent(context, parentGroup)); // Clean up our data + context.turnOffAuthorisationSystem(); groupService.delete(context, parentGroup); groupService.delete(context, childGroup); groupService.delete(context, child2Group); groupService.delete(context, child3Group); + context.restoreAuthSystemState(); } From 58a15b72975940d48ae450e6b46557b4443f2978 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 18 Sep 2023 10:27:58 -0500 Subject: [PATCH 476/510] Add countAllMembers() with tests. Update tests to use try/catch --- .../org/dspace/eperson/GroupServiceImpl.java | 15 ++++ .../dspace/eperson/service/GroupService.java | 18 ++++- .../java/org/dspace/eperson/EPersonTest.java | 70 +++++++++++-------- .../java/org/dspace/eperson/GroupTest.java | 60 +++++++++++----- 4 files changed, 115 insertions(+), 48 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/eperson/GroupServiceImpl.java b/dspace-api/src/main/java/org/dspace/eperson/GroupServiceImpl.java index 4fdd1a3ba384..faf7b2b52a5e 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/GroupServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/GroupServiceImpl.java @@ -381,6 +381,21 @@ public List allMembers(Context c, Group g) throws SQLException { return new ArrayList<>(childGroupChildren); } + @Override + public int countAllMembers(Context context, Group group) throws SQLException { + // Get all groups which are a member of this group + List group2GroupCaches = group2GroupCacheDAO.findByParent(context, group); + Set groups = new HashSet<>(); + for (Group2GroupCache group2GroupCache : group2GroupCaches) { + groups.add(group2GroupCache.getChild()); + } + // Append current group as well + groups.add(group); + + // Return total number of unique EPerson objects in any of these groups + return ePersonService.countByGroups(context, groups); + } + @Override public Group find(Context context, UUID id) throws SQLException { if (id == null) { diff --git a/dspace-api/src/main/java/org/dspace/eperson/service/GroupService.java b/dspace-api/src/main/java/org/dspace/eperson/service/GroupService.java index 634fd0aca20c..ef3949149f14 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/service/GroupService.java +++ b/dspace-api/src/main/java/org/dspace/eperson/service/GroupService.java @@ -189,9 +189,11 @@ public interface GroupService extends DSpaceObjectService, DSpaceObjectLe Set allMemberGroupsSet(Context context, EPerson ePerson) throws SQLException; /** - * Get all of the epeople who are a member of the - * specified group, or a member of a sub-group of the + * Get all of the EPerson objects who are a member of the specified group, or a member of a subgroup of the * specified group, etc. + *

    + * WARNING: This method may have bad performance for Groups with a very large number of members, as it will load + * all member EPerson objects into memory. Only use if you need access to *every* EPerson object at once. * * @param context The relevant DSpace Context. * @param group Group object @@ -200,6 +202,18 @@ public interface GroupService extends DSpaceObjectService, DSpaceObjectLe */ public List allMembers(Context context, Group group) throws SQLException; + /** + * Count all of the EPerson objects who are a member of the specified group, or a member of a subgroup of the + * specified group, etc. + * In other words, this will return the size of "allMembers()" without having to load all EPerson objects into + * memory. + * @param context current DSpace context + * @param group Group object + * @return count of EPerson object members + * @throws SQLException if error + */ + int countAllMembers(Context context, Group group) throws SQLException; + /** * Find the group by its name - assumes name is unique * diff --git a/dspace-api/src/test/java/org/dspace/eperson/EPersonTest.java b/dspace-api/src/test/java/org/dspace/eperson/EPersonTest.java index fb62edec0931..6c162c30d1ad 100644 --- a/dspace-api/src/test/java/org/dspace/eperson/EPersonTest.java +++ b/dspace-api/src/test/java/org/dspace/eperson/EPersonTest.java @@ -1041,36 +1041,46 @@ public void findAndCountByGroups() throws SQLException, AuthorizeException, IOEx EPerson eperson3 = createEPersonAndAddToGroup("test3@example.com", group); groupService.update(context, group); - // Assert that findByGroup is the same list of EPersons as getMembers() when pagination is ignored - // (NOTE: Pagination is tested in GroupRestRepositoryIT) - // NOTE: isEqualCollection() must be used for comparison because Hibernate's "PersistentBag" cannot be compared - // directly to a List. See https://stackoverflow.com/a/57399383/3750035 - assertTrue(CollectionUtils.isEqualCollection(group.getMembers(), - ePersonService.findByGroups(context, Set.of(group), -1, -1))); - // Assert countByGroups is the same as the size of members - assertEquals(group.getMembers().size(), ePersonService.countByGroups(context, Set.of(group))); - - // Add another group with duplicate EPerson - Group group2 = createGroup("anotherGroup"); - groupService.addMember(context, group2, eperson1); - groupService.update(context, group2); - - // Verify countByGroups is still 3 (existing person should not be counted twice) - assertEquals(3, ePersonService.countByGroups(context, Set.of(group, group2))); - - // Add a new EPerson to new group, verify count goes up by one - EPerson eperson4 = createEPersonAndAddToGroup("test4@example.com", group2); - assertEquals(4, ePersonService.countByGroups(context, Set.of(group, group2))); - - // Clean up our data - context.turnOffAuthorisationSystem(); - groupService.delete(context, group); - groupService.delete(context, group2); - ePersonService.delete(context, eperson1); - ePersonService.delete(context, eperson2); - ePersonService.delete(context, eperson3); - ePersonService.delete(context, eperson4); - context.restoreAuthSystemState(); + Group group2 = null; + EPerson eperson4 = null; + + try { + // Assert that findByGroup is the same list of EPersons as getMembers() when pagination is ignored + // (NOTE: Pagination is tested in GroupRestRepositoryIT) + // NOTE: isEqualCollection() must be used for comparison because Hibernate's "PersistentBag" cannot be + // compared directly to a List. See https://stackoverflow.com/a/57399383/3750035 + assertTrue( + CollectionUtils.isEqualCollection(group.getMembers(), + ePersonService.findByGroups(context, Set.of(group), -1, -1))); + // Assert countByGroups is the same as the size of members + assertEquals(group.getMembers().size(), ePersonService.countByGroups(context, Set.of(group))); + + // Add another group with duplicate EPerson + group2 = createGroup("anotherGroup"); + groupService.addMember(context, group2, eperson1); + groupService.update(context, group2); + + // Verify countByGroups is still 3 (existing person should not be counted twice) + assertEquals(3, ePersonService.countByGroups(context, Set.of(group, group2))); + + // Add a new EPerson to new group, verify count goes up by one + eperson4 = createEPersonAndAddToGroup("test4@example.com", group2); + assertEquals(4, ePersonService.countByGroups(context, Set.of(group, group2))); + } finally { + // Clean up our data + context.turnOffAuthorisationSystem(); + groupService.delete(context, group); + if (group2 != null) { + groupService.delete(context, group2); + } + ePersonService.delete(context, eperson1); + ePersonService.delete(context, eperson2); + ePersonService.delete(context, eperson3); + if (eperson4 != null) { + ePersonService.delete(context, eperson4); + } + context.restoreAuthSystemState(); + } } /** diff --git a/dspace-api/src/test/java/org/dspace/eperson/GroupTest.java b/dspace-api/src/test/java/org/dspace/eperson/GroupTest.java index a056c8061e54..0eaacb6194ed 100644 --- a/dspace-api/src/test/java/org/dspace/eperson/GroupTest.java +++ b/dspace-api/src/test/java/org/dspace/eperson/GroupTest.java @@ -606,6 +606,30 @@ public void allMembers() throws SQLException, AuthorizeException, EPersonDeletio } } + @Test + public void countAllMembers() throws SQLException, AuthorizeException, EPersonDeletionException, IOException { + List allEPeopleAdded = new ArrayList<>(); + try { + context.turnOffAuthorisationSystem(); + allEPeopleAdded.add(createEPersonAndAddToGroup("allMemberGroups1@dspace.org", topGroup)); + allEPeopleAdded.add(createEPersonAndAddToGroup("allMemberGroups2@dspace.org", level1Group)); + allEPeopleAdded.add(createEPersonAndAddToGroup("allMemberGroups3@dspace.org", level2Group)); + context.restoreAuthSystemState(); + + assertEquals(3, groupService.countAllMembers(context, topGroup)); + assertEquals(2, groupService.countAllMembers(context, level1Group)); + assertEquals(1, groupService.countAllMembers(context, level2Group)); + } finally { + // Remove all the people added (in order to not impact other tests) + context.turnOffAuthorisationSystem(); + for (EPerson ePerson : allEPeopleAdded) { + ePersonService.delete(context, ePerson); + } + context.restoreAuthSystemState(); + } + } + + @Test public void isEmpty() throws SQLException, AuthorizeException, EPersonDeletionException, IOException { assertTrue(groupService.isEmpty(topGroup)); @@ -624,6 +648,7 @@ public void isEmpty() throws SQLException, AuthorizeException, EPersonDeletionEx @Test public void findAndCountByParent() throws SQLException, AuthorizeException, IOException { + // Create a parent group with 3 child groups Group parentGroup = createGroup("parentGroup"); Group childGroup = createGroup("childGroup"); @@ -634,22 +659,25 @@ public void findAndCountByParent() throws SQLException, AuthorizeException, IOEx groupService.addMember(context, parentGroup, child3Group); groupService.update(context, parentGroup); - // Assert that findByParent is the same list of groups as getMemberGroups() when pagination is ignored - // (NOTE: Pagination is tested in GroupRestRepositoryIT) - // NOTE: isEqualCollection() must be used for comparison because Hibernate's "PersistentBag" cannot be compared - // directly to a List. See https://stackoverflow.com/a/57399383/3750035 - assertTrue(CollectionUtils.isEqualCollection(parentGroup.getMemberGroups(), - groupService.findByParent(context, parentGroup, -1, -1))); - // Assert countBy parent is the same as the size of group members - assertEquals(parentGroup.getMemberGroups().size(), groupService.countByParent(context, parentGroup)); - - // Clean up our data - context.turnOffAuthorisationSystem(); - groupService.delete(context, parentGroup); - groupService.delete(context, childGroup); - groupService.delete(context, child2Group); - groupService.delete(context, child3Group); - context.restoreAuthSystemState(); + try { + // Assert that findByParent is the same list of groups as getMemberGroups() when pagination is ignored + // (NOTE: Pagination is tested in GroupRestRepositoryIT) + // NOTE: isEqualCollection() must be used for comparison because Hibernate's "PersistentBag" cannot be + // compared directly to a List. See https://stackoverflow.com/a/57399383/3750035 + assertTrue( + CollectionUtils.isEqualCollection(parentGroup.getMemberGroups(), + groupService.findByParent(context, parentGroup, -1, -1))); + // Assert countBy parent is the same as the size of group members + assertEquals(parentGroup.getMemberGroups().size(), groupService.countByParent(context, parentGroup)); + } finally { + // Clean up our data + context.turnOffAuthorisationSystem(); + groupService.delete(context, parentGroup); + groupService.delete(context, childGroup); + groupService.delete(context, child2Group); + groupService.delete(context, child3Group); + context.restoreAuthSystemState(); + } } From 2c9165afb08126189ee3367347e7011f89227b7c Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 18 Sep 2023 10:58:18 -0500 Subject: [PATCH 477/510] Replace several usages of allMembers() with count methods to avoid performance issues --- .../dspace/eperson/EPersonServiceImpl.java | 7 +++++-- .../org/dspace/eperson/GroupServiceImpl.java | 19 ++++++++++++------- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/eperson/EPersonServiceImpl.java b/dspace-api/src/main/java/org/dspace/eperson/EPersonServiceImpl.java index 5f17051dbbb0..ce117282de32 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/EPersonServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/EPersonServiceImpl.java @@ -305,10 +305,13 @@ public void delete(Context context, EPerson ePerson, boolean cascade) throw new AuthorizeException( "You must be an admin to delete an EPerson"); } + // Get all workflow-related groups that the current EPerson belongs to Set workFlowGroups = getAllWorkFlowGroups(context, ePerson); for (Group group: workFlowGroups) { - List ePeople = groupService.allMembers(context, group); - if (ePeople.size() == 1 && ePeople.contains(ePerson)) { + // Get total number of unique EPerson objs who are a member of this group (or subgroup) + int totalMembers = groupService.countAllMembers(context, group); + // If only one EPerson is a member, then we cannot delete the last member of this group. + if (totalMembers == 1) { throw new EmptyWorkflowGroupException(ePerson.getID(), group.getID()); } } diff --git a/dspace-api/src/main/java/org/dspace/eperson/GroupServiceImpl.java b/dspace-api/src/main/java/org/dspace/eperson/GroupServiceImpl.java index faf7b2b52a5e..d5d7ebcec11f 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/GroupServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/GroupServiceImpl.java @@ -179,8 +179,10 @@ public void removeMember(Context context, Group group, EPerson ePerson) throws S for (CollectionRole collectionRole : collectionRoles) { if (StringUtils.equals(collectionRole.getRoleId(), role.getId()) && claimedTask.getWorkflowItem().getCollection() == collectionRole.getCollection()) { - List ePeople = allMembers(context, group); - if (ePeople.size() == 1 && ePeople.contains(ePerson)) { + // Get total number of unique EPerson objs who are a member of this group (or subgroup) + int totalMembers = countAllMembers(context, group); + // If only one EPerson is a member, then we cannot delete the last member of this group. + if (totalMembers == 1) { throw new IllegalStateException( "Refused to remove user " + ePerson .getID() + " from workflow group because the group " + group @@ -191,8 +193,10 @@ public void removeMember(Context context, Group group, EPerson ePerson) throws S } } if (!poolTasks.isEmpty()) { - List ePeople = allMembers(context, group); - if (ePeople.size() == 1 && ePeople.contains(ePerson)) { + // Get total number of unique EPerson objs who are a member of this group (or subgroup) + int totalMembers = countAllMembers(context, group); + // If only one EPerson is a member, then we cannot delete the last member of this group. + if (totalMembers == 1) { throw new IllegalStateException( "Refused to remove user " + ePerson .getID() + " from workflow group because the group " + group @@ -212,9 +216,10 @@ public void removeMember(Context context, Group groupParent, Group childGroup) t if (!collectionRoles.isEmpty()) { List poolTasks = poolTaskService.findByGroup(context, groupParent); if (!poolTasks.isEmpty()) { - List parentPeople = allMembers(context, groupParent); - List childPeople = allMembers(context, childGroup); - if (childPeople.containsAll(parentPeople)) { + // Count number of Groups which have this groupParent as a direct parent + int totalChildren = countByParent(context, groupParent); + // If only one group has this as a parent, we cannot delete the last child group + if (totalChildren == 1) { throw new IllegalStateException( "Refused to remove sub group " + childGroup .getID() + " from workflow group because the group " + groupParent From 9832259aa06d9fe140407ed54c4687989e98f7b2 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 20 Sep 2023 15:15:44 -0500 Subject: [PATCH 478/510] Fix bug in logic for determining whether a workflow group will be left empty. Need to check *both* EPerson and subgroup counts. --- .../org/dspace/eperson/GroupServiceImpl.java | 31 ++++++++++++------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/eperson/GroupServiceImpl.java b/dspace-api/src/main/java/org/dspace/eperson/GroupServiceImpl.java index d5d7ebcec11f..20d29734cbaa 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/GroupServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/GroupServiceImpl.java @@ -179,10 +179,13 @@ public void removeMember(Context context, Group group, EPerson ePerson) throws S for (CollectionRole collectionRole : collectionRoles) { if (StringUtils.equals(collectionRole.getRoleId(), role.getId()) && claimedTask.getWorkflowItem().getCollection() == collectionRole.getCollection()) { - // Get total number of unique EPerson objs who are a member of this group (or subgroup) - int totalMembers = countAllMembers(context, group); - // If only one EPerson is a member, then we cannot delete the last member of this group. - if (totalMembers == 1) { + // Count number of EPersons who are *direct* members of this group + int totalDirectEPersons = ePersonService.countByGroups(context, Set.of(group)); + // Count number of Groups which have this groupParent as a direct parent + int totalChildGroups = countByParent(context, group); + // If this group has only one direct EPerson and *zero* child groups, then we cannot delete the + // EPerson or we will leave this group empty. + if (totalDirectEPersons == 1 && totalChildGroups == 0) { throw new IllegalStateException( "Refused to remove user " + ePerson .getID() + " from workflow group because the group " + group @@ -193,10 +196,13 @@ public void removeMember(Context context, Group group, EPerson ePerson) throws S } } if (!poolTasks.isEmpty()) { - // Get total number of unique EPerson objs who are a member of this group (or subgroup) - int totalMembers = countAllMembers(context, group); - // If only one EPerson is a member, then we cannot delete the last member of this group. - if (totalMembers == 1) { + // Count number of EPersons who are *direct* members of this group + int totalDirectEPersons = ePersonService.countByGroups(context, Set.of(group)); + // Count number of Groups which have this groupParent as a direct parent + int totalChildGroups = countByParent(context, group); + // If this group has only one direct EPerson and *zero* child groups, then we cannot delete the + // EPerson or we will leave this group empty. + if (totalDirectEPersons == 1 && totalChildGroups == 0) { throw new IllegalStateException( "Refused to remove user " + ePerson .getID() + " from workflow group because the group " + group @@ -217,9 +223,12 @@ public void removeMember(Context context, Group groupParent, Group childGroup) t List poolTasks = poolTaskService.findByGroup(context, groupParent); if (!poolTasks.isEmpty()) { // Count number of Groups which have this groupParent as a direct parent - int totalChildren = countByParent(context, groupParent); - // If only one group has this as a parent, we cannot delete the last child group - if (totalChildren == 1) { + int totalChildGroups = countByParent(context, groupParent); + // Count number of EPersons who are *direct* members of this group + int totalDirectEPersons = ePersonService.countByGroups(context, Set.of(groupParent)); + // If this group has only one childGroup and *zero* direct EPersons, then we cannot delete the + // childGroup or we will leave this group empty. + if (totalChildGroups == 1 && totalDirectEPersons == 0) { throw new IllegalStateException( "Refused to remove sub group " + childGroup .getID() + " from workflow group because the group " + groupParent From 9c0bf08cf4c3ab7e941ebe1bae66cf2aea720697 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 6 Oct 2023 13:25:47 -0500 Subject: [PATCH 479/510] Use join instead of subquery as join seems slightly faster. --- .../java/org/dspace/eperson/dao/impl/GroupDAOImpl.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/eperson/dao/impl/GroupDAOImpl.java b/dspace-api/src/main/java/org/dspace/eperson/dao/impl/GroupDAOImpl.java index f071a1bc754b..ad9c7b54fdb5 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/dao/impl/GroupDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/dao/impl/GroupDAOImpl.java @@ -199,7 +199,8 @@ public int countRows(Context context) throws SQLException { @Override public List findByParent(Context context, Group parent, int pageSize, int offset) throws SQLException { Query query = createQuery(context, - "from Group where (from Group g where g.id = :parent_id) in elements (parentGroups)"); + "SELECT g FROM Group g JOIN g.parentGroups pg " + + "WHERE pg.id = :parent_id"); query.setParameter("parent_id", parent.getID()); if (pageSize > 0) { query.setMaxResults(pageSize); @@ -213,8 +214,8 @@ public List findByParent(Context context, Group parent, int pageSize, int } public int countByParent(Context context, Group parent) throws SQLException { - Query query = createQuery(context, "SELECT count(*) from Group " + - "where (from Group g where g.id = :parent_id) in elements (parentGroups)"); + Query query = createQuery(context, "SELECT count(g) FROM Group g JOIN g.parentGroups pg " + + "WHERE pg.id = :parent_id"); query.setParameter("parent_id", parent.getID()); return count(query); From 6d86e65b720b5108e94b1df85e6038394c183214 Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Wed, 1 Nov 2023 00:43:17 +0100 Subject: [PATCH 480/510] 107671: Expose the handle.canonical.prefix to the frontend --- dspace/config/modules/rest.cfg | 1 + 1 file changed, 1 insertion(+) diff --git a/dspace/config/modules/rest.cfg b/dspace/config/modules/rest.cfg index 657e02b58de7..c27c3d3d1234 100644 --- a/dspace/config/modules/rest.cfg +++ b/dspace/config/modules/rest.cfg @@ -52,6 +52,7 @@ rest.properties.exposed = google.recaptcha.mode 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 #---------------------------------------------------------------# # These configs are used by the deprecated REST (v4-6) module # From 56aae347c2a7012af912a8893142fc04809e7ff3 Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Wed, 1 Nov 2023 00:47:31 +0100 Subject: [PATCH 481/510] Remove line breaks from default.license because they are being rendered in the frontend --- dspace/config/default.license | 44 +++++++++++------------------------ 1 file changed, 13 insertions(+), 31 deletions(-) diff --git a/dspace/config/default.license b/dspace/config/default.license index 0b5b3cb4b8f1..390e9786688d 100644 --- a/dspace/config/default.license +++ b/dspace/config/default.license @@ -3,34 +3,16 @@ This sample license is provided for informational purposes only. NON-EXCLUSIVE DISTRIBUTION LICENSE -By signing and submitting this license, you (the author(s) or copyright -owner) grants to DSpace University (DSU) the non-exclusive right to reproduce, -translate (as defined below), and/or distribute your submission (including -the abstract) worldwide in print and electronic format and in any medium, -including but not limited to audio or video. - -You agree that DSU may, without changing the content, translate the -submission to any medium or format for the purpose of preservation. - -You also agree that DSU may keep more than one copy of this submission for -purposes of security, back-up and preservation. - -You represent that the submission is your original work, and that you have -the right to grant the rights contained in this license. You also represent -that your submission does not, to the best of your knowledge, infringe upon -anyone's copyright. - -If the submission contains material for which you do not hold copyright, -you represent that you have obtained the unrestricted permission of the -copyright owner to grant DSU the rights required by this license, and that -such third-party owned material is clearly identified and acknowledged -within the text or content of the submission. - -IF THE SUBMISSION IS BASED UPON WORK THAT HAS BEEN SPONSORED OR SUPPORTED -BY AN AGENCY OR ORGANIZATION OTHER THAN DSU, YOU REPRESENT THAT YOU HAVE -FULFILLED ANY RIGHT OF REVIEW OR OTHER OBLIGATIONS REQUIRED BY SUCH -CONTRACT OR AGREEMENT. - -DSU will clearly identify your name(s) as the author(s) or owner(s) of the -submission, and will not make any alteration, other than as allowed by this -license, to your submission. +By signing and submitting this license, you (the author(s) or copyright owner) grants to DSpace University (DSU) the non-exclusive right to reproduce, translate (as defined below), and/or distribute your submission (including the abstract) worldwide in print and electronic format and in any medium, including but not limited to audio or video. + +You agree that DSU may, without changing the content, translate the submission to any medium or format for the purpose of preservation. + +You also agree that DSU may keep more than one copy of this submission for purposes of security, back-up and preservation. + +You represent that the submission is your original work, and that you have the right to grant the rights contained in this license. You also represent that your submission does not, to the best of your knowledge, infringe upon anyone's copyright. + +If the submission contains material for which you do not hold copyright, you represent that you have obtained the unrestricted permission of the copyright owner to grant DSU the rights required by this license, and that such third-party owned material is clearly identified and acknowledged within the text or content of the submission. + +IF THE SUBMISSION IS BASED UPON WORK THAT HAS BEEN SPONSORED OR SUPPORTED BY AN AGENCY OR ORGANIZATION OTHER THAN DSU, YOU REPRESENT THAT YOU HAVE FULFILLED ANY RIGHT OF REVIEW OR OTHER OBLIGATIONS REQUIRED BY SUCH CONTRACT OR AGREEMENT. + +DSU will clearly identify your name(s) as the author(s) or owner(s) of the submission, and will not make any alteration, other than as allowed by this license, to your submission. From b40ad0dfc23040f335d6c6be0fcd0ae6e68a318f Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Wed, 1 Nov 2023 12:19:25 +0100 Subject: [PATCH 482/510] Simplified the process of fixing the tests after adding new sidebar facets/search filters and sort options to discover.xml --- .../app/rest/DiscoveryRestControllerIT.java | 671 ++++++++++-------- 1 file changed, 377 insertions(+), 294 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryRestControllerIT.java index a115c8aa2f15..80d8ab2df422 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryRestControllerIT.java @@ -26,6 +26,8 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; import java.util.UUID; import com.jayway.jsonpath.matchers.JsonPathMatchers; @@ -69,6 +71,7 @@ import org.dspace.supervision.SupervisionOrder; import org.dspace.xmlworkflow.storedcomponents.ClaimedTask; import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem; +import org.hamcrest.Matcher; import org.hamcrest.Matchers; import org.junit.Ignore; import org.junit.Test; @@ -85,6 +88,24 @@ public class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest @Autowired ChoiceAuthorityService choiceAuthorityService; + /** + * This field has been created to easily modify the tests when updating the defaultConfiguration's sidebar facets + */ + List> customSidebarFacets = List.of( + ); + + /** + * This field has been created to easily modify the tests when updating the defaultConfiguration's search filters + */ + List> customSearchFilters = List.of( + ); + + /** + * This field has been created to easily modify the tests when updating the defaultConfiguration's sort fields + */ + List> customSortFields = List.of( + ); + @Test public void rootDiscoverTest() throws Exception { @@ -105,6 +126,14 @@ public void rootDiscoverTest() throws Exception { @Test public void discoverFacetsTestWithoutParameters() throws Exception { + List> allExpectedSidebarFacets = new ArrayList<>(customSidebarFacets); + allExpectedSidebarFacets.addAll(List.of( + FacetEntryMatcher.authorFacet(false), + FacetEntryMatcher.entityTypeFacet(false), + FacetEntryMatcher.dateIssuedFacet(false), + FacetEntryMatcher.subjectFacet(false), + FacetEntryMatcher.hasContentInOriginalBundleFacet(false) + )); //When we call this facets endpoint getClient().perform(get("/api/discover/facets")) @@ -116,13 +145,7 @@ public void discoverFacetsTestWithoutParameters() throws Exception { //There needs to be a self link to this endpoint .andExpect(jsonPath("$._links.self.href", containsString("api/discover/facets"))) //We have 4 facets in the default configuration, they need to all be present in the embedded section - .andExpect(jsonPath("$._embedded.facets", containsInAnyOrder( - FacetEntryMatcher.authorFacet(false), - FacetEntryMatcher.entityTypeFacet(false), - FacetEntryMatcher.dateIssuedFacet(false), - FacetEntryMatcher.subjectFacet(false), - FacetEntryMatcher.hasContentInOriginalBundleFacet(false))) - ); + .andExpect(jsonPath("$._embedded.facets", containsInAnyOrder(allExpectedSidebarFacets))); } @Test @@ -266,7 +289,7 @@ public void discoverFacetsAuthorWithAuthorityWithSizeParameter() throws Exceptio .andExpect(status().isOk()) //The type needs to be 'discover' .andExpect(jsonPath("$.type", is("discover"))) - //The name of the facet needs to be seubject, because that's what we called + //The name of the facet needs to be author, because that's what we called .andExpect(jsonPath("$.name", is("author"))) //Because we've constructed such a structure so that we have more than 2 (size) subjects, there // needs to be a next link @@ -1194,6 +1217,34 @@ public void discoverFacetsDateTestWithSearchFilter() throws Exception { @Test public void discoverSearchTest() throws Exception { + List> allExpectedSearchFilters = new ArrayList<>(customSearchFilters); + allExpectedSearchFilters.addAll(List.of( + SearchFilterMatcher.titleFilter(), + SearchFilterMatcher.authorFilter(), + SearchFilterMatcher.subjectFilter(), + SearchFilterMatcher.dateIssuedFilter(), + SearchFilterMatcher.hasContentInOriginalBundleFilter(), + SearchFilterMatcher.hasFileNameInOriginalBundleFilter(), + SearchFilterMatcher.hasFileDescriptionInOriginalBundleFilter(), + SearchFilterMatcher.entityTypeFilter(), + SearchFilterMatcher.isAuthorOfPublicationRelation(), + SearchFilterMatcher.isProjectOfPublicationRelation(), + SearchFilterMatcher.isOrgUnitOfPublicationRelation(), + SearchFilterMatcher.isPublicationOfJournalIssueRelation(), + SearchFilterMatcher.isJournalOfPublicationRelation() + )); + + List> allExpectedSortFields = new ArrayList<>(customSortFields); + allExpectedSortFields.addAll(List.of( + SortOptionMatcher.sortOptionMatcher( + "score", DiscoverySortFieldConfiguration.SORT_ORDER.desc.name()), + SortOptionMatcher.sortOptionMatcher( + "dc.title", DiscoverySortFieldConfiguration.SORT_ORDER.asc.name()), + SortOptionMatcher.sortOptionMatcher( + "dc.date.issued", DiscoverySortFieldConfiguration.SORT_ORDER.desc.name()), + SortOptionMatcher.sortOptionMatcher( + "dc.date.accessioned", DiscoverySortFieldConfiguration.SORT_ORDER.desc.name()) + )); //When calling this root endpoint getClient().perform(get("/api/discover/search")) @@ -1208,32 +1259,9 @@ public void discoverSearchTest() throws Exception { .andExpect(jsonPath("$._links.self.href", containsString("api/discover/search"))) //There needs to be a section where these filters as specified as they're the default filters // given in the configuration - .andExpect(jsonPath("$.filters", containsInAnyOrder( - SearchFilterMatcher.titleFilter(), - SearchFilterMatcher.authorFilter(), - SearchFilterMatcher.subjectFilter(), - SearchFilterMatcher.dateIssuedFilter(), - SearchFilterMatcher.hasContentInOriginalBundleFilter(), - SearchFilterMatcher.hasFileNameInOriginalBundleFilter(), - SearchFilterMatcher.hasFileDescriptionInOriginalBundleFilter(), - SearchFilterMatcher.entityTypeFilter(), - SearchFilterMatcher.isAuthorOfPublicationRelation(), - SearchFilterMatcher.isProjectOfPublicationRelation(), - SearchFilterMatcher.isOrgUnitOfPublicationRelation(), - SearchFilterMatcher.isPublicationOfJournalIssueRelation(), - SearchFilterMatcher.isJournalOfPublicationRelation() - ))) + .andExpect(jsonPath("$.filters", containsInAnyOrder(allExpectedSearchFilters))) //These sortOptions need to be present as it's the default in the configuration - .andExpect(jsonPath("$.sortOptions", contains( - SortOptionMatcher.sortOptionMatcher( - "score", DiscoverySortFieldConfiguration.SORT_ORDER.desc.name()), - SortOptionMatcher.sortOptionMatcher( - "dc.title", DiscoverySortFieldConfiguration.SORT_ORDER.asc.name()), - SortOptionMatcher.sortOptionMatcher( - "dc.date.issued", DiscoverySortFieldConfiguration.SORT_ORDER.desc.name()), - SortOptionMatcher.sortOptionMatcher( - "dc.date.accessioned", DiscoverySortFieldConfiguration.SORT_ORDER.desc.name()) - ))); + .andExpect(jsonPath("$.sortOptions", contains(allExpectedSortFields))); } @Test @@ -1337,6 +1365,14 @@ public void discoverSearchObjectsTest() throws Exception { //** WHEN ** //An anonymous user browses this endpoint to find the objects in the system + List> allExpectedSidebarFacets = new ArrayList<>(customSidebarFacets); + allExpectedSidebarFacets.addAll(List.of( + FacetEntryMatcher.authorFacet(false), + FacetEntryMatcher.entityTypeFacet(false), + FacetEntryMatcher.subjectFacet(false), + FacetEntryMatcher.dateIssuedFacet(false), + FacetEntryMatcher.hasContentInOriginalBundleFacet(false) + )); getClient().perform(get("/api/discover/search/objects")) //** THEN ** //The status has to be 200 OK @@ -1363,13 +1399,7 @@ public void discoverSearchObjectsTest() throws Exception { ))) //These facets have to show up in the embedded.facets section as well with the given hasMore // property because we don't exceed their default limit for a hasMore true (the default is 10) - .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder( - FacetEntryMatcher.authorFacet(false), - FacetEntryMatcher.entityTypeFacet(false), - FacetEntryMatcher.subjectFacet(false), - FacetEntryMatcher.dateIssuedFacet(false), - FacetEntryMatcher.hasContentInOriginalBundleFacet(false) - ))) + .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder(allExpectedSidebarFacets))) //There always needs to be a self link .andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects"))) ; @@ -1473,6 +1503,14 @@ public void discoverSearchObjectsTestHasMoreAuthorFacet() throws Exception { //** WHEN ** //An anonymous user browses this endpoint to find the objects in the system + List> allExpectedSidebarFacets = new ArrayList<>(customSidebarFacets); + allExpectedSidebarFacets.addAll(List.of( + FacetEntryMatcher.authorFacet(true), + FacetEntryMatcher.entityTypeFacet(false), + FacetEntryMatcher.subjectFacet(false), + FacetEntryMatcher.dateIssuedFacet(false), + FacetEntryMatcher.hasContentInOriginalBundleFacet(false) + )); getClient().perform(get("/api/discover/search/objects")) //** THEN ** //The status has to be 200 OK @@ -1502,13 +1540,7 @@ public void discoverSearchObjectsTestHasMoreAuthorFacet() throws Exception { // property because we don't exceed their default limit for a hasMore true (the default is 10) //We do however exceed the limit for the authors, so this property has to be true for the author // facet - .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder( - FacetEntryMatcher.authorFacet(true), - FacetEntryMatcher.entityTypeFacet(false), - FacetEntryMatcher.subjectFacet(false), - FacetEntryMatcher.dateIssuedFacet(false), - FacetEntryMatcher.hasContentInOriginalBundleFacet(false) - ))) + .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder(allExpectedSidebarFacets))) //There always needs to be a self link available .andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects"))) ; @@ -1564,7 +1596,15 @@ public void discoverSearchObjectsTestHasMoreSubjectFacet() throws Exception { context.restoreAuthSystemState(); //** WHEN ** - //An anonymous user browses this endpoint to find the the objects in the system + //An anonymous user browses this endpoint to find the objects in the system + List> allExpectedSidebarFacets = new ArrayList<>(customSidebarFacets); + allExpectedSidebarFacets.addAll(List.of( + FacetEntryMatcher.authorFacet(false), + FacetEntryMatcher.entityTypeFacet(false), + FacetEntryMatcher.subjectFacet(true), + FacetEntryMatcher.dateIssuedFacet(false), + FacetEntryMatcher.hasContentInOriginalBundleFacet(false) + )); getClient().perform(get("/api/discover/search/objects")) //** THEN ** @@ -1592,13 +1632,7 @@ public void discoverSearchObjectsTestHasMoreSubjectFacet() throws Exception { // property because we don't exceed their default limit for a hasMore true (the default is 10) //We do however exceed the limit for the subject, so this property has to be true for the subject // facet - .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder( - FacetEntryMatcher.authorFacet(false), - FacetEntryMatcher.entityTypeFacet(false), - FacetEntryMatcher.subjectFacet(true), - FacetEntryMatcher.dateIssuedFacet(false), - FacetEntryMatcher.hasContentInOriginalBundleFacet(false) - ))) + .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder(allExpectedSidebarFacets))) //There always needs to be a self link available .andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects"))) ; @@ -1645,8 +1679,16 @@ public void discoverSearchObjectsTestWithBasicQuery() throws Exception { context.restoreAuthSystemState(); //** WHEN ** - //An anonymous user browses this endpoint to find the the objects in the system + //An anonymous user browses this endpoint to find the objects in the system //With a query that says that the title has to contain 'test' + List> allExpectedSidebarFacets = new ArrayList<>(customSidebarFacets); + allExpectedSidebarFacets.addAll(List.of( + FacetEntryMatcher.authorFacet(false), + FacetEntryMatcher.entityTypeFacet(false), + FacetEntryMatcher.subjectFacet(false), + FacetEntryMatcher.dateIssuedFacet(false), + FacetEntryMatcher.hasContentInOriginalBundleFacet(false) + )); getClient().perform(get("/api/discover/search/objects") .param("f.title", "test,contains")) @@ -1666,19 +1708,13 @@ public void discoverSearchObjectsTestWithBasicQuery() throws Exception { SearchResultMatcher.match("core", "item", "items"), SearchResultMatcher.match("core", "item", "items") ))) - //We need to display the appliedFilters object that contains the query that we've ran + //We need to display the appliedFilters object that contains the query that we've run .andExpect(jsonPath("$.appliedFilters", contains( AppliedFilterMatcher.appliedFilterEntry("title", "contains", "test", "test") ))) //These facets have to show up in the embedded.facets section as well with the given hasMore // property because we don't exceed their default limit for a hasMore true (the default is 10) - .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder( - FacetEntryMatcher.authorFacet(false), - FacetEntryMatcher.entityTypeFacet(false), - FacetEntryMatcher.subjectFacet(false), - FacetEntryMatcher.dateIssuedFacet(false), - FacetEntryMatcher.hasContentInOriginalBundleFacet(false) - ))) + .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder(allExpectedSidebarFacets))) //There always needs to be a self link available .andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects"))) ; @@ -1751,8 +1787,16 @@ public void discoverSearchObjectsTestWithScope() throws Exception { context.restoreAuthSystemState(); //** WHEN ** - //An anonymous user browses this endpoint to find the the objects in the system + //An anonymous user browses this endpoint to find the objects in the system //With a scope 'test' + List> allExpectedSidebarFacets = new ArrayList<>(customSidebarFacets); + allExpectedSidebarFacets.addAll(List.of( + FacetEntryMatcher.authorFacet(false), + FacetEntryMatcher.entityTypeFacet(false), + FacetEntryMatcher.subjectFacet(false), + FacetEntryMatcher.dateIssuedFacet(false), + FacetEntryMatcher.hasContentInOriginalBundleFacet(false) + )); getClient().perform(get("/api/discover/search/objects") .param("scope", "test")) @@ -1780,13 +1824,7 @@ public void discoverSearchObjectsTestWithScope() throws Exception { ))) //These facets have to show up in the embedded.facets section as well with the given hasMore // property because we don't exceed their default limit for a hasMore true (the default is 10) - .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder( - FacetEntryMatcher.authorFacet(false), - FacetEntryMatcher.entityTypeFacet(false), - FacetEntryMatcher.subjectFacet(false), - FacetEntryMatcher.dateIssuedFacet(false), - FacetEntryMatcher.hasContentInOriginalBundleFacet(false) - ))) + .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder(allExpectedSidebarFacets))) //There always needs to be a self link available .andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects"))) ; @@ -1835,9 +1873,17 @@ public void discoverSearchObjectsTestWithDsoType() throws Exception { context.restoreAuthSystemState(); // ** WHEN ** - // An anonymous user browses this endpoint to find the the objects in the system + // An anonymous user browses this endpoint to find the objects in the system // With dsoType 'item' + List> allExpectedSidebarFacetsWithDsoTypeItem = new ArrayList<>(customSidebarFacets); + allExpectedSidebarFacetsWithDsoTypeItem.addAll(List.of( + FacetEntryMatcher.authorFacet(false), + FacetEntryMatcher.entityTypeFacet(false), + FacetEntryMatcher.subjectFacet(false), + FacetEntryMatcher.dateIssuedFacet(false), + FacetEntryMatcher.hasContentInOriginalBundleFacet(false) + )); getClient().perform(get("/api/discover/search/objects") .param("dsoType", "Item")) @@ -1860,17 +1906,20 @@ public void discoverSearchObjectsTestWithDsoType() throws Exception { ))) //These facets have to show up in the embedded.facets section as well with the given hasMore // property because we don't exceed their default limit for a hasMore true (the default is 10) - .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder( - FacetEntryMatcher.authorFacet(false), - FacetEntryMatcher.entityTypeFacet(false), - FacetEntryMatcher.subjectFacet(false), - FacetEntryMatcher.dateIssuedFacet(false), - FacetEntryMatcher.hasContentInOriginalBundleFacet(false) - ))) + .andExpect(jsonPath("$._embedded.facets", + Matchers.containsInAnyOrder(allExpectedSidebarFacetsWithDsoTypeItem))) //There always needs to be a self link available .andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects"))); // With dsoTypes 'community' and 'collection' + List> allExpectedSidebarFacetsWithDsoTypesComCol = new ArrayList<>(customSidebarFacets); + allExpectedSidebarFacetsWithDsoTypesComCol.addAll(List.of( + FacetEntryMatcher.authorFacet(false), + FacetEntryMatcher.entityTypeFacet(false), + FacetEntryMatcher.subjectFacet(false), + FacetEntryMatcher.dateIssuedFacet(false), + FacetEntryMatcher.hasContentInOriginalBundleFacet(false) + )); getClient().perform(get("/api/discover/search/objects") .param("dsoType", "Community") .param("dsoType", "Collection")) @@ -1895,17 +1944,21 @@ public void discoverSearchObjectsTestWithDsoType() throws Exception { ))) //These facets have to show up in the embedded.facets section as well with the given hasMore // property because we don't exceed their default limit for a hasMore true (the default is 10) - .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder( - FacetEntryMatcher.authorFacet(false), - FacetEntryMatcher.entityTypeFacet(false), - FacetEntryMatcher.subjectFacet(false), - FacetEntryMatcher.dateIssuedFacet(false), - FacetEntryMatcher.hasContentInOriginalBundleFacet(false) - ))) + .andExpect(jsonPath("$._embedded.facets", + Matchers.containsInAnyOrder(allExpectedSidebarFacetsWithDsoTypesComCol))) //There always needs to be a self link available .andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects"))); // With dsoTypes 'collection' and 'item' + List> allExpectedSidebarFacetsWithDsoTypesColItem = + new ArrayList<>(customSidebarFacets); + allExpectedSidebarFacetsWithDsoTypesColItem.addAll(List.of( + FacetEntryMatcher.authorFacet(false), + FacetEntryMatcher.entityTypeFacet(false), + FacetEntryMatcher.subjectFacet(false), + FacetEntryMatcher.dateIssuedFacet(false), + FacetEntryMatcher.hasContentInOriginalBundleFacet(false) + )); getClient().perform(get("/api/discover/search/objects") .param("dsoType", "Collection") .param("dsoType", "Item")) @@ -1931,17 +1984,21 @@ public void discoverSearchObjectsTestWithDsoType() throws Exception { ))) //These facets have to show up in the embedded.facets section as well with the given hasMore // property because we don't exceed their default limit for a hasMore true (the default is 10) - .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder( - FacetEntryMatcher.authorFacet(false), - FacetEntryMatcher.entityTypeFacet(false), - FacetEntryMatcher.subjectFacet(false), - FacetEntryMatcher.dateIssuedFacet(false), - FacetEntryMatcher.hasContentInOriginalBundleFacet(false) - ))) + .andExpect(jsonPath("$._embedded.facets", + Matchers.containsInAnyOrder(allExpectedSidebarFacetsWithDsoTypesColItem))) //There always needs to be a self link available .andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects"))); // With dsoTypes 'community', 'collection' and 'item' + List> allExpectedSidebarFacetsWithDsoTypesComColItem = + new ArrayList<>(customSidebarFacets); + allExpectedSidebarFacetsWithDsoTypesComColItem.addAll(List.of( + FacetEntryMatcher.authorFacet(false), + FacetEntryMatcher.entityTypeFacet(false), + FacetEntryMatcher.subjectFacet(false), + FacetEntryMatcher.dateIssuedFacet(false), + FacetEntryMatcher.hasContentInOriginalBundleFacet(false) + )); getClient().perform(get("/api/discover/search/objects") .param("dsoType", "Community") .param("dsoType", "Collection") @@ -1971,13 +2028,8 @@ public void discoverSearchObjectsTestWithDsoType() throws Exception { ))) //These facets have to show up in the embedded.facets section as well with the given hasMore // property because we don't exceed their default limit for a hasMore true (the default is 10) - .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder( - FacetEntryMatcher.authorFacet(false), - FacetEntryMatcher.entityTypeFacet(false), - FacetEntryMatcher.subjectFacet(false), - FacetEntryMatcher.dateIssuedFacet(false), - FacetEntryMatcher.hasContentInOriginalBundleFacet(false) - ))) + .andExpect(jsonPath("$._embedded.facets", + Matchers.containsInAnyOrder(allExpectedSidebarFacetsWithDsoTypesComColItem))) //There always needs to be a self link available .andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects"))); } @@ -2024,9 +2076,17 @@ public void discoverSearchObjectsTestWithDsoTypeAndSort() throws Exception { context.restoreAuthSystemState(); //** WHEN ** - //An anonymous user browses this endpoint to find the the objects in the system + //An anonymous user browses this endpoint to find the objects in the system //With a dsoType 'item' //And a sort on the dc.title ascending + List> allExpectedSidebarFacets = new ArrayList<>(customSidebarFacets); + allExpectedSidebarFacets.addAll(List.of( + FacetEntryMatcher.authorFacet(false), + FacetEntryMatcher.entityTypeFacet(false), + FacetEntryMatcher.subjectFacet(false), + FacetEntryMatcher.dateIssuedFacet(false), + FacetEntryMatcher.hasContentInOriginalBundleFacet(false) + )); getClient().perform(get("/api/discover/search/objects") .param("dsoType", "Item") .param("sort", "dc.title,ASC")) @@ -2058,13 +2118,7 @@ public void discoverSearchObjectsTestWithDsoTypeAndSort() throws Exception { ))) //These facets have to show up in the embedded.facets section as well with the given hasMore // property because we don't exceed their default limit for a hasMore true (the default is 10) - .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder( - FacetEntryMatcher.authorFacet(false), - FacetEntryMatcher.entityTypeFacet(false), - FacetEntryMatcher.subjectFacet(false), - FacetEntryMatcher.dateIssuedFacet(false), - FacetEntryMatcher.hasContentInOriginalBundleFacet(false) - ))) + .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder(allExpectedSidebarFacets))) //We want to get the sort that's been used as well in the response .andExpect(jsonPath("$.sort", is( SortOptionMatcher.sortByAndOrder("dc.title", "ASC") @@ -2246,8 +2300,16 @@ public void discoverSearchObjectsTestForPaginationAndNextLinks() throws Exceptio context.restoreAuthSystemState(); //** WHEN ** - //An anonymous user browses this endpoint to find the the objects in the system + //An anonymous user browses this endpoint to find the objects in the system //With a size 2 + List> allExpectedSidebarFacets = new ArrayList<>(customSidebarFacets); + allExpectedSidebarFacets.addAll(List.of( + FacetEntryMatcher.authorFacet(true), + FacetEntryMatcher.subjectFacet(true), + FacetEntryMatcher.dateIssuedFacet(false), + FacetEntryMatcher.hasContentInOriginalBundleFacet(false), + FacetEntryMatcher.entityTypeFacet(false) + )); getClient().perform(get("/api/discover/search/objects") .param("size", "2") .param("page", "1")) @@ -2270,13 +2332,7 @@ public void discoverSearchObjectsTestForPaginationAndNextLinks() throws Exceptio SearchResultMatcher.match(), SearchResultMatcher.match() ))) - .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder( - FacetEntryMatcher.authorFacet(true), - FacetEntryMatcher.subjectFacet(true), - FacetEntryMatcher.dateIssuedFacet(false), - FacetEntryMatcher.hasContentInOriginalBundleFacet(false), - FacetEntryMatcher.entityTypeFacet(false) - ))) + .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder(allExpectedSidebarFacets))) //There always needs to be a self link available .andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects"))) ; @@ -2339,8 +2395,16 @@ public void discoverSearchObjectsTestWithContentInABitstream() throws Exception context.restoreAuthSystemState(); //** WHEN ** - //An anonymous user browses this endpoint to find the the objects in the system + //An anonymous user browses this endpoint to find the objects in the system //With a query stating 'ThisIsSomeDummyText' + List> allExpectedSidebarFacets = new ArrayList<>(customSidebarFacets); + allExpectedSidebarFacets.addAll(List.of( + FacetEntryMatcher.authorFacet(false), + FacetEntryMatcher.entityTypeFacet(false), + FacetEntryMatcher.subjectFacet(false), + FacetEntryMatcher.dateIssuedFacet(false), + FacetEntryMatcher.hasContentInOriginalBundleFacet(false) + )); getClient().perform(get("/api/discover/search/objects") .param("query", "ThisIsSomeDummyText")) @@ -2360,13 +2424,7 @@ public void discoverSearchObjectsTestWithContentInABitstream() throws Exception //These facets have to show up in the embedded.facets section as well with the given hasMore // property because we don't exceed their default limit for a hasMore true (the default is 10) - .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder( - FacetEntryMatcher.authorFacet(false), - FacetEntryMatcher.entityTypeFacet(false), - FacetEntryMatcher.subjectFacet(false), - FacetEntryMatcher.dateIssuedFacet(false), - FacetEntryMatcher.hasContentInOriginalBundleFacet(false) - ))) + .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder(allExpectedSidebarFacets))) //There always needs to be a self link available .andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects"))) ; @@ -2419,8 +2477,15 @@ public void discoverSearchObjectsTestForEmbargoedItemsAndPrivateItems() throws E //Turn on the authorization again context.restoreAuthSystemState(); //** WHEN ** - //An anonymous user browses this endpoint to find the the objects in the system - // + //An anonymous user browses this endpoint to find the objects in the system + List> allExpectedSidebarFacets = new ArrayList<>(customSidebarFacets); + allExpectedSidebarFacets.addAll(List.of( + FacetEntryMatcher.authorFacet(false), + FacetEntryMatcher.entityTypeFacet(false), + FacetEntryMatcher.subjectFacet(false), + FacetEntryMatcher.dateIssuedFacet(false), + FacetEntryMatcher.hasContentInOriginalBundleFacet(false) + )); getClient().perform(get("/api/discover/search/objects")) //** THEN ** //The status has to be 200 OK @@ -2450,13 +2515,7 @@ public void discoverSearchObjectsTestForEmbargoedItemsAndPrivateItems() throws E ))) //These facets have to show up in the embedded.facets section as well with the given hasMore // property because we don't exceed their default limit for a hasMore true (the default is 10) - .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder( - FacetEntryMatcher.authorFacet(false), - FacetEntryMatcher.entityTypeFacet(false), - FacetEntryMatcher.subjectFacet(false), - FacetEntryMatcher.dateIssuedFacet(false), - FacetEntryMatcher.hasContentInOriginalBundleFacet(false) - ))) + .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder(allExpectedSidebarFacets))) //There always needs to be a self link available .andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects"))) ; @@ -2514,7 +2573,7 @@ public void discoverSearchObjectsTestWithContentInAPrivateBitstream() throws Exc context.restoreAuthSystemState(); context.setCurrentUser(null); //** WHEN ** - //An anonymous user browses this endpoint to find the the objects in the system + //An anonymous user browses this endpoint to find the objects in the system //With a size 2 getClient().perform(get("/api/discover/search/objects") .param("query", "ThisIsSomeDummyText")) @@ -2591,8 +2650,16 @@ public void discoverSearchObjectsTestForScope() throws Exception { UUID scope = col2.getID(); //** WHEN ** - //An anonymous user browses this endpoint to find the the objects in the system + //An anonymous user browses this endpoint to find the objects in the system //With the scope given + List> allExpectedSidebarFacets = new ArrayList<>(customSidebarFacets); + allExpectedSidebarFacets.addAll(List.of( + FacetEntryMatcher.authorFacet(false), + FacetEntryMatcher.entityTypeFacet(false), + FacetEntryMatcher.subjectFacet(false), + FacetEntryMatcher.dateIssuedFacet(false), + FacetEntryMatcher.hasContentInOriginalBundleFacet(false) + )); getClient().perform(get("/api/discover/search/objects") .param("scope", String.valueOf(scope))) //** THEN ** @@ -2613,13 +2680,7 @@ public void discoverSearchObjectsTestForScope() throws Exception { ))) //These facets have to show up in the embedded.facets section as well with the given hasMore // property because we don't exceed their default limit for a hasMore true (the default is 10) - .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder( - FacetEntryMatcher.authorFacet(false), - FacetEntryMatcher.entityTypeFacet(false), - FacetEntryMatcher.subjectFacet(false), - FacetEntryMatcher.dateIssuedFacet(false), - FacetEntryMatcher.hasContentInOriginalBundleFacet(false) - ))) + .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder(allExpectedSidebarFacets))) //There always needs to be a self link available .andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects"))) ; @@ -2670,8 +2731,16 @@ public void discoverSearchObjectsTestForScopeWithPrivateItem() throws Exception UUID scope = col2.getID(); //** WHEN ** - //An anonymous user browses this endpoint to find the the objects in the system + //An anonymous user browses this endpoint to find the objects in the system //With a size 2 + List> allExpectedSidebarFacets = new ArrayList<>(customSidebarFacets); + allExpectedSidebarFacets.addAll(List.of( + FacetEntryMatcher.authorFacet(false), + FacetEntryMatcher.entityTypeFacet(false), + FacetEntryMatcher.subjectFacet(false), + FacetEntryMatcher.dateIssuedFacet(false), + FacetEntryMatcher.hasContentInOriginalBundleFacet(false) + )); getClient().perform(get("/api/discover/search/objects") .param("scope", String.valueOf(scope))) //** THEN ** @@ -2698,13 +2767,7 @@ public void discoverSearchObjectsTestForScopeWithPrivateItem() throws Exception )))) //These facets have to show up in the embedded.facets section as well with the given hasMore // property because we don't exceed their default limit for a hasMore true (the default is 10) - .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder( - FacetEntryMatcher.authorFacet(false), - FacetEntryMatcher.entityTypeFacet(false), - FacetEntryMatcher.subjectFacet(false), - FacetEntryMatcher.dateIssuedFacet(false), - FacetEntryMatcher.hasContentInOriginalBundleFacet(false) - ))) + .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder(allExpectedSidebarFacets))) //There always needs to be a self link available .andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects"))) ; @@ -2856,8 +2919,16 @@ public void discoverSearchObjectsTestForHitHighlights() throws Exception { String query = "Public"; //** WHEN ** - //An anonymous user browses this endpoint to find the the objects in the system + //An anonymous user browses this endpoint to find the objects in the system //With a query stating 'public' + List> allExpectedSidebarFacets = new ArrayList<>(customSidebarFacets); + allExpectedSidebarFacets.addAll(List.of( + FacetEntryMatcher.authorFacet(false), + FacetEntryMatcher.entityTypeFacet(false), + FacetEntryMatcher.subjectFacet(false), + FacetEntryMatcher.dateIssuedFacet(false), + FacetEntryMatcher.hasContentInOriginalBundleFacet(false) + )); getClient().perform(get("/api/discover/search/objects") .param("query", query)) //** THEN ** @@ -2878,13 +2949,7 @@ public void discoverSearchObjectsTestForHitHighlights() throws Exception { ))) //These facets have to show up in the embedded.facets section as well with the given hasMore // property because we don't exceed their default limit for a hasMore true (the default is 10) - .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder( - FacetEntryMatcher.authorFacet(false), - FacetEntryMatcher.entityTypeFacet(false), - FacetEntryMatcher.subjectFacet(false), - FacetEntryMatcher.dateIssuedFacet(false), - FacetEntryMatcher.hasContentInOriginalBundleFacet(false) - ))) + .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder(allExpectedSidebarFacets))) //There always needs to be a self link available .andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects"))) ; @@ -2934,7 +2999,7 @@ public void discoverSearchObjectsTestForHitHighlightsWithPrivateItem() throws Ex String query = "Public"; //** WHEN ** - //An anonymous user browses this endpoint to find the the objects in the system + //An anonymous user browses this endpoint to find the objects in the system //With a query stating 'Public' getClient().perform(get("/api/discover/search/objects") .param("query", query)) @@ -3000,10 +3065,17 @@ public void discoverSearchObjectsWithQueryOperatorContains_query() throws Except context.restoreAuthSystemState(); - UUID scope = col2.getID(); //** WHEN ** - //An anonymous user browses this endpoint to find the the objects in the system + //An anonymous user browses this endpoint to find the objects in the system //With the given search filter + List> allExpectedSidebarFacets = new ArrayList<>(customSidebarFacets); + allExpectedSidebarFacets.addAll(List.of( + FacetEntryMatcher.authorFacet(false), + FacetEntryMatcher.entityTypeFacet(false), + FacetEntryMatcher.subjectFacet(false), + FacetEntryMatcher.dateIssuedFacet(false), + FacetEntryMatcher.hasContentInOriginalBundleFacet(false) + )); getClient().perform(get("/api/discover/search/objects") .param("f.title", "test*,query")) //** THEN ** @@ -3022,13 +3094,7 @@ public void discoverSearchObjectsWithQueryOperatorContains_query() throws Except ))) //These facets have to show up in the embedded.facets section as well with the given hasMore property // because we don't exceed their default limit for a hasMore true (the default is 10) - .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder( - FacetEntryMatcher.authorFacet(false), - FacetEntryMatcher.entityTypeFacet(false), - FacetEntryMatcher.subjectFacet(false), - FacetEntryMatcher.dateIssuedFacet(false), - FacetEntryMatcher.hasContentInOriginalBundleFacet(false) - ))) + .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder(allExpectedSidebarFacets))) //There always needs to be a self link available .andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects"))) ; @@ -3076,10 +3142,17 @@ public void discoverSearchObjectsWithQueryOperatorContains() throws Exception { context.restoreAuthSystemState(); - UUID scope = col2.getID(); //** WHEN ** - //An anonymous user browses this endpoint to find the the objects in the system + //An anonymous user browses this endpoint to find the objects in the system //With the given search filter + List> allExpectedSidebarFacets = new ArrayList<>(customSidebarFacets); + allExpectedSidebarFacets.addAll(List.of( + FacetEntryMatcher.authorFacet(false), + FacetEntryMatcher.entityTypeFacet(false), + FacetEntryMatcher.subjectFacet(false), + FacetEntryMatcher.dateIssuedFacet(false), + FacetEntryMatcher.hasContentInOriginalBundleFacet(false) + )); getClient().perform(get("/api/discover/search/objects") .param("f.title", "test,contains")) //** THEN ** @@ -3098,14 +3171,9 @@ public void discoverSearchObjectsWithQueryOperatorContains() throws Exception { ))) //These facets have to show up in the embedded.facets section as well with the given hasMore property // because we don't exceed their default limit for a hasMore true (the default is 10) - .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder( - FacetEntryMatcher.authorFacet(false), - FacetEntryMatcher.entityTypeFacet(false), - FacetEntryMatcher.subjectFacet(false), - FacetEntryMatcher.dateIssuedFacet(false), - FacetEntryMatcher.hasContentInOriginalBundleFacet(false) - ))) - //There always needs to be a self link available + .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder(allExpectedSidebarFacets))) + + //There always needs to be a self link available .andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects"))) ; @@ -3151,10 +3219,17 @@ public void discoverSearchObjectsWithQueryOperatorNotContains_query() throws Exc context.restoreAuthSystemState(); - UUID scope = col2.getID(); //** WHEN ** - //An anonymous user browses this endpoint to find the the objects in the system + //An anonymous user browses this endpoint to find the objects in the system //With the given search filter + List> allExpectedSidebarFacets = new ArrayList<>(customSidebarFacets); + allExpectedSidebarFacets.addAll(List.of( + FacetEntryMatcher.authorFacet(false), + FacetEntryMatcher.entityTypeFacet(false), + FacetEntryMatcher.subjectFacet(false), + FacetEntryMatcher.dateIssuedFacet(false), + FacetEntryMatcher.hasContentInOriginalBundleFacet(false) + )); getClient().perform(get("/api/discover/search/objects") .param("f.title", "-test*,query")) //** THEN ** @@ -3172,13 +3247,7 @@ public void discoverSearchObjectsWithQueryOperatorNotContains_query() throws Exc ))) //These facets have to show up in the embedded.facets section as well with the given hasMore property // because we don't exceed their default limit for a hasMore true (the default is 10) - .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder( - FacetEntryMatcher.authorFacet(false), - FacetEntryMatcher.entityTypeFacet(false), - FacetEntryMatcher.subjectFacet(false), - FacetEntryMatcher.dateIssuedFacet(false), - FacetEntryMatcher.hasContentInOriginalBundleFacet(false) - ))) + .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder(allExpectedSidebarFacets))) //There always needs to be a self link available .andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects"))) ; @@ -3226,10 +3295,17 @@ public void discoverSearchObjectsWithQueryOperatorNotContains() throws Exception context.restoreAuthSystemState(); - UUID scope = col2.getID(); //** WHEN ** - //An anonymous user browses this endpoint to find the the objects in the system + //An anonymous user browses this endpoint to find the objects in the system //With the given search filter + List> allExpectedSidebarFacets = new ArrayList<>(customSidebarFacets); + allExpectedSidebarFacets.addAll(List.of( + FacetEntryMatcher.authorFacet(false), + FacetEntryMatcher.entityTypeFacet(false), + FacetEntryMatcher.subjectFacet(false), + FacetEntryMatcher.dateIssuedFacet(false), + FacetEntryMatcher.hasContentInOriginalBundleFacet(false) + )); getClient().perform(get("/api/discover/search/objects") .param("f.title", "test,notcontains")) //** THEN ** @@ -3247,13 +3323,8 @@ public void discoverSearchObjectsWithQueryOperatorNotContains() throws Exception ))) //These facets have to show up in the embedded.facets section as well with the given hasMore property // because we don't exceed their default limit for a hasMore true (the default is 10) - .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder( - FacetEntryMatcher.authorFacet(false), - FacetEntryMatcher.entityTypeFacet(false), - FacetEntryMatcher.subjectFacet(false), - FacetEntryMatcher.dateIssuedFacet(false), - FacetEntryMatcher.hasContentInOriginalBundleFacet(false) - ))) + .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder(allExpectedSidebarFacets))) + //There always needs to be a self link available .andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects"))) ; @@ -3308,8 +3379,16 @@ public void discoverSearchObjectsTestForMinMaxValues() throws Exception { context.restoreAuthSystemState(); //** WHEN ** - //An anonymous user browses this endpoint to find the the objects in the system + //An anonymous user browses this endpoint to find the objects in the system //With a size 2 + List> allExpectedSidebarFacets = new ArrayList<>(customSidebarFacets); + allExpectedSidebarFacets.addAll(List.of( + FacetEntryMatcher.authorFacetWithMinMax(true, "Doe, Jane", "Testing, Works"), + FacetEntryMatcher.entityTypeFacet(false), + FacetEntryMatcher.subjectFacet(true), + FacetEntryMatcher.dateIssuedFacetWithMinMax(false, "1990-02-13", "2010-10-17"), + FacetEntryMatcher.hasContentInOriginalBundleFacet(false) + )); getClient().perform(get("/api/discover/search/objects") .param("size", "2") .param("page", "1")) @@ -3332,13 +3411,7 @@ public void discoverSearchObjectsTestForMinMaxValues() throws Exception { SearchResultMatcher.match(), SearchResultMatcher.match() ))) - .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder( - FacetEntryMatcher.authorFacetWithMinMax(true, "Doe, Jane", "Testing, Works"), - FacetEntryMatcher.entityTypeFacet(false), - FacetEntryMatcher.subjectFacet(true), - FacetEntryMatcher.dateIssuedFacetWithMinMax(false, "1990-02-13", "2010-10-17"), - FacetEntryMatcher.hasContentInOriginalBundleFacet(false) - ))) + .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder(allExpectedSidebarFacets))) //There always needs to be a self link available .andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects"))) ; @@ -3393,21 +3466,23 @@ public void discoverSearchFacetsTestForMinMaxValues() throws Exception { context.restoreAuthSystemState(); //** WHEN ** - //An anonymous user browses this endpoint to find the the objects in the system + //An anonymous user browses this endpoint to find the objects in the system //With a size 2 + List> allExpectedSidebarFacets = new ArrayList<>(customSidebarFacets); + allExpectedSidebarFacets.addAll(List.of( + FacetEntryMatcher.authorFacetWithMinMax(true, "Doe, Jane", "Testing, Works"), + FacetEntryMatcher.entityTypeFacet(false), + FacetEntryMatcher.subjectFacet(true), + FacetEntryMatcher.dateIssuedFacetWithMinMax(false, "1990-02-13", "2010-10-17"), + FacetEntryMatcher.hasContentInOriginalBundleFacet(false) + )); getClient().perform(get("/api/discover/search/facets")) //** THEN ** //The status has to be 200 OK .andExpect(status().isOk()) //The type has to be 'discover' .andExpect(jsonPath("$.type", is("discover"))) - .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder( - FacetEntryMatcher.authorFacetWithMinMax(true, "Doe, Jane", "Testing, Works"), - FacetEntryMatcher.entityTypeFacet(false), - FacetEntryMatcher.subjectFacet(true), - FacetEntryMatcher.dateIssuedFacetWithMinMax(false, "1990-02-13", "2010-10-17"), - FacetEntryMatcher.hasContentInOriginalBundleFacet(false) - ))) + .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder(allExpectedSidebarFacets))) //There always needs to be a self link available .andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/facets"))) ; @@ -3454,10 +3529,17 @@ public void discoverSearchObjectsWithQueryOperatorEquals_query() throws Exceptio context.restoreAuthSystemState(); - UUID scope = col2.getID(); //** WHEN ** - //An anonymous user browses this endpoint to find the the objects in the system + //An anonymous user browses this endpoint to find the objects in the system //With the given search filter + List> allExpectedSidebarFacets = new ArrayList<>(customSidebarFacets); + allExpectedSidebarFacets.addAll(List.of( + FacetEntryMatcher.authorFacet(false), + FacetEntryMatcher.entityTypeFacet(false), + FacetEntryMatcher.subjectFacet(false), + FacetEntryMatcher.dateIssuedFacet(false), + FacetEntryMatcher.hasContentInOriginalBundleFacet(false) + )); getClient().perform(get("/api/discover/search/objects") .param("f.title", "Test,query")) //** THEN ** @@ -3475,13 +3557,7 @@ public void discoverSearchObjectsWithQueryOperatorEquals_query() throws Exceptio ))) //These facets have to show up in the embedded.facets section as well with the given hasMore property // because we don't exceed their default limit for a hasMore true (the default is 10) - .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder( - FacetEntryMatcher.authorFacet(false), - FacetEntryMatcher.entityTypeFacet(false), - FacetEntryMatcher.subjectFacet(false), - FacetEntryMatcher.dateIssuedFacet(false), - FacetEntryMatcher.hasContentInOriginalBundleFacet(false) - ))) + .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder(allExpectedSidebarFacets))) //There always needs to be a self link available .andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects"))) ; @@ -3529,10 +3605,17 @@ public void discoverSearchObjectsWithQueryOperatorEquals() throws Exception { context.restoreAuthSystemState(); - UUID scope = col2.getID(); //** WHEN ** - //An anonymous user browses this endpoint to find the the objects in the system + //An anonymous user browses this endpoint to find the objects in the system //With the given search filter + List> allExpectedSidebarFacets = new ArrayList<>(customSidebarFacets); + allExpectedSidebarFacets.addAll(List.of( + FacetEntryMatcher.authorFacet(false), + FacetEntryMatcher.entityTypeFacet(false), + FacetEntryMatcher.subjectFacet(false), + FacetEntryMatcher.dateIssuedFacet(false), + FacetEntryMatcher.hasContentInOriginalBundleFacet(false) + )); getClient().perform(get("/api/discover/search/objects") .param("f.title", "Test,equals")) //** THEN ** @@ -3550,13 +3633,7 @@ public void discoverSearchObjectsWithQueryOperatorEquals() throws Exception { ))) //These facets have to show up in the embedded.facets section as well with the given hasMore property // because we don't exceed their default limit for a hasMore true (the default is 10) - .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder( - FacetEntryMatcher.authorFacet(false), - FacetEntryMatcher.entityTypeFacet(false), - FacetEntryMatcher.subjectFacet(false), - FacetEntryMatcher.dateIssuedFacet(false), - FacetEntryMatcher.hasContentInOriginalBundleFacet(false) - ))) + .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder(allExpectedSidebarFacets))) //There always needs to be a self link available .andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects"))) ; @@ -3603,10 +3680,17 @@ public void discoverSearchObjectsWithQueryOperatorNotEquals_query() throws Excep context.restoreAuthSystemState(); - UUID scope = col2.getID(); //** WHEN ** - //An anonymous user browses this endpoint to find the the objects in the system + //An anonymous user browses this endpoint to find the objects in the system //With the given search filter + List> allExpectedSidebarFacets = new ArrayList<>(customSidebarFacets); + allExpectedSidebarFacets.addAll(List.of( + FacetEntryMatcher.authorFacet(false), + FacetEntryMatcher.entityTypeFacet(false), + FacetEntryMatcher.subjectFacet(false), + FacetEntryMatcher.dateIssuedFacet(false), + FacetEntryMatcher.hasContentInOriginalBundleFacet(false) + )); getClient().perform(get("/api/discover/search/objects") .param("f.title", "-Test,query")) //** THEN ** @@ -3625,13 +3709,7 @@ public void discoverSearchObjectsWithQueryOperatorNotEquals_query() throws Excep ))) //These facets have to show up in the embedded.facets section as well with the given hasMore property // because we don't exceed their default limit for a hasMore true (the default is 10) - .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder( - FacetEntryMatcher.authorFacet(false), - FacetEntryMatcher.entityTypeFacet(false), - FacetEntryMatcher.subjectFacet(false), - FacetEntryMatcher.dateIssuedFacet(false), - FacetEntryMatcher.hasContentInOriginalBundleFacet(false) - ))) + .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder(allExpectedSidebarFacets))) //There always needs to be a self link available .andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects"))) ; @@ -3679,10 +3757,17 @@ public void discoverSearchObjectsWithQueryOperatorNotEquals() throws Exception { context.restoreAuthSystemState(); - UUID scope = col2.getID(); //** WHEN ** - //An anonymous user browses this endpoint to find the the objects in the system + //An anonymous user browses this endpoint to find the objects in the system //With the given search filter + List> allExpectedSidebarFacets = new ArrayList<>(customSidebarFacets); + allExpectedSidebarFacets.addAll(List.of( + FacetEntryMatcher.authorFacet(false), + FacetEntryMatcher.entityTypeFacet(false), + FacetEntryMatcher.subjectFacet(false), + FacetEntryMatcher.dateIssuedFacet(false), + FacetEntryMatcher.hasContentInOriginalBundleFacet(false) + )); getClient().perform(get("/api/discover/search/objects") .param("f.title", "Test,notequals")) //** THEN ** @@ -3701,13 +3786,7 @@ public void discoverSearchObjectsWithQueryOperatorNotEquals() throws Exception { ))) //These facets have to show up in the embedded.facets section as well with the given hasMore property // because we don't exceed their default limit for a hasMore true (the default is 10) - .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder( - FacetEntryMatcher.authorFacet(false), - FacetEntryMatcher.entityTypeFacet(false), - FacetEntryMatcher.subjectFacet(false), - FacetEntryMatcher.dateIssuedFacet(false), - FacetEntryMatcher.hasContentInOriginalBundleFacet(false) - ))) + .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder(allExpectedSidebarFacets))) //There always needs to be a self link available .andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects"))) ; @@ -3754,10 +3833,17 @@ public void discoverSearchObjectsWithQueryOperatorNotAuthority_query() throws Ex context.restoreAuthSystemState(); - UUID scope = col2.getID(); //** WHEN ** - //An anonymous user browses this endpoint to find the the objects in the system + //An anonymous user browses this endpoint to find the objects in the system //With the given search filter + List> allExpectedSidebarFacets = new ArrayList<>(customSidebarFacets); + allExpectedSidebarFacets.addAll(List.of( + FacetEntryMatcher.authorFacet(false), + FacetEntryMatcher.entityTypeFacet(false), + FacetEntryMatcher.subjectFacet(false), + FacetEntryMatcher.dateIssuedFacet(false), + FacetEntryMatcher.hasContentInOriginalBundleFacet(false) + )); getClient().perform(get("/api/discover/search/objects") .param("f.title", "-id:test,query")) //** THEN ** @@ -3775,13 +3861,7 @@ public void discoverSearchObjectsWithQueryOperatorNotAuthority_query() throws Ex ))) //These facets have to show up in the embedded.facets section as well with the given hasMore property // because we don't exceed their default limit for a hasMore true (the default is 10) - .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder( - FacetEntryMatcher.authorFacet(false), - FacetEntryMatcher.entityTypeFacet(false), - FacetEntryMatcher.subjectFacet(false), - FacetEntryMatcher.dateIssuedFacet(false), - FacetEntryMatcher.hasContentInOriginalBundleFacet(false) - ))) + .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder(allExpectedSidebarFacets))) //There always needs to be a self link available .andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects"))) ; @@ -3829,10 +3909,17 @@ public void discoverSearchObjectsWithQueryOperatorNotAuthority() throws Exceptio context.restoreAuthSystemState(); - UUID scope = col2.getID(); //** WHEN ** - //An anonymous user browses this endpoint to find the the objects in the system + //An anonymous user browses this endpoint to find the objects in the system //With the given search filter + List> allExpectedSidebarFacets = new ArrayList<>(customSidebarFacets); + allExpectedSidebarFacets.addAll(List.of( + FacetEntryMatcher.authorFacet(false), + FacetEntryMatcher.entityTypeFacet(false), + FacetEntryMatcher.subjectFacet(false), + FacetEntryMatcher.dateIssuedFacet(false), + FacetEntryMatcher.hasContentInOriginalBundleFacet(false) + )); getClient().perform(get("/api/discover/search/objects") .param("f.title", "test,notauthority")) //** THEN ** @@ -3850,13 +3937,7 @@ public void discoverSearchObjectsWithQueryOperatorNotAuthority() throws Exceptio ))) //These facets have to show up in the embedded.facets section as well with the given hasMore property // because we don't exceed their default limit for a hasMore true (the default is 10) - .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder( - FacetEntryMatcher.authorFacet(false), - FacetEntryMatcher.entityTypeFacet(false), - FacetEntryMatcher.subjectFacet(false), - FacetEntryMatcher.dateIssuedFacet(false), - FacetEntryMatcher.hasContentInOriginalBundleFacet(false) - ))) + .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder(allExpectedSidebarFacets))) //There always needs to be a self link available .andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects"))) ; @@ -3866,7 +3947,7 @@ public void discoverSearchObjectsWithQueryOperatorNotAuthority() throws Exceptio @Test public void discoverSearchObjectsWithMissingQueryOperator() throws Exception { //** WHEN ** - // An anonymous user browses this endpoint to find the the objects in the system + // An anonymous user browses this endpoint to find the objects in the system // With the given search filter where there is the filter operator missing in the value (must be of form // <:filter-value>,<:filter-operator>) getClient().perform(get("/api/discover/search/objects") @@ -3879,10 +3960,10 @@ public void discoverSearchObjectsWithMissingQueryOperator() throws Exception { @Test public void discoverSearchObjectsWithNotValidQueryOperator() throws Exception { //** WHEN ** - // An anonymous user browses this endpoint to find the the objects in the system + // An anonymous user browses this endpoint to find the objects in the system // With the given search filter where there is a non-valid filter operator given (must be of form // <:filter-value>,<:filter-operator> where the filter operator is one of: “contains”, “notcontains”, "equals" - // “notequals”, “authority”, “notauthority”, "query”); see enum RestSearchOperator + // “notequals”, “authority”, “notauthority”, "query"); see enum RestSearchOperator getClient().perform(get("/api/discover/search/objects") .param("f.title", "test,operator")) //** THEN ** @@ -4180,8 +4261,8 @@ public void discoverSearchObjectsTestWithUnEscapedLuceneCharactersTest() throws @Test /** - * This test is intent to verify that inprogress submission (workspaceitem, workflowitem, pool task and claimed - * tasks) don't interfers with the standard search + * This test is intended to verify that an in progress submission (workspaceitem, workflowitem, pool task and + * claimed tasks) don't interfere with the standard search * * @throws Exception */ @@ -4231,7 +4312,7 @@ public void discoverSearchObjectsWithInProgressSubmissionTest() throws Exception .withSubject("ExtraEntry") .build(); - //3. three inprogress submission from a normal user (2 ws, 1 wf that will produce also a pooltask) + //3. three in progress submission from a normal user (2 ws, 1 wf that will produce also a pooltask) context.setCurrentUser(eperson); WorkspaceItem wsItem1 = WorkspaceItemBuilder.createWorkspaceItem(context, col1).withTitle("Workspace Item 1") .build(); @@ -4246,7 +4327,7 @@ public void discoverSearchObjectsWithInProgressSubmissionTest() throws Exception ClaimedTask cTask = ClaimedTaskBuilder.createClaimedTask(context, col2, admin).withTitle("Claimed Item") .build(); - // 5. other inprogress submissions made by the administrator + // 5. other in progress submissions made by the administrator context.setCurrentUser(admin); WorkspaceItem wsItem1Admin = WorkspaceItemBuilder.createWorkspaceItem(context, col1) .withTitle("Admin Workspace Item 1").build(); @@ -4261,7 +4342,15 @@ public void discoverSearchObjectsWithInProgressSubmissionTest() throws Exception //** WHEN ** // An anonymous user, the submitter and the admin that browse this endpoint to find the public objects in the - // system should not retrieve the inprogress submissions and related objects + // system should not retrieve the in progress submissions and related objects + List> allExpectedSidebarFacets = new ArrayList<>(customSidebarFacets); + allExpectedSidebarFacets.addAll(List.of( + FacetEntryMatcher.authorFacet(false), + FacetEntryMatcher.subjectFacet(false), + FacetEntryMatcher.dateIssuedFacet(false), + FacetEntryMatcher.hasContentInOriginalBundleFacet(false), + FacetEntryMatcher.entityTypeFacet(false) + )); String[] tokens = new String[] { null, getAuthToken(eperson.getEmail(), password), @@ -4297,13 +4386,7 @@ public void discoverSearchObjectsWithInProgressSubmissionTest() throws Exception ))) //These facets have to show up in the embedded.facets section as well with the given hasMore // property because we don't exceed their default limit for a hasMore true (the default is 10) - .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder( - FacetEntryMatcher.authorFacet(false), - FacetEntryMatcher.subjectFacet(false), - FacetEntryMatcher.dateIssuedFacet(false), - FacetEntryMatcher.hasContentInOriginalBundleFacet(false), - FacetEntryMatcher.entityTypeFacet(false) - ))) + .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder(allExpectedSidebarFacets))) //There always needs to be a self link .andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects"))) ; @@ -4366,7 +4449,7 @@ public void discoverSearchObjectsWorkspaceConfigurationTest() throws Exception { .withSubject("ExtraEntry") .build(); - //3. three inprogress submission from our submitter user (2 ws, 1 wf that will produce also a pooltask) + //3. three in progress submission from our submitter user (2 ws, 1 wf that will produce also a pooltask) WorkspaceItem wsItem1 = WorkspaceItemBuilder.createWorkspaceItem(context, col1).withTitle("Workspace Item 1") .withIssueDate("2010-07-23") .build(); @@ -4384,7 +4467,7 @@ public void discoverSearchObjectsWorkspaceConfigurationTest() throws Exception { .withIssueDate("2010-11-03") .build(); - // 5. other inprogress submissions made by the administrator + // 5. other in progress submissions made by the administrator context.setCurrentUser(admin); WorkspaceItem wsItem1Admin = WorkspaceItemBuilder.createWorkspaceItem(context, col1) .withIssueDate("2010-07-23") @@ -4568,7 +4651,7 @@ public void discoverSearchObjectsWorkflowConfigurationTest() throws Exception { .withSubject("ExtraEntry") .build(); - //3. three inprogress submission from a normal user (2 ws, 1 wf that will produce also a pooltask) + //3. three in progress submission from a normal user (2 ws, 1 wf that will produce also a pooltask) context.setCurrentUser(eperson); WorkspaceItem wsItem1 = WorkspaceItemBuilder.createWorkspaceItem(context, col1).withTitle("Workspace Item 1") .withIssueDate("2010-07-23") @@ -4587,7 +4670,7 @@ public void discoverSearchObjectsWorkflowConfigurationTest() throws Exception { .withIssueDate("2010-11-03") .build(); - // 5. other inprogress submissions made by the administrator + // 5. other in progress submissions made by the administrator context.setCurrentUser(admin); WorkspaceItem wsItem1Admin = WorkspaceItemBuilder.createWorkspaceItem(context, col1) .withIssueDate("2010-07-23") @@ -4601,7 +4684,7 @@ public void discoverSearchObjectsWorkflowConfigurationTest() throws Exception { .withIssueDate("2010-11-03") .withTitle("Admin Workflow Item 1").build(); - // 6. a pool taks in the second step of the workflow + // 6. a pool task in the second step of the workflow ClaimedTask cTask2 = ClaimedTaskBuilder.createClaimedTask(context, col2, admin).withTitle("Pool Step2 Item") .withIssueDate("2010-11-04") .build(); @@ -4628,7 +4711,7 @@ public void discoverSearchObjectsWorkflowConfigurationTest() throws Exception { // 1 pool task in step 1, submitted by the same regular submitter // 1 pool task in step 1, submitted by the admin // 1 claimed task in the first workflow step from the repository admin - // 1 pool task task in step 2, from the repository admin + // 1 pool task in step 2, from the repository admin // (This one is created by creating a claimed task for step 1 and approving it) //** WHEN ** @@ -4838,7 +4921,7 @@ public void discoverSearchObjectsWorkflowAdminConfigurationTest() throws Excepti .withSubject("ExtraEntry") .build(); - //3. three inprogress submission from a normal user (2 ws, 1 wf that will produce also a pooltask) + //3. three in progress submission from a normal user (2 ws, 1 wf that will produce also a pooltask) context.setCurrentUser(eperson); WorkspaceItem wsItem1 = WorkspaceItemBuilder.createWorkspaceItem(context, col1).withTitle("Workspace Item 1") .withIssueDate("2010-07-23") @@ -4857,7 +4940,7 @@ public void discoverSearchObjectsWorkflowAdminConfigurationTest() throws Excepti .withIssueDate("2010-11-03") .build(); - // 5. other inprogress submissions made by the administrator + // 5. other in progress submissions made by the administrator context.setCurrentUser(admin); WorkspaceItem wsItem1Admin = WorkspaceItemBuilder.createWorkspaceItem(context, col1) .withIssueDate("2010-07-23") @@ -4871,7 +4954,7 @@ public void discoverSearchObjectsWorkflowAdminConfigurationTest() throws Excepti .withIssueDate("2010-11-03") .withTitle("Admin Workflow Item 1").build(); - // 6. a pool taks in the second step of the workflow + // 6. a pool task in the second step of the workflow ClaimedTask cTask2 = ClaimedTaskBuilder.createClaimedTask(context, col2, admin).withTitle("Pool Step2 Item") .withIssueDate("2010-11-04") .build(); @@ -4898,7 +4981,7 @@ public void discoverSearchObjectsWorkflowAdminConfigurationTest() throws Excepti // 1 pool task in step 1, submitted by the same regular submitter // 1 pool task in step 1, submitted by the admin // 1 claimed task in the first workflow step from the repository admin - // 1 pool task task in step 2, from the repository admin + // 1 pool task in step 2, from the repository admin // (This one is created by creating a claimed task for step 1 and approving it) //** WHEN ** @@ -6586,7 +6669,7 @@ public void discoverSearchObjectsSupervisionConfigurationTest() throws Exception .withSubject("ExtraEntry") .build(); - //3. three inprogress submission from a normal user (2 ws, 1 wf that will produce also a pooltask) + //3. three in progress submission from a normal user (2 ws, 1 wf that will produce also a pooltask) context.setCurrentUser(eperson); WorkspaceItem wsItem1 = WorkspaceItemBuilder.createWorkspaceItem(context, col1) .withTitle("Workspace Item 1") @@ -6616,7 +6699,7 @@ public void discoverSearchObjectsSupervisionConfigurationTest() throws Exception .withIssueDate("2010-11-03") .build(); - // 5. other inprogress submissions made by the administrator + // 5. other in progress submissions made by the administrator context.setCurrentUser(admin); WorkspaceItem wsItem1Admin = WorkspaceItemBuilder.createWorkspaceItem(context, col1) .withIssueDate("2010-07-23") From f011a5a5dbcd2def47dde7830981cf282ca660aa Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 1 Nov 2023 11:16:09 -0500 Subject: [PATCH 483/510] Address feedback. Initialize HashSet sizes to avoid resizing. Correct comment about indeterminante ordering. --- .../src/main/java/org/dspace/eperson/GroupServiceImpl.java | 7 +++++-- .../src/main/java/org/dspace/eperson/dao/EPersonDAO.java | 4 ++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/eperson/GroupServiceImpl.java b/dspace-api/src/main/java/org/dspace/eperson/GroupServiceImpl.java index 20d29734cbaa..c2f2ea68bdd7 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/GroupServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/GroupServiceImpl.java @@ -382,7 +382,8 @@ public List allMembers(Context c, Group g) throws SQLException { // Get all groups which are a member of this group List group2GroupCaches = group2GroupCacheDAO.findByParent(c, g); - Set groups = new HashSet<>(); + // Initialize HashSet based on List size to avoid Set resizing. See https://stackoverflow.com/a/21822273 + Set groups = new HashSet<>((int) (group2GroupCaches.size() / 0.75 + 1)); for (Group2GroupCache group2GroupCache : group2GroupCaches) { groups.add(group2GroupCache.getChild()); } @@ -399,7 +400,9 @@ public List allMembers(Context c, Group g) throws SQLException { public int countAllMembers(Context context, Group group) throws SQLException { // Get all groups which are a member of this group List group2GroupCaches = group2GroupCacheDAO.findByParent(context, group); - Set groups = new HashSet<>(); + // Initialize HashSet based on List size + current 'group' to avoid Set resizing. + // See https://stackoverflow.com/a/21822273 + Set groups = new HashSet<>((int) ((group2GroupCaches.size() + 1) / 0.75 + 1)); for (Group2GroupCache group2GroupCache : group2GroupCaches) { groups.add(group2GroupCache.getChild()); } diff --git a/dspace-api/src/main/java/org/dspace/eperson/dao/EPersonDAO.java b/dspace-api/src/main/java/org/dspace/eperson/dao/EPersonDAO.java index 573103f86ad3..9e78e758f92b 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/dao/EPersonDAO.java +++ b/dspace-api/src/main/java/org/dspace/eperson/dao/EPersonDAO.java @@ -39,8 +39,8 @@ public List search(Context context, String query, List q public int searchResultCount(Context context, String query, List queryFields) throws SQLException; /** - * Find all EPersons who are a member of one or more of the listed groups in a paginated fashion. Order is - * indeterminate. + * Find all EPersons who are a member of one or more of the listed groups in a paginated fashion. This returns + * EPersons ordered by UUID. * * @param context current Context * @param groups Set of group(s) to check membership in From f8f88060408c30314cdcf38ba5bbac0f367ee3fd Mon Sep 17 00:00:00 2001 From: nwoodward Date: Thu, 2 Nov 2023 13:36:46 -0500 Subject: [PATCH 484/510] removed options to ping search engines when generating sitemaps --- .../dspace/app/sitemap/GenerateSitemaps.java | 109 +----------------- dspace/config/dspace.cfg | 13 --- 2 files changed, 3 insertions(+), 119 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 400b5ecb87cb..5e9a61556083 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,15 +7,8 @@ */ package org.dspace.app.sitemap; -import java.io.BufferedReader; import java.io.File; import java.io.IOException; -import java.io.InputStreamReader; -import java.io.UnsupportedEncodingException; -import java.net.HttpURLConnection; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.URLEncoder; import java.sql.SQLException; import java.util.Date; import java.util.Iterator; @@ -29,7 +22,6 @@ import org.apache.commons.cli.ParseException; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.io.FileUtils; -import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; import org.dspace.content.Collection; @@ -87,11 +79,6 @@ public static void main(String[] args) throws Exception { "do not generate sitemaps.org protocol sitemap"); options.addOption("b", "no_htmlmap", false, "do not generate a basic HTML sitemap"); - options.addOption("a", "ping_all", false, - "ping configured search engines"); - options - .addOption("p", "ping", true, - "ping specified search engine URL"); options .addOption("d", "delete", false, "delete sitemaps dir and its contents"); @@ -116,14 +103,13 @@ public static void main(String[] args) throws Exception { } /* - * Sanity check -- if no sitemap generation or pinging to do, or deletion, print usage + * Sanity check -- if no sitemap generation or deletion, print usage */ if (line.getArgs().length != 0 || line.hasOption('d') || line.hasOption('b') && line.hasOption('s') && !line.hasOption('g') - && !line.hasOption('m') && !line.hasOption('y') - && !line.hasOption('p')) { + && !line.hasOption('m') && !line.hasOption('y')) { System.err - .println("Nothing to do (no sitemap to generate, no search engines to ping)"); + .println("Nothing to do (no sitemap to generate)"); hf.printHelp(usage, options); System.exit(1); } @@ -137,20 +123,6 @@ public static void main(String[] args) throws Exception { deleteSitemaps(); } - if (line.hasOption('a')) { - pingConfiguredSearchEngines(); - } - - if (line.hasOption('p')) { - try { - pingSearchEngine(line.getOptionValue('p')); - } catch (MalformedURLException me) { - System.err - .println("Bad search engine URL (include all except sitemap URL)"); - System.exit(1); - } - } - System.exit(0); } @@ -303,79 +275,4 @@ public static void generateSitemaps(boolean makeHTMLMap, boolean makeSitemapOrg) c.abort(); } - - /** - * Ping all search engines configured in {@code dspace.cfg}. - * - * @throws UnsupportedEncodingException theoretically should never happen - */ - public static void pingConfiguredSearchEngines() - throws UnsupportedEncodingException { - String[] engineURLs = configurationService - .getArrayProperty("sitemap.engineurls"); - - if (ArrayUtils.isEmpty(engineURLs)) { - log.warn("No search engine URLs configured to ping"); - return; - } - - for (int i = 0; i < engineURLs.length; i++) { - try { - pingSearchEngine(engineURLs[i]); - } catch (MalformedURLException me) { - log.warn("Bad search engine URL in configuration: " - + engineURLs[i]); - } - } - } - - /** - * Ping the given search engine. - * - * @param engineURL Search engine URL minus protocol etc, e.g. - * {@code www.google.com} - * @throws MalformedURLException if the passed in URL is malformed - * @throws UnsupportedEncodingException theoretically should never happen - */ - public static void pingSearchEngine(String engineURL) - throws MalformedURLException, UnsupportedEncodingException { - // Set up HTTP proxy - if ((StringUtils.isNotBlank(configurationService.getProperty("http.proxy.host"))) - && (StringUtils.isNotBlank(configurationService.getProperty("http.proxy.port")))) { - System.setProperty("proxySet", "true"); - System.setProperty("proxyHost", configurationService - .getProperty("http.proxy.host")); - System.getProperty("proxyPort", configurationService - .getProperty("http.proxy.port")); - } - - String sitemapURL = configurationService.getProperty("dspace.ui.url") - + "/sitemap"; - - URL url = new URL(engineURL + URLEncoder.encode(sitemapURL, "UTF-8")); - - try { - HttpURLConnection connection = (HttpURLConnection) url - .openConnection(); - - BufferedReader in = new BufferedReader(new InputStreamReader( - connection.getInputStream())); - - String inputLine; - StringBuffer resp = new StringBuffer(); - while ((inputLine = in.readLine()) != null) { - resp.append(inputLine).append("\n"); - } - in.close(); - - if (connection.getResponseCode() == 200) { - log.info("Pinged " + url.toString() + " successfully"); - } else { - log.warn("Error response pinging " + url.toString() + ":\n" - + resp); - } - } catch (IOException e) { - log.warn("Error pinging " + url.toString(), e); - } - } } diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 61027c555079..381d079ca621 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -1403,19 +1403,6 @@ sitemap.dir = ${dspace.dir}/sitemaps # Defaults to "sitemaps", which means they are available at ${dspace.server.url}/sitemaps/ # sitemap.path = sitemaps -# -# Comma-separated list of search engine URLs to 'ping' when a new Sitemap has -# been created. Include everything except the Sitemap URL itself (which will -# be URL-encoded and appended to form the actual URL 'pinged'). -# -sitemap.engineurls = http://www.google.com/webmasters/sitemaps/ping?sitemap= - -# Add this to the above parameter if you have an application ID with Yahoo -# (Replace REPLACE_ME with your application ID) -# http://search.yahooapis.com/SiteExplorerService/V1/updateNotification?appid=REPLACE_ME&url= -# -# No known Sitemap 'ping' URL for MSN/Live search - # Define cron for how frequently the sitemap should refresh. # Defaults to running daily at 1:15am # Cron syntax is defined at https://www.quartz-scheduler.org/api/2.3.0/org/quartz/CronTrigger.html From 9d271b24b9721741a53142a690b86287efb738fe Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Thu, 5 Oct 2023 16:15:43 -0500 Subject: [PATCH 485/510] Add isNotMemberOf for groups, including unit and integration tests --- .../org/dspace/eperson/GroupServiceImpl.java | 54 ++++- .../java/org/dspace/eperson/dao/GroupDAO.java | 32 +++ .../dspace/eperson/dao/impl/GroupDAOImpl.java | 36 ++++ .../dspace/eperson/service/GroupService.java | 62 ++++-- .../java/org/dspace/eperson/GroupTest.java | 103 ++++++++++ .../rest/repository/GroupRestRepository.java | 29 +++ .../app/rest/GroupRestRepositoryIT.java | 186 ++++++++++++++++++ 7 files changed, 478 insertions(+), 24 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/eperson/GroupServiceImpl.java b/dspace-api/src/main/java/org/dspace/eperson/GroupServiceImpl.java index c2f2ea68bdd7..b8d8c75d0f2e 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/GroupServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/GroupServiceImpl.java @@ -460,17 +460,17 @@ public List findAll(Context context, List metadataSortFiel } @Override - public List search(Context context, String groupIdentifier) throws SQLException { - return search(context, groupIdentifier, -1, -1); + public List search(Context context, String query) throws SQLException { + return search(context, query, -1, -1); } @Override - public List search(Context context, String groupIdentifier, int offset, int limit) throws SQLException { + public List search(Context context, String query, int offset, int limit) throws SQLException { List groups = new ArrayList<>(); - UUID uuid = UUIDUtils.fromString(groupIdentifier); + UUID uuid = UUIDUtils.fromString(query); if (uuid == null) { //Search by group name - groups = groupDAO.findByNameLike(context, groupIdentifier, offset, limit); + groups = groupDAO.findByNameLike(context, query, offset, limit); } else { //Search by group id Group group = find(context, uuid); @@ -483,12 +483,12 @@ public List search(Context context, String groupIdentifier, int offset, i } @Override - public int searchResultCount(Context context, String groupIdentifier) throws SQLException { + public int searchResultCount(Context context, String query) throws SQLException { int result = 0; - UUID uuid = UUIDUtils.fromString(groupIdentifier); + UUID uuid = UUIDUtils.fromString(query); if (uuid == null) { //Search by group name - result = groupDAO.countByNameLike(context, groupIdentifier); + result = groupDAO.countByNameLike(context, query); } else { //Search by group id Group group = find(context, uuid); @@ -500,6 +500,44 @@ public int searchResultCount(Context context, String groupIdentifier) throws SQL return result; } + @Override + public List searchNonMembers(Context context, String query, Group excludeParentGroup, + int offset, int limit) throws SQLException { + List groups = new ArrayList<>(); + UUID uuid = UUIDUtils.fromString(query); + if (uuid == null) { + // Search by group name + groups = groupDAO.findByNameLikeAndNotMember(context, query, excludeParentGroup, offset, limit); + } else if (!uuid.equals(excludeParentGroup.getID())) { + // Search by group id + Group group = find(context, uuid); + // Verify it is NOT a member of the given excludeParentGroup before adding + if (group != null && !isMember(excludeParentGroup, group)) { + groups.add(group); + } + } + + return groups; + } + + @Override + public int searchNonMembersCount(Context context, String query, Group excludeParentGroup) throws SQLException { + int result = 0; + UUID uuid = UUIDUtils.fromString(query); + if (uuid == null) { + // Search by group name + result = groupDAO.countByNameLikeAndNotMember(context, query, excludeParentGroup); + } else if (!uuid.equals(excludeParentGroup.getID())) { + // Search by group id + Group group = find(context, uuid); + // Verify it is NOT a member of the given excludeParentGroup before adding + if (group != null && !isMember(excludeParentGroup, group)) { + result = 1; + } + } + return result; + } + @Override public void delete(Context context, Group group) throws SQLException { if (group.isPermanent()) { diff --git a/dspace-api/src/main/java/org/dspace/eperson/dao/GroupDAO.java b/dspace-api/src/main/java/org/dspace/eperson/dao/GroupDAO.java index fd56fe9bd1d0..9742e1611e5a 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/dao/GroupDAO.java +++ b/dspace-api/src/main/java/org/dspace/eperson/dao/GroupDAO.java @@ -135,6 +135,38 @@ List findAll(Context context, List metadataSortFields, int */ int countByNameLike(Context context, String groupName) throws SQLException; + /** + * Search all groups via their name (fuzzy match), limited to those groups which are NOT a member of the given + * parent group. This may be used to search across groups which are valid to add to the given parent group. + *

    + * NOTE: The parent group itself is also excluded from the search. + * + * @param context The DSpace context + * @param groupName Group name to fuzzy match against. + * @param excludeParent Parent Group to exclude results from. Groups under this parent will never be returned. + * @param offset Offset to use for pagination (-1 to disable) + * @param limit The maximum number of results to return (-1 to disable) + * @return Groups matching the query (which are not members of the given parent) + * @throws SQLException if database error + */ + List findByNameLikeAndNotMember(Context context, String groupName, Group excludeParent, + int offset, int limit) throws SQLException; + + /** + * Count number of groups that match a given name (fuzzy match), limited to those groups which are NOT a member of + * the given parent group. This may be used (with findByNameLikeAndNotMember()) to search across groups which are + * valid to add to the given parent group. + *

    + * NOTE: The parent group itself is also excluded from the count. + * + * @param context The DSpace context + * @param groupName Group name to fuzzy match against. + * @param excludeParent Parent Group to exclude results from. Groups under this parent will never be returned. + * @return Groups matching the query (which are not members of the given parent) + * @throws SQLException if database error + */ + int countByNameLikeAndNotMember(Context context, String groupName, Group excludeParent) throws SQLException; + /** * Find a group by its name and the membership of the given EPerson * diff --git a/dspace-api/src/main/java/org/dspace/eperson/dao/impl/GroupDAOImpl.java b/dspace-api/src/main/java/org/dspace/eperson/dao/impl/GroupDAOImpl.java index ad9c7b54fdb5..6aea9ecd8d67 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/dao/impl/GroupDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/dao/impl/GroupDAOImpl.java @@ -164,6 +164,41 @@ public int countByNameLike(final Context context, final String groupName) throws return count(query); } + @Override + public List findByNameLikeAndNotMember(Context context, String groupName, Group excludeParent, + int offset, int limit) throws SQLException { + Query query = createQuery(context, + "FROM Group " + + "WHERE lower(name) LIKE lower(:group_name) " + + "AND id != :parent_id " + + "AND (from Group g where g.id = :parent_id) not in elements (parentGroups)"); + query.setParameter("parent_id", excludeParent.getID()); + query.setParameter("group_name", "%" + StringUtils.trimToEmpty(groupName) + "%"); + + if (0 <= offset) { + query.setFirstResult(offset); + } + if (0 <= limit) { + query.setMaxResults(limit); + } + query.setHint("org.hibernate.cacheable", Boolean.TRUE); + + return list(query); + } + + @Override + public int countByNameLikeAndNotMember(Context context, String groupName, Group excludeParent) throws SQLException { + Query query = createQuery(context, + "SELECT count(*) FROM Group " + + "WHERE lower(name) LIKE lower(:group_name) " + + "AND id != :parent_id " + + "AND (from Group g where g.id = :parent_id) not in elements (parentGroups)"); + query.setParameter("parent_id", excludeParent.getID()); + query.setParameter("group_name", "%" + StringUtils.trimToEmpty(groupName) + "%"); + + return count(query); + } + @Override public void delete(Context context, Group group) throws SQLException { Query query = getHibernateSession(context) @@ -213,6 +248,7 @@ public List findByParent(Context context, Group parent, int pageSize, int return list(query); } + @Override public int countByParent(Context context, Group parent) throws SQLException { Query query = createQuery(context, "SELECT count(g) FROM Group g JOIN g.parentGroups pg " + "WHERE pg.id = :parent_id"); diff --git a/dspace-api/src/main/java/org/dspace/eperson/service/GroupService.java b/dspace-api/src/main/java/org/dspace/eperson/service/GroupService.java index ef3949149f14..0be2f47a61eb 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/service/GroupService.java +++ b/dspace-api/src/main/java/org/dspace/eperson/service/GroupService.java @@ -261,37 +261,67 @@ public List findAll(Context context, List metadataSortFiel public List findAll(Context context, int sortField) throws SQLException; /** - * Find the groups that match the search query across eperson_group_id or name + * Find the Groups that match the query across both Group name and Group ID. This is an unpaginated search, + * which means it will load all matching groups into memory at once. This may provide POOR PERFORMANCE when a large + * number of groups are matched. * - * @param context DSpace context - * @param groupIdentifier The group name or group ID - * @return array of Group objects + * @param context DSpace context + * @param query The search string used to search across group name or group ID + * @return List of matching Group objects * @throws SQLException if error */ - public List search(Context context, String groupIdentifier) throws SQLException; + List search(Context context, String query) throws SQLException; /** - * Find the groups that match the search query across eperson_group_id or name + * Find the Groups that match the query across both Group name and Group ID. This method supports pagination, + * which provides better performance than the above non-paginated search() method. * - * @param context DSpace context - * @param groupIdentifier The group name or group ID - * @param offset Inclusive offset - * @param limit Maximum number of matches returned - * @return array of Group objects + * @param context DSpace context + * @param query The search string used to search across group name or group ID + * @param offset Inclusive offset (the position of the first result to return) + * @param limit Maximum number of matches returned + * @return List of matching Group objects * @throws SQLException if error */ - public List search(Context context, String groupIdentifier, int offset, int limit) throws SQLException; + List search(Context context, String query, int offset, int limit) throws SQLException; /** - * Returns the total number of groups returned by a specific query, without the overhead - * of creating the Group objects to store the results. + * Returns the total number of Groups returned by a specific query. Search is performed based on Group name + * and Group ID. May be used with search() above to support pagination of matching Groups. * * @param context DSpace context - * @param query The search string + * @param query The search string used to search across group name or group ID * @return the number of groups matching the query * @throws SQLException if error */ - public int searchResultCount(Context context, String query) throws SQLException; + int searchResultCount(Context context, String query) throws SQLException; + + /** + * Find the groups that match the search query which are NOT currently members (subgroups) + * of the given parentGroup + * + * @param context DSpace context + * @param query The search string used to search across group name or group ID + * @param excludeParentGroup Parent group to exclude results from + * @param offset Inclusive offset (the position of the first result to return) + * @param limit Maximum number of matches returned + * @return List of matching Group objects + * @throws SQLException if error + */ + List searchNonMembers(Context context, String query, Group excludeParentGroup, + int offset, int limit) throws SQLException; + + /** + * Returns the total number of groups that match the search query which are NOT currently members (subgroups) + * of the given parentGroup. Can be used with searchNonMembers() to support pagination. + * + * @param context DSpace context + * @param query The search string used to search across group name or group ID + * @param excludeParentGroup Parent group to exclude results from + * @return the number of Groups matching the query + * @throws SQLException if error + */ + int searchNonMembersCount(Context context, String query, Group excludeParentGroup) throws SQLException; /** * Return true if group has no direct or indirect members diff --git a/dspace-api/src/test/java/org/dspace/eperson/GroupTest.java b/dspace-api/src/test/java/org/dspace/eperson/GroupTest.java index 0eaacb6194ed..fddcabe4b038 100644 --- a/dspace-api/src/test/java/org/dspace/eperson/GroupTest.java +++ b/dspace-api/src/test/java/org/dspace/eperson/GroupTest.java @@ -680,6 +680,109 @@ public void findAndCountByParent() throws SQLException, AuthorizeException, IOEx } } + @Test + // Tests searchNonMembers() and searchNonMembersCount() + // NOTE: This does not test pagination as that is tested in GroupRestRepositoryIT in server-webapp + public void searchAndCountNonMembers() throws SQLException, AuthorizeException, IOException { + // Create a parent group with 2 child groups + Group parentGroup = createGroup("Some Parent Group"); + Group someStaffGroup = createGroup("Some Other Staff"); + Group someStudentsGroup = createGroup("Some Students"); + groupService.addMember(context, parentGroup, someStaffGroup); + groupService.addMember(context, parentGroup, someStudentsGroup); + groupService.update(context, parentGroup); + + // Create a separate parent which is not a member of the first & add two child groups to it + Group studentsNotInParentGroup = createGroup("Students not in Parent"); + Group otherStudentsNotInParentGroup = createGroup("Other Students"); + Group someOtherStudentsNotInParentGroup = createGroup("Some Other Students"); + groupService.addMember(context, studentsNotInParentGroup, otherStudentsNotInParentGroup); + groupService.addMember(context, studentsNotInParentGroup, someOtherStudentsNotInParentGroup); + groupService.update(context, studentsNotInParentGroup); + + try { + // Assert that all Groups *not* in parent group match an empty search + List notInParent = Arrays.asList(studentsNotInParentGroup, otherStudentsNotInParentGroup, + someOtherStudentsNotInParentGroup); + List nonMembersSearch = groupService.searchNonMembers(context, "", parentGroup, -1, -1); + // NOTE: Because others unit tests create groups, this search will return an undetermined number of results. + // Therefore, we just verify that our expected groups are included and others are NOT included. + assertTrue(nonMembersSearch.containsAll(notInParent)); + // Verify it does NOT contain members of parentGroup + assertFalse(nonMembersSearch.contains(someStaffGroup)); + assertFalse(nonMembersSearch.contains(someStudentsGroup)); + // Verify it also does NOT contain the parentGroup itself + assertFalse(nonMembersSearch.contains(parentGroup)); + // Verify the count for empty search matches the size of the search results + assertEquals(nonMembersSearch.size(), groupService.searchNonMembersCount(context, "", parentGroup)); + + // Assert a search on "Students" matches all those same groups (as they all include that word in their name) + nonMembersSearch = groupService.searchNonMembers(context, "Students", parentGroup, -1, -1); + assertTrue(nonMembersSearch.containsAll(notInParent)); + //Verify an existing member group with "Students" in its name does NOT get returned + assertFalse(nonMembersSearch.contains(someStudentsGroup)); + assertEquals(nonMembersSearch.size(), + groupService.searchNonMembersCount(context, "Students", parentGroup)); + + + // Assert a search on "other" matches just two groups + // (this also tests search is case insensitive) + nonMembersSearch = groupService.searchNonMembers(context, "other", parentGroup, -1, -1); + assertTrue(nonMembersSearch.containsAll( + Arrays.asList(otherStudentsNotInParentGroup, someOtherStudentsNotInParentGroup))); + // Verify an existing member group with "Other" in its name does NOT get returned + assertFalse(nonMembersSearch.contains(someStaffGroup)); + assertEquals(nonMembersSearch.size(), groupService.searchNonMembersCount(context, "other", parentGroup)); + + // Assert a search on "Parent" matches just one group + nonMembersSearch = groupService.searchNonMembers(context, "Parent", parentGroup, -1, -1); + assertTrue(nonMembersSearch.contains(studentsNotInParentGroup)); + // Verify Parent Group itself does NOT get returned + assertFalse(nonMembersSearch.contains(parentGroup)); + assertEquals(nonMembersSearch.size(), groupService.searchNonMembersCount(context, "Parent", parentGroup)); + + // Assert a UUID search matching a non-member group will return just that one group + nonMembersSearch = groupService.searchNonMembers(context, + someOtherStudentsNotInParentGroup.getID().toString(), + parentGroup, -1, -1); + assertEquals(1, nonMembersSearch.size()); + assertTrue(nonMembersSearch.contains(someOtherStudentsNotInParentGroup)); + assertEquals(nonMembersSearch.size(), + groupService.searchNonMembersCount(context, + someOtherStudentsNotInParentGroup.getID().toString(), + parentGroup)); + + // Assert a UUID search matching an EXISTING member will return NOTHING + // (as this group is excluded from the search) + nonMembersSearch = groupService.searchNonMembers(context, someStudentsGroup.getID().toString(), + parentGroup,-1, -1); + assertEquals(0, nonMembersSearch.size()); + assertEquals(nonMembersSearch.size(), + groupService.searchNonMembersCount(context, someStudentsGroup.getID().toString(), + parentGroup)); + + // Assert a UUID search matching Parent Group *itself* will return NOTHING + // (as this group is excluded from the search) + nonMembersSearch = groupService.searchNonMembers(context, parentGroup.getID().toString(), + parentGroup,-1, -1); + assertEquals(0, nonMembersSearch.size()); + assertEquals(nonMembersSearch.size(), + groupService.searchNonMembersCount(context, parentGroup.getID().toString(), + parentGroup)); + } finally { + // Clean up our data + context.turnOffAuthorisationSystem(); + groupService.delete(context, parentGroup); + groupService.delete(context, someStaffGroup); + groupService.delete(context, someStudentsGroup); + groupService.delete(context, studentsNotInParentGroup); + groupService.delete(context, otherStudentsNotInParentGroup); + groupService.delete(context, someOtherStudentsNotInParentGroup); + context.restoreAuthSystemState(); + } + + } + protected Group createGroup(String name) throws SQLException, AuthorizeException { context.turnOffAuthorisationSystem(); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/GroupRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/GroupRestRepository.java index 103abdcae645..9eb92d8e6fe5 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/GroupRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/GroupRestRepository.java @@ -148,6 +148,35 @@ public Page findByMetadata(@Parameter(value = "query", required = tru } } + /** + * Find the Groups matching the query parameter which are NOT a member of the given parent Group. + * The search is delegated to the + * {@link GroupService#search(Context, String, int, int)} method + * + * @param groupUUID the parent group UUID + * @param query is the *required* query string + * @param pageable contains the pagination information + * @return a Page of GroupRest instances matching the user query + */ + @PreAuthorize("hasAuthority('ADMIN') || hasAuthority('MANAGE_ACCESS_GROUP')") + @SearchRestMethod(name = "isNotMemberOf") + public Page findIsNotMemberOf(@Parameter(value = "group", required = true) UUID groupUUID, + @Parameter(value = "query", required = true) String query, + Pageable pageable) { + + try { + Context context = obtainContext(); + Group excludeParentGroup = gs.find(context, groupUUID); + long total = gs.searchNonMembersCount(context, query, excludeParentGroup); + List groups = gs.searchNonMembers(context, query, excludeParentGroup, + Math.toIntExact(pageable.getOffset()), + Math.toIntExact(pageable.getPageSize())); + return converter.toRestPage(groups, pageable, total, utils.obtainProjection()); + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + @Override public Class getDomainClass() { return GroupRest.class; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/GroupRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/GroupRestRepositoryIT.java index 797657794a6e..4300c987589a 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/GroupRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/GroupRestRepositoryIT.java @@ -3242,6 +3242,192 @@ public void subgroupPaginationTest() throws Exception { .andExpect(jsonPath("$.page.totalElements", is(5))); } + // Test of /groups/search/isNotMemberOf pagination + // NOTE: Additional tests of 'isNotMemberOf' search functionality can be found in GroupTest in 'dspace-api' + @Test + public void searchIsNotMemberOfPaginationTest() throws Exception { + context.turnOffAuthorisationSystem(); + + Group group = GroupBuilder.createGroup(context) + .withName("Test Parent group") + .build(); + // Create two subgroups of main group. These SHOULD NOT be included in pagination + GroupBuilder.createGroup(context) + .withParent(group) + .withName("Test group 1") + .build(); + GroupBuilder.createGroup(context) + .withParent(group) + .withName("Test group 2") + .build(); + + // Create five non-member groups. These SHOULD be included in pagination + GroupBuilder.createGroup(context) + .withName("Test group 3") + .build(); + GroupBuilder.createGroup(context) + .withName("Test group 4") + .build(); + GroupBuilder.createGroup(context) + .withName("Test group 5") + .build(); + GroupBuilder.createGroup(context) + .withName("Test group 6") + .build(); + GroupBuilder.createGroup(context) + .withName("Test group 7") + .build(); + + context.restoreAuthSystemState(); + + String authTokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(authTokenAdmin).perform(get("/api/eperson/groups/search/isNotMemberOf") + .param("group", group.getID().toString()) + .param("query", "test group") + .param("page", "0") + .param("size", "2")) + .andExpect(status().isOk()).andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.groups", Matchers.everyItem( + hasJsonPath("$.type", is("group"))) + )) + .andExpect(jsonPath("$._embedded.groups").value(Matchers.hasSize(2))) + .andExpect(jsonPath("$.page.size", is(2))) + .andExpect(jsonPath("$.page.number", is(0))) + .andExpect(jsonPath("$.page.totalPages", is(3))) + .andExpect(jsonPath("$.page.totalElements", is(5))); + + getClient(authTokenAdmin).perform(get("/api/eperson/groups/search/isNotMemberOf") + .param("group", group.getID().toString()) + .param("query", "test group") + .param("page", "1") + .param("size", "2")) + .andExpect(status().isOk()).andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.groups", Matchers.everyItem( + hasJsonPath("$.type", is("group"))) + )) + .andExpect(jsonPath("$._embedded.groups").value(Matchers.hasSize(2))) + .andExpect(jsonPath("$.page.size", is(2))) + .andExpect(jsonPath("$.page.number", is(1))) + .andExpect(jsonPath("$.page.totalPages", is(3))) + .andExpect(jsonPath("$.page.totalElements", is(5))); + + getClient(authTokenAdmin).perform(get("/api/eperson/groups/search/isNotMemberOf") + .param("group", group.getID().toString()) + .param("query", "test group") + .param("page", "2") + .param("size", "2")) + .andExpect(status().isOk()).andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.groups", Matchers.everyItem( + hasJsonPath("$.type", is("group"))) + )) + .andExpect(jsonPath("$._embedded.groups").value(Matchers.hasSize(1))) + .andExpect(jsonPath("$.page.size", is(2))) + .andExpect(jsonPath("$.page.number", is(2))) + .andExpect(jsonPath("$.page.totalPages", is(3))) + .andExpect(jsonPath("$.page.totalElements", is(5))); + } + + @Test + public void searchIsNotMemberOfByUUID() throws Exception { + context.turnOffAuthorisationSystem(); + // Create two groups which have no parent group + Group group1 = GroupBuilder.createGroup(context) + .withName("Test Parent group 1") + .build(); + + Group group2 = GroupBuilder.createGroup(context) + .withName("Test Parent group 2") + .build(); + + // Create a subgroup of parent group 1 + Group group3 = GroupBuilder.createGroup(context) + .withParent(group1) + .withName("Test subgroup") + .build(); + context.restoreAuthSystemState(); + + String authTokenAdmin = getAuthToken(admin.getEmail(), password); + // Search for UUID in a group that the subgroup already belongs to. Should return ZERO results. + getClient(authTokenAdmin).perform(get("/api/eperson/groups/search/isNotMemberOf") + .param("group", group1.getID().toString()) + .param("query", group3.getID().toString())) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$.page.totalElements", is(0))); + + // Search for UUID in a group that the subgroup does NOT belong to. Should return group via exact match + getClient(authTokenAdmin).perform(get("/api/eperson/groups/search/isNotMemberOf") + .param("group", group2.getID().toString()) + .param("query", group3.getID().toString())) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.groups", Matchers.contains( + GroupMatcher.matchGroupEntry(group3.getID(), group3.getName()) + ))) + .andExpect(jsonPath("$.page.totalElements", is(1))); + + // Search for UUID of the group in the "group" param. Should return ZERO results, as "group" param is excluded + getClient(authTokenAdmin).perform(get("/api/eperson/groups/search/isNotMemberOf") + .param("group", group1.getID().toString()) + .param("query", group1.getID().toString())) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$.page.totalElements", is(0))); + } + + @Test + public void searchIsNotMemberOfUnauthorized() throws Exception { + // To avoid creating data, just use the Admin & Anon groups for this test + GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); + Group adminGroup = groupService.findByName(context, Group.ADMIN); + Group anonGroup = groupService.findByName(context, Group.ANONYMOUS); + + getClient().perform(get("/api/eperson/groups/search/isNotMemberOf") + .param("query", anonGroup.getID().toString()) + .param("group", adminGroup.getID().toString())) + .andExpect(status().isUnauthorized()); + } + + @Test + public void searchIsNotMemberOfForbidden() throws Exception { + // To avoid creating data, just use the Admin & Anon groups for this test + GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); + Group adminGroup = groupService.findByName(context, Group.ADMIN); + Group anonGroup = groupService.findByName(context, Group.ANONYMOUS); + + String authToken = getAuthToken(eperson.getEmail(), password); + getClient(authToken).perform(get("/api/eperson/groups/search/isNotMemberOf") + .param("query", anonGroup.getID().toString()) + .param("group", adminGroup.getID().toString())) + .andExpect(status().isForbidden()); + } + + @Test + public void searchIsNotMemberOfMissingOrInvalidParameter() throws Exception { + // To avoid creating data, just use the Admin & Anon groups for this test + GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); + Group adminGroup = groupService.findByName(context, Group.ADMIN); + Group anonGroup = groupService.findByName(context, Group.ANONYMOUS); + + String authToken = getAuthToken(admin.getEmail(), password); + getClient(authToken).perform(get("/api/eperson/groups/search/isNotMemberOf")) + .andExpect(status().isBadRequest()); + + getClient(authToken).perform(get("/api/eperson/groups/search/isNotMemberOf") + .param("query", anonGroup.getID().toString())) + .andExpect(status().isBadRequest()); + + getClient(authToken).perform(get("/api/eperson/groups/search/isNotMemberOf") + .param("group", adminGroup.getID().toString())) + .andExpect(status().isBadRequest()); + + // Test invalid group UUID + getClient(authToken).perform(get("/api/eperson/groups/search/isNotMemberOf") + .param("query", anonGroup.getID().toString()) + .param("group", "not-a-uuid")) + .andExpect(status().isBadRequest()); + } + @Test public void commAdminAndColAdminCannotExploitItemReadGroupTest() throws Exception { From f186dcf4ca17f56478ce27946acdc2c269d8bd50 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 11 Oct 2023 16:29:43 -0500 Subject: [PATCH 486/510] Implement searchNonMembers for EPersonService. Add tests to prove it works (and tests for search()). Requires minor bug fix to AbstractHibernateDSODAO to allow for additional OR/AND clauses to be appended. --- .../dspace/core/AbstractHibernateDSODAO.java | 6 +- .../dspace/eperson/EPersonServiceImpl.java | 94 ++++++- .../org/dspace/eperson/dao/EPersonDAO.java | 57 +++++ .../eperson/dao/impl/EPersonDAOImpl.java | 86 +++++-- .../eperson/service/EPersonService.java | 32 ++- .../java/org/dspace/eperson/EPersonTest.java | 242 ++++++++++++++---- 6 files changed, 434 insertions(+), 83 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/core/AbstractHibernateDSODAO.java b/dspace-api/src/main/java/org/dspace/core/AbstractHibernateDSODAO.java index e6535f094152..e9c6b95b7f05 100644 --- a/dspace-api/src/main/java/org/dspace/core/AbstractHibernateDSODAO.java +++ b/dspace-api/src/main/java/org/dspace/core/AbstractHibernateDSODAO.java @@ -83,13 +83,14 @@ protected void addMetadataValueWhereQuery(StringBuilder query, List search(Context context, String query) throws SQLException { @Override public List search(Context context, String query, int offset, int limit) throws SQLException { - try { - List ePerson = new ArrayList<>(); - EPerson person = find(context, UUID.fromString(query)); + List ePersons = new ArrayList<>(); + UUID uuid = UUIDUtils.fromString(query); + if (uuid == null) { + // Search by firstname & lastname (NOTE: email will also be included automatically) + MetadataField firstNameField = metadataFieldService.findByElement(context, "eperson", "firstname", null); + MetadataField lastNameField = metadataFieldService.findByElement(context, "eperson", "lastname", null); + if (StringUtils.isBlank(query)) { + query = null; + } + ePersons = ePersonDAO.search(context, query, Arrays.asList(firstNameField, lastNameField), + Arrays.asList(firstNameField, lastNameField), offset, limit); + } else { + // Search by UUID + EPerson person = find(context, uuid); if (person != null) { - ePerson.add(person); + ePersons.add(person); } - return ePerson; - } catch (IllegalArgumentException e) { + } + return ePersons; + } + + @Override + public int searchResultCount(Context context, String query) throws SQLException { + int result = 0; + UUID uuid = UUIDUtils.fromString(query); + if (uuid == null) { + // Count results found by firstname & lastname (email is also included automatically) MetadataField firstNameField = metadataFieldService.findByElement(context, "eperson", "firstname", null); MetadataField lastNameField = metadataFieldService.findByElement(context, "eperson", "lastname", null); if (StringUtils.isBlank(query)) { query = null; } - return ePersonDAO.search(context, query, Arrays.asList(firstNameField, lastNameField), - Arrays.asList(firstNameField, lastNameField), offset, limit); + result = ePersonDAO.searchResultCount(context, query, Arrays.asList(firstNameField, lastNameField)); + } else { + // Search by UUID + EPerson person = find(context, uuid); + if (person != null) { + result = 1; + } } + return result; } @Override - public int searchResultCount(Context context, String query) throws SQLException { - MetadataField firstNameField = metadataFieldService.findByElement(context, "eperson", "firstname", null); - MetadataField lastNameField = metadataFieldService.findByElement(context, "eperson", "lastname", null); - if (StringUtils.isBlank(query)) { - query = null; + public List searchNonMembers(Context context, String query, Group excludeGroup, int offset, int limit) + throws SQLException { + List ePersons = new ArrayList<>(); + UUID uuid = UUIDUtils.fromString(query); + if (uuid == null) { + // Search by firstname & lastname (NOTE: email will also be included automatically) + MetadataField firstNameField = metadataFieldService.findByElement(context, "eperson", "firstname", null); + MetadataField lastNameField = metadataFieldService.findByElement(context, "eperson", "lastname", null); + if (StringUtils.isBlank(query)) { + query = null; + } + ePersons = ePersonDAO.searchNotMember(context, query, Arrays.asList(firstNameField, lastNameField), + excludeGroup, Arrays.asList(firstNameField, lastNameField), + offset, limit); + } else { + // Search by UUID + EPerson person = find(context, uuid); + // Verify EPerson is NOT a member of the given excludeGroup before adding + if (person != null && !groupService.isDirectMember(excludeGroup, person)) { + ePersons.add(person); + } + } + + return ePersons; + } + + @Override + public int searchNonMembersCount(Context context, String query, Group excludeGroup) throws SQLException { + int result = 0; + UUID uuid = UUIDUtils.fromString(query); + if (uuid == null) { + // Count results found by firstname & lastname (email is also included automatically) + MetadataField firstNameField = metadataFieldService.findByElement(context, "eperson", "firstname", null); + MetadataField lastNameField = metadataFieldService.findByElement(context, "eperson", "lastname", null); + if (StringUtils.isBlank(query)) { + query = null; + } + result = ePersonDAO.searchNotMemberCount(context, query, Arrays.asList(firstNameField, lastNameField), + excludeGroup); + } else { + // Search by UUID + EPerson person = find(context, uuid); + // Verify EPerson is NOT a member of the given excludeGroup before counting + if (person != null && !groupService.isDirectMember(excludeGroup, person)) { + result = 1; + } } - return ePersonDAO.searchResultCount(context, query, Arrays.asList(firstNameField, lastNameField)); + return result; } @Override diff --git a/dspace-api/src/main/java/org/dspace/eperson/dao/EPersonDAO.java b/dspace-api/src/main/java/org/dspace/eperson/dao/EPersonDAO.java index 9e78e758f92b..f7543570dffb 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/dao/EPersonDAO.java +++ b/dspace-api/src/main/java/org/dspace/eperson/dao/EPersonDAO.java @@ -33,11 +33,68 @@ public interface EPersonDAO extends DSpaceObjectDAO, DSpaceObjectLegacy public EPerson findByNetid(Context context, String netid) throws SQLException; + /** + * Search all EPersons by the given MetadataField objects, sorting by the given sort fields. + *

    + * NOTE: As long as a query is specified, the EPerson's email address is included in the search alongside any given + * metadata fields. + * + * @param context DSpace context + * @param query the text to search EPersons for + * @param queryFields the metadata fields to search within (email is also included automatically) + * @param sortFields the metadata field(s) to sort the results by + * @param offset the position of the first result to return + * @param limit how many results return + * @return List of matching EPerson objects + * @throws SQLException if an error occurs + */ public List search(Context context, String query, List queryFields, List sortFields, int offset, int limit) throws SQLException; + /** + * Count number of EPersons who match a search on the given metadata fields. This returns the count of total + * results for the same query using the 'search()', and therefore can be used to provide pagination. + * + * @param context DSpace context + * @param query the text to search EPersons for + * @param queryFields the metadata fields to search within (email is also included automatically) + * @return total number of EPersons who match the query + * @throws SQLException if an error occurs + */ public int searchResultCount(Context context, String query, List queryFields) throws SQLException; + /** + * Search all EPersons via their firstname, lastname, email (fuzzy match), limited to those EPersons which are NOT + * a member of the given group. This may be used to search across EPersons which are valid to add as members to the + * given group. + * + * @param context The DSpace context + * @param query the text to search EPersons for + * @param queryFields the metadata fields to search within (email is also included automatically) + * @param excludeGroup Group to exclude results from. Members of this group will never be returned. + * @param offset the position of the first result to return + * @param limit how many results return + * @return EPersons matching the query (which are not members of the given group) + * @throws SQLException if database error + */ + List searchNotMember(Context context, String query, List queryFields, Group excludeGroup, + List sortFields, int offset, int limit) throws SQLException; + + /** + * Count number of EPersons that match a given search (fuzzy match) across firstname, lastname and email. This + * search is limited to those EPersons which are NOT a member of the given group. This may be used + * (with searchNotMember()) to perform a paginated search across EPersons which are valid to add to the given group. + * + * @param context The DSpace context + * @param query querystring to fuzzy match against. + * @param queryFields the metadata fields to search within (email is also included automatically) + * @param excludeGroup Group to exclude results from. Members of this group will never be returned. + * @return Groups matching the query (which are not members of the given parent) + * @throws SQLException if database error + */ + int searchNotMemberCount(Context context, String query, List queryFields, Group excludeGroup) + throws SQLException; + /** * Find all EPersons who are a member of one or more of the listed groups in a paginated fashion. This returns * EPersons ordered by UUID. diff --git a/dspace-api/src/main/java/org/dspace/eperson/dao/impl/EPersonDAOImpl.java b/dspace-api/src/main/java/org/dspace/eperson/dao/impl/EPersonDAOImpl.java index bd68a7f399d9..4d64dd967ff8 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/dao/impl/EPersonDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/dao/impl/EPersonDAOImpl.java @@ -70,17 +70,9 @@ public List search(Context context, String query, List q String queryString = "SELECT " + EPerson.class.getSimpleName() .toLowerCase() + " FROM EPerson as " + EPerson.class .getSimpleName().toLowerCase() + " "; - if (query != null) { - query = "%" + query.toLowerCase() + "%"; - } - Query hibernateQuery = getSearchQuery(context, queryString, query, queryFields, sortFields, null); - if (0 <= offset) { - hibernateQuery.setFirstResult(offset); - } - if (0 <= limit) { - hibernateQuery.setMaxResults(limit); - } + Query hibernateQuery = getSearchQuery(context, queryString, query, queryFields, null, + sortFields, null, limit, offset); return list(hibernateQuery); } @@ -92,6 +84,28 @@ public int searchResultCount(Context context, String query, List return count(hibernateQuery); } + @Override + public List searchNotMember(Context context, String query, List queryFields, + Group excludeGroup, List sortFields, + int offset, int limit) throws SQLException { + String queryString = "SELECT " + EPerson.class.getSimpleName() + .toLowerCase() + " FROM EPerson as " + EPerson.class + .getSimpleName().toLowerCase() + " "; + + Query hibernateQuery = getSearchQuery(context, queryString, query, queryFields, excludeGroup, + sortFields, null, limit, offset); + return list(hibernateQuery); + } + + public int searchNotMemberCount(Context context, String query, List queryFields, + Group excludeGroup) throws SQLException { + String queryString = "SELECT count(*) FROM EPerson as " + EPerson.class.getSimpleName().toLowerCase(); + + Query hibernateQuery = getSearchQuery(context, queryString, query, queryFields, excludeGroup, + Collections.EMPTY_LIST, null, -1, -1); + return count(hibernateQuery); + } + @Override public List findAll(Context context, MetadataField metadataSortField, String sortField, int pageSize, int offset) throws SQLException { @@ -105,8 +119,8 @@ public List findAll(Context context, MetadataField metadataSortField, S sortFields = Collections.singletonList(metadataSortField); } - Query query = getSearchQuery(context, queryString, null, ListUtils.EMPTY_LIST, sortFields, sortField, pageSize, - offset); + Query query = getSearchQuery(context, queryString, null, ListUtils.EMPTY_LIST, null, + sortFields, sortField, pageSize, offset); return list(query); } @@ -178,43 +192,81 @@ public List findNotActiveSince(Context context, Date date) throws SQLEx protected Query getSearchQuery(Context context, String queryString, String queryParam, List queryFields, List sortFields, String sortField) throws SQLException { - return getSearchQuery(context, queryString, queryParam, queryFields, sortFields, sortField, -1, -1); + return getSearchQuery(context, queryString, queryParam, queryFields, null, sortFields, sortField, -1, -1); } + /** + * Build a search query across EPersons based on the given metadata fields and sorted based on the given metadata + * field(s) or database column. + *

    + * NOTE: the EPerson's email address is included in the search alongside any given metadata fields. + * + * @param context DSpace Context + * @param queryString String which defines the beginning "SELECT" for the SQL query + * @param queryParam Actual text being searched for + * @param queryFields List of metadata fields to search within + * @param excludeGroup Optional Group which should be excluded from search. Any EPersons who are members + * of this group will not be included in the results. + * @param sortFields Optional List of metadata fields to sort by (should not be specified if sortField is used) + * @param sortField Optional database column to sort on (should not be specified if sortFields is used) + * @param pageSize how many results return + * @param offset the position of the first result to return + * @return built Query object + * @throws SQLException if error occurs + */ protected Query getSearchQuery(Context context, String queryString, String queryParam, - List queryFields, List sortFields, String sortField, - int pageSize, int offset) throws SQLException { - + List queryFields, Group excludeGroup, + List sortFields, String sortField, + int pageSize, int offset) throws SQLException { + // Initialize SQL statement using the passed in "queryString" StringBuilder queryBuilder = new StringBuilder(); queryBuilder.append(queryString); + Set metadataFieldsToJoin = new LinkedHashSet<>(); metadataFieldsToJoin.addAll(queryFields); metadataFieldsToJoin.addAll(sortFields); + // Append necessary join information for MetadataFields we will search within if (!CollectionUtils.isEmpty(metadataFieldsToJoin)) { addMetadataLeftJoin(queryBuilder, EPerson.class.getSimpleName().toLowerCase(), metadataFieldsToJoin); } - if (queryParam != null) { + // Always append a search on EPerson "email" based on query + if (StringUtils.isNotBlank(queryParam)) { addMetadataValueWhereQuery(queryBuilder, queryFields, "like", EPerson.class.getSimpleName().toLowerCase() + ".email like :queryParam"); } + // If excludeGroup is specified, exclude members of that group from results + // This uses a subquery to find the excluded group & verify that it is not in the EPerson list of "groups" + if (excludeGroup != null) { + queryBuilder.append(" AND (FROM Group g where g.id = :group_id) NOT IN elements (") + .append(EPerson.class.getSimpleName().toLowerCase()).append(".groups)"); + } + // Add sort/order by info to query, if specified if (!CollectionUtils.isEmpty(sortFields) || StringUtils.isNotBlank(sortField)) { addMetadataSortQuery(queryBuilder, sortFields, Collections.singletonList(sortField)); } + // Create the final SQL SELECT statement (based on included params above) Query query = createQuery(context, queryBuilder.toString()); + // Set pagesize & offset for pagination if (pageSize > 0) { query.setMaxResults(pageSize); } if (offset > 0) { query.setFirstResult(offset); } + // Set all parameters to the SQL SELECT statement (based on included params above) if (StringUtils.isNotBlank(queryParam)) { query.setParameter("queryParam", "%" + queryParam.toLowerCase() + "%"); } for (MetadataField metadataField : metadataFieldsToJoin) { query.setParameter(metadataField.toString(), metadataField.getID()); } + if (excludeGroup != null) { + query.setParameter("group_id", excludeGroup.getID()); + } + + query.setHint("org.hibernate.cacheable", Boolean.TRUE); return query; } diff --git a/dspace-api/src/main/java/org/dspace/eperson/service/EPersonService.java b/dspace-api/src/main/java/org/dspace/eperson/service/EPersonService.java index 5b10ea539b33..2afec161a672 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/service/EPersonService.java +++ b/dspace-api/src/main/java/org/dspace/eperson/service/EPersonService.java @@ -98,9 +98,9 @@ public List search(Context context, String query) * * @param context The relevant DSpace Context. * @param query The search string - * @param offset Inclusive offset + * @param offset Inclusive offset (the position of the first result to return) * @param limit Maximum number of matches returned - * @return array of EPerson objects + * @return List of matching EPerson objects * @throws SQLException An exception that provides information on a database access error or other errors. */ public List search(Context context, String query, int offset, int limit) @@ -118,6 +118,34 @@ public List search(Context context, String query, int offset, int limit public int searchResultCount(Context context, String query) throws SQLException; + /** + * Find the EPersons that match the search query which are NOT currently members of the given Group. The search + * query is run against firstname, lastname or email. + * + * @param context DSpace context + * @param query The search string + * @param excludeGroup Group to exclude results from. Members of this group will never be returned. + * @param offset Inclusive offset (the position of the first result to return) + * @param limit Maximum number of matches returned + * @return List of matching EPerson objects + * @throws SQLException if error + */ + List searchNonMembers(Context context, String query, Group excludeGroup, + int offset, int limit) throws SQLException; + + /** + * Returns the total number of EPersons that match the search query which are NOT currently members of the given + * Group. The search query is run against firstname, lastname or email. Can be used with searchNonMembers() to + * support pagination + * + * @param context DSpace context + * @param query The search string + * @param excludeGroup Group to exclude results from. Members of this group will never be returned. + * @return List of matching EPerson objects + * @throws SQLException if error + */ + int searchNonMembersCount(Context context, String query, Group excludeGroup) throws SQLException; + /** * Find all the {@code EPerson}s in a specific order by field. * The sortable fields are: diff --git a/dspace-api/src/test/java/org/dspace/eperson/EPersonTest.java b/dspace-api/src/test/java/org/dspace/eperson/EPersonTest.java index 6c162c30d1ad..3780afcf6393 100644 --- a/dspace-api/src/test/java/org/dspace/eperson/EPersonTest.java +++ b/dspace-api/src/test/java/org/dspace/eperson/EPersonTest.java @@ -8,6 +8,7 @@ package org.dspace.eperson; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; @@ -15,6 +16,8 @@ import java.io.IOException; import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.Set; @@ -277,63 +280,184 @@ public void testFindByNetid() */ /** - * Test of search method, of class EPerson. + * Test of search() and searchResultCount() methods of EPersonService + * NOTE: Pagination is not verified here because it is tested in EPersonRestRepositoryIT */ -/* @Test - public void testSearch_Context_String() - throws Exception - { - System.out.println("search"); - Context context = null; - String query = ""; - EPerson[] expResult = null; - EPerson[] result = EPerson.search(context, query); - assertEquals(expResult, result); - // TODO review the generated test code and remove the default call to fail. - fail("The test case is a prototype."); + public void testSearchAndCountByNameEmail() throws SQLException, AuthorizeException, IOException { + List allEPeopleAdded = new ArrayList<>(); + Group testGroup = createGroup("TestingGroup"); + try { + // Create 4 EPersons. Add a few to a test group to verify group membership doesn't matter + EPerson eperson1 = createEPersonAndAddToGroup("eperson1@example.com", "Jane", "Doe", testGroup); + EPerson eperson2 = createEPerson("eperson2@example.com", "John", "Doe"); + EPerson eperson3 = createEPersonAndAddToGroup("eperson3@example.com", "John", "Smith", testGroup); + EPerson eperson4 = createEPerson("eperson4@example.com", "Doe", "Smith"); + allEPeopleAdded.addAll(Arrays.asList(eperson1, eperson2, eperson3, eperson4)); + + List allJohns = Arrays.asList(eperson2, eperson3); + List searchJohnResults = ePersonService.search(context, "John", -1, -1); + assertTrue(searchJohnResults.containsAll(allJohns)); + assertEquals(searchJohnResults.size(), ePersonService.searchResultCount(context, "John")); + + List allDoes = Arrays.asList(eperson1, eperson2, eperson4); + List searchDoeResults = ePersonService.search(context, "Doe", -1, -1); + assertTrue(searchDoeResults.containsAll(allDoes)); + assertEquals(searchDoeResults.size(), ePersonService.searchResultCount(context, "Doe")); + + List allSmiths = Arrays.asList(eperson3, eperson4); + List searchSmithResults = ePersonService.search(context, "Smith", -1, -1); + assertTrue(searchSmithResults.containsAll(allSmiths)); + assertEquals(searchSmithResults.size(), ePersonService.searchResultCount(context, "Smith")); + + // Assert search on example.com returns everyone + List searchEmailResults = ePersonService.search(context, "example.com", -1, -1); + assertTrue(searchEmailResults.containsAll(allEPeopleAdded)); + assertEquals(searchEmailResults.size(), ePersonService.searchResultCount(context, "example.com")); + + // Assert exact email search returns just one + List exactEmailResults = ePersonService.search(context, "eperson1@example.com", -1, -1); + assertTrue(exactEmailResults.contains(eperson1)); + assertEquals(exactEmailResults.size(), ePersonService.searchResultCount(context, "eperson1@example.com")); + + // Assert UUID search returns exact match + List uuidResults = ePersonService.search(context, eperson4.getID().toString(), -1, -1); + assertTrue(uuidResults.contains(eperson4)); + assertEquals(1, uuidResults.size()); + assertEquals(uuidResults.size(), ePersonService.searchResultCount(context, eperson4.getID().toString())); + } finally { + // Remove all Groups & EPersons we added for this test + context.turnOffAuthorisationSystem(); + groupService.delete(context, testGroup); + for (EPerson ePerson : allEPeopleAdded) { + ePersonService.delete(context, ePerson); + } + context.restoreAuthSystemState(); + } } -*/ /** - * Test of search method, of class EPerson. + * Test of searchNonMembers() and searchNonMembersCount() methods of EPersonService + * NOTE: Pagination is not verified here because it is tested in EPersonRestRepositoryIT */ -/* @Test - public void testSearch_4args() - throws Exception - { - System.out.println("search"); - Context context = null; - String query = ""; - int offset = 0; - int limit = 0; - EPerson[] expResult = null; - EPerson[] result = EPerson.search(context, query, offset, limit); - assertEquals(expResult, result); - // TODO review the generated test code and remove the default call to fail. - fail("The test case is a prototype."); - } -*/ + public void testSearchAndCountByNameEmailNonMembers() throws SQLException, AuthorizeException, IOException { + List allEPeopleAdded = new ArrayList<>(); + Group testGroup1 = createGroup("TestingGroup1"); + Group testGroup2 = createGroup("TestingGroup2"); + Group testGroup3 = createGroup("TestingGroup3"); + try { + // Create two EPersons in Group 1 + EPerson eperson1 = createEPersonAndAddToGroup("eperson1@example.com", "Jane", "Doe", testGroup1); + EPerson eperson2 = createEPersonAndAddToGroup("eperson2@example.com", "John", "Smith", testGroup1); - /** - * Test of searchResultCount method, of class EPerson. - */ -/* - @Test - public void testSearchResultCount() - throws Exception - { - System.out.println("searchResultCount"); - Context context = null; - String query = ""; - int expResult = 0; - int result = EPerson.searchResultCount(context, query); - assertEquals(expResult, result); - // TODO review the generated test code and remove the default call to fail. - fail("The test case is a prototype."); + // Create one more EPerson, and add it and a previous EPerson to Group 2 + EPerson eperson3 = createEPersonAndAddToGroup("eperson3@example.com", "John", "Doe", testGroup2); + context.turnOffAuthorisationSystem(); + groupService.addMember(context, testGroup2, eperson2); + groupService.update(context, testGroup2); + ePersonService.update(context, eperson2); + context.restoreAuthSystemState(); + + // Create 2 more EPersons with no group memberships + EPerson eperson4 = createEPerson("eperson4@example.com", "John", "Anthony"); + EPerson eperson5 = createEPerson("eperson5@example.org", "Smith", "Doe"); + allEPeopleAdded.addAll(Arrays.asList(eperson1, eperson2, eperson3, eperson4, eperson5)); + + // FIRST, test search by last name + // Verify all Does match a nonMember search of Group3 (which is an empty group) + List allDoes = Arrays.asList(eperson1, eperson3, eperson5); + List searchDoeResults = ePersonService.searchNonMembers(context, "Doe", testGroup3, -1, -1); + assertTrue(searchDoeResults.containsAll(allDoes)); + assertEquals(searchDoeResults.size(), ePersonService.searchNonMembersCount(context, "Doe", testGroup3)); + + // Verify searching "Doe" with Group 2 *excludes* the one which is already a member + List allNonMemberDoes = Arrays.asList(eperson1, eperson5); + List searchNonMemberDoeResults = ePersonService.searchNonMembers(context, "Doe", testGroup2, + -1, -1); + assertTrue(searchNonMemberDoeResults.containsAll(allNonMemberDoes)); + assertFalse(searchNonMemberDoeResults.contains(eperson3)); + assertEquals(searchNonMemberDoeResults.size(), ePersonService.searchNonMembersCount(context, "Doe", + testGroup2)); + + // Verify searching "Doe" with Group 1 *excludes* the one which is already a member + allNonMemberDoes = Arrays.asList(eperson3, eperson5); + searchNonMemberDoeResults = ePersonService.searchNonMembers(context, "Doe", testGroup1, -1, -1); + assertTrue(searchNonMemberDoeResults.containsAll(allNonMemberDoes)); + assertFalse(searchNonMemberDoeResults.contains(eperson1)); + assertEquals(searchNonMemberDoeResults.size(), ePersonService.searchNonMembersCount(context, "Doe", + testGroup1)); + + // SECOND, test search by first name + // Verify all Johns match a nonMember search of Group3 (which is an empty group) + List allJohns = Arrays.asList(eperson2, eperson3, eperson4); + List searchJohnResults = ePersonService.searchNonMembers(context, "John", + testGroup3, -1, -1); + assertTrue(searchJohnResults.containsAll(allJohns)); + assertEquals(searchJohnResults.size(), ePersonService.searchNonMembersCount(context, "John", + testGroup3)); + + // Verify searching "John" with Group 2 *excludes* the two who are already a member + List allNonMemberJohns = Arrays.asList(eperson4); + List searchNonMemberJohnResults = ePersonService.searchNonMembers(context, "John", + testGroup2, -1, -1); + assertTrue(searchNonMemberJohnResults.containsAll(allNonMemberJohns)); + assertFalse(searchNonMemberJohnResults.contains(eperson2)); + assertFalse(searchNonMemberJohnResults.contains(eperson3)); + assertEquals(searchNonMemberJohnResults.size(), ePersonService.searchNonMembersCount(context, "John", + testGroup2)); + + // FINALLY, test search by email + // Assert search on example.com excluding Group 1 returns just those not in that group + List exampleNonMembers = Arrays.asList(eperson3, eperson4); + List searchEmailResults = ePersonService.searchNonMembers(context, "example.com", + testGroup1, -1, -1); + assertTrue(searchEmailResults.containsAll(exampleNonMembers)); + assertFalse(searchEmailResults.contains(eperson1)); + assertFalse(searchEmailResults.contains(eperson2)); + assertEquals(searchEmailResults.size(), ePersonService.searchNonMembersCount(context, "example.com", + testGroup1)); + + // Assert exact email search returns just one (if not in group) + List exactEmailResults = ePersonService.searchNonMembers(context, "eperson1@example.com", + testGroup2, -1, -1); + assertTrue(exactEmailResults.contains(eperson1)); + assertEquals(exactEmailResults.size(), ePersonService.searchNonMembersCount(context, "eperson1@example.com", + testGroup2)); + // But, change the group to one they are a member of, and they won't be included + exactEmailResults = ePersonService.searchNonMembers(context, "eperson1@example.com", + testGroup1, -1, -1); + assertFalse(exactEmailResults.contains(eperson1)); + assertEquals(exactEmailResults.size(), ePersonService.searchNonMembersCount(context, "eperson1@example.com", + testGroup1)); + + // Assert UUID search returns exact match (if not in group) + List uuidResults = ePersonService.searchNonMembers(context, eperson3.getID().toString(), + testGroup1, -1, -1); + assertTrue(uuidResults.contains(eperson3)); + assertEquals(1, uuidResults.size()); + assertEquals(uuidResults.size(), ePersonService.searchNonMembersCount(context, eperson3.getID().toString(), + testGroup1)); + // But, change the group to one they are a member of, and you'll get no results + uuidResults = ePersonService.searchNonMembers(context, eperson3.getID().toString(), + testGroup2, -1, -1); + assertFalse(uuidResults.contains(eperson3)); + assertEquals(0, uuidResults.size()); + assertEquals(uuidResults.size(), ePersonService.searchNonMembersCount(context, eperson3.getID().toString(), + testGroup2)); + + } finally { + // Remove all Groups & EPersons we added for this test + context.turnOffAuthorisationSystem(); + groupService.delete(context, testGroup1); + groupService.delete(context, testGroup2); + groupService.delete(context, testGroup3); + for (EPerson ePerson : allEPeopleAdded) { + ePersonService.delete(context, ePerson); + } + context.restoreAuthSystemState(); + } } -*/ /** * Test of findAll method, of class EPerson. @@ -1149,6 +1273,17 @@ protected EPerson createEPersonAndAddToGroup(String email, Group group) throws S return ePerson; } + protected EPerson createEPersonAndAddToGroup(String email, String firstname, String lastname, Group group) + throws SQLException, AuthorizeException { + context.turnOffAuthorisationSystem(); + EPerson ePerson = createEPerson(email, firstname, lastname); + groupService.addMember(context, group, ePerson); + groupService.update(context, group); + ePersonService.update(context, ePerson); + context.restoreAuthSystemState(); + return ePerson; + } + protected EPerson createEPerson(String email) throws SQLException, AuthorizeException { context.turnOffAuthorisationSystem(); EPerson ePerson = ePersonService.create(context); @@ -1157,4 +1292,15 @@ protected EPerson createEPerson(String email) throws SQLException, AuthorizeExce context.restoreAuthSystemState(); return ePerson; } + protected EPerson createEPerson(String email, String firstname, String lastname) + throws SQLException, AuthorizeException { + context.turnOffAuthorisationSystem(); + EPerson ePerson = ePersonService.create(context); + ePerson.setEmail(email); + ePerson.setFirstName(context, firstname); + ePerson.setLastName(context, lastname); + ePersonService.update(context, ePerson); + context.restoreAuthSystemState(); + return ePerson; + } } From 5208a355d69c86dc7cb3ea372656c6959664fd9a Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Thu, 12 Oct 2023 12:09:41 -0500 Subject: [PATCH 487/510] Add /epersons/search/isNotMemberOf endpoint to REST API along with integration tests --- .../repository/EPersonRestRepository.java | 34 +++ .../rest/repository/GroupRestRepository.java | 2 +- .../app/rest/EPersonRestRepositoryIT.java | 240 ++++++++++++++++++ 3 files changed, 275 insertions(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EPersonRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EPersonRestRepository.java index 062f7b7a9482..bd42b7420649 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EPersonRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EPersonRestRepository.java @@ -38,9 +38,11 @@ import org.dspace.core.Context; import org.dspace.eperson.EPerson; import org.dspace.eperson.EmptyWorkflowGroupException; +import org.dspace.eperson.Group; import org.dspace.eperson.RegistrationData; import org.dspace.eperson.service.AccountService; import org.dspace.eperson.service.EPersonService; +import org.dspace.eperson.service.GroupService; import org.dspace.eperson.service.RegistrationDataService; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; @@ -79,6 +81,9 @@ public class EPersonRestRepository extends DSpaceObjectRestRepository findByMetadata(@Parameter(value = "query", required = t } } + /** + * Find the EPersons matching the query parameter which are NOT a member of the given Group. + * The search is delegated to the + * {@link EPersonService#searchNonMembers(Context, String, Group, int, int)} method + * + * @param groupUUID the *required* group UUID to exclude results from + * @param query is the *required* query string + * @param pageable contains the pagination information + * @return a Page of EPersonRest instances matching the user query + */ + @PreAuthorize("hasAuthority('ADMIN') || hasAuthority('MANAGE_ACCESS_GROUP')") + @SearchRestMethod(name = "isNotMemberOf") + public Page findIsNotMemberOf(@Parameter(value = "group", required = true) UUID groupUUID, + @Parameter(value = "query", required = true) String query, + Pageable pageable) { + + try { + Context context = obtainContext(); + Group excludeGroup = groupService.find(context, groupUUID); + long total = es.searchNonMembersCount(context, query, excludeGroup); + List epersons = es.searchNonMembers(context, query, excludeGroup, + Math.toIntExact(pageable.getOffset()), + Math.toIntExact(pageable.getPageSize())); + return converter.toRestPage(epersons, pageable, total, utils.obtainProjection()); + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + @Override @PreAuthorize("hasPermission(#uuid, 'EPERSON', #patch)") protected void patch(Context context, HttpServletRequest request, String apiCategory, String model, UUID uuid, diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/GroupRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/GroupRestRepository.java index 9eb92d8e6fe5..a3b525387c62 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/GroupRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/GroupRestRepository.java @@ -151,7 +151,7 @@ public Page findByMetadata(@Parameter(value = "query", required = tru /** * Find the Groups matching the query parameter which are NOT a member of the given parent Group. * The search is delegated to the - * {@link GroupService#search(Context, String, int, int)} method + * {@link GroupService#searchNonMembers(Context, String, Group, int, int)} method * * @param groupUUID the parent group UUID * @param query is the *required* query string diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java index 1f09779ab0e9..6c20dcff4a80 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java @@ -79,6 +79,7 @@ import org.dspace.eperson.PasswordHash; import org.dspace.eperson.service.AccountService; import org.dspace.eperson.service.EPersonService; +import org.dspace.eperson.service.GroupService; import org.dspace.eperson.service.RegistrationDataService; import org.dspace.services.ConfigurationService; import org.hamcrest.Matchers; @@ -96,6 +97,9 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { @Autowired private EPersonService ePersonService; + @Autowired + private GroupService groupService; + @Autowired private ConfigurationService configurationService; @@ -775,6 +779,242 @@ public void findByMetadataMissingParameter() throws Exception { .andExpect(status().isBadRequest()); } + // Test of /epersons/search/isNotMemberOf pagination + // NOTE: Additional tests of 'isNotMemberOf' search functionality can be found in EPersonTest in 'dspace-api' + @Test + public void searchIsNotMemberOfPaginationTest() throws Exception { + context.turnOffAuthorisationSystem(); + + Group group = GroupBuilder.createGroup(context) + .withName("Test Parent group") + .build(); + // Create two EPerson in main group. These SHOULD NOT be included in pagination + EPersonBuilder.createEPerson(context) + .withNameInMetadata("Test", "Person") + .withEmail("test@example.com") + .withGroupMembership(group) + .build(); + EPersonBuilder.createEPerson(context) + .withNameInMetadata("Test2", "Person") + .withEmail("test2@example.com") + .withGroupMembership(group) + .build(); + + // Create five EPersons who are NOT members of that group. These SHOULD be included in pagination + EPersonBuilder.createEPerson(context) + .withNameInMetadata("Test3", "Person") + .withEmail("test3@example.com") + .build(); + EPersonBuilder.createEPerson(context) + .withNameInMetadata("Test4", "Person") + .withEmail("test4@example.com") + .build(); + EPersonBuilder.createEPerson(context) + .withNameInMetadata("Test5", "Person") + .withEmail("test5@example.com") + .build(); + EPersonBuilder.createEPerson(context) + .withNameInMetadata("Test6", "Person") + .withEmail("test6@example.com") + .build(); + EPersonBuilder.createEPerson(context) + .withNameInMetadata("Test7", "Person") + .withEmail("test7@example.com") + .build(); + + context.restoreAuthSystemState(); + + String authTokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(authTokenAdmin).perform(get("/api/eperson/epersons/search/isNotMemberOf") + .param("group", group.getID().toString()) + .param("query", "person") + .param("page", "0") + .param("size", "2")) + .andExpect(status().isOk()).andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.epersons", Matchers.everyItem( + hasJsonPath("$.type", is("eperson"))) + )) + .andExpect(jsonPath("$._embedded.epersons").value(Matchers.hasSize(2))) + .andExpect(jsonPath("$.page.size", is(2))) + .andExpect(jsonPath("$.page.number", is(0))) + .andExpect(jsonPath("$.page.totalPages", is(3))) + .andExpect(jsonPath("$.page.totalElements", is(5))); + + getClient(authTokenAdmin).perform(get("/api/eperson/epersons/search/isNotMemberOf") + .param("group", group.getID().toString()) + .param("query", "person") + .param("page", "1") + .param("size", "2")) + .andExpect(status().isOk()).andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.epersons", Matchers.everyItem( + hasJsonPath("$.type", is("eperson"))) + )) + .andExpect(jsonPath("$._embedded.epersons").value(Matchers.hasSize(2))) + .andExpect(jsonPath("$.page.size", is(2))) + .andExpect(jsonPath("$.page.number", is(1))) + .andExpect(jsonPath("$.page.totalPages", is(3))) + .andExpect(jsonPath("$.page.totalElements", is(5))); + + getClient(authTokenAdmin).perform(get("/api/eperson/epersons/search/isNotMemberOf") + .param("group", group.getID().toString()) + .param("query", "person") + .param("page", "2") + .param("size", "2")) + .andExpect(status().isOk()).andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.epersons", Matchers.everyItem( + hasJsonPath("$.type", is("eperson"))) + )) + .andExpect(jsonPath("$._embedded.epersons").value(Matchers.hasSize(1))) + .andExpect(jsonPath("$.page.size", is(2))) + .andExpect(jsonPath("$.page.number", is(2))) + .andExpect(jsonPath("$.page.totalPages", is(3))) + .andExpect(jsonPath("$.page.totalElements", is(5))); + } + + @Test + public void searchIsNotMemberOfByEmail() throws Exception { + context.turnOffAuthorisationSystem(); + Group group = GroupBuilder.createGroup(context) + .withName("Test group") + .build(); + Group group2 = GroupBuilder.createGroup(context) + .withName("Test another group") + .build(); + EPerson ePerson = EPersonBuilder.createEPerson(context) + .withNameInMetadata("John", "Doe") + .withEmail("Johndoe@example.com") + .withGroupMembership(group) + .build(); + + EPerson ePerson2 = EPersonBuilder.createEPerson(context) + .withNameInMetadata("Jane", "Smith") + .withEmail("janesmith@example.com") + .build(); + + EPerson ePerson3 = EPersonBuilder.createEPerson(context) + .withNameInMetadata("Tom", "Doe") + .withEmail("tomdoe@example.com") + .build(); + + EPerson ePerson4 = EPersonBuilder.createEPerson(context) + .withNameInMetadata("Harry", "Prefix-Doe") + .withEmail("harrydoeprefix@example.com") + .build(); + + context.restoreAuthSystemState(); + + String authToken = getAuthToken(admin.getEmail(), password); + // Search for exact email in a group the person already belongs to. Should return zero results. + getClient(authToken).perform(get("/api/eperson/epersons/search/isNotMemberOf") + .param("query", ePerson.getEmail()) + .param("group", group.getID().toString())) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$.page.totalElements", is(0))); + + // Search for exact email in a group the person does NOT belong to. Should return the person + getClient(authToken).perform(get("/api/eperson/epersons/search/isNotMemberOf") + .param("query", ePerson.getEmail()) + .param("group", group2.getID().toString())) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.epersons", Matchers.contains( + EPersonMatcher.matchEPersonEntry(ePerson) + ))) + .andExpect(jsonPath("$.page.totalElements", is(1))); + + // Search partial email should return all the people created above. + getClient(authToken).perform(get("/api/eperson/epersons/search/isNotMemberOf") + .param("query", "example.com") + .param("group", group2.getID().toString())) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.epersons", Matchers.containsInAnyOrder( + EPersonMatcher.matchEPersonEntry(ePerson), + EPersonMatcher.matchEPersonEntry(ePerson2), + EPersonMatcher.matchEPersonEntry(ePerson3), + EPersonMatcher.matchEPersonEntry(ePerson4) + ))); + } + + @Test + public void searchIsNotMemberOfByUUID() throws Exception { + context.turnOffAuthorisationSystem(); + Group group = GroupBuilder.createGroup(context) + .withName("Test group") + .build(); + Group group2 = GroupBuilder.createGroup(context) + .withName("Test another group") + .build(); + EPerson ePerson = EPersonBuilder.createEPerson(context) + .withNameInMetadata("John", "Doe") + .withEmail("Johndoe@example.com") + .withGroupMembership(group) + .build(); + context.restoreAuthSystemState(); + + String authToken = getAuthToken(admin.getEmail(), password); + // Search for UUID in a group the person already belongs to. Should return zero results. + getClient(authToken).perform(get("/api/eperson/epersons/search/isNotMemberOf") + .param("query", ePerson.getID().toString()) + .param("group", group.getID().toString())) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$.page.totalElements", is(0))); + + // Search for exact email in a group the person does NOT belong to. Should return the person + getClient(authToken).perform(get("/api/eperson/epersons/search/isNotMemberOf") + .param("query", ePerson.getID().toString()) + .param("group", group2.getID().toString())) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.epersons", Matchers.contains( + EPersonMatcher.matchEPersonEntry(ePerson) + ))) + .andExpect(jsonPath("$.page.totalElements", is(1))); + } + + @Test + public void searchIsNotMemberOfUnauthorized() throws Exception { + Group adminGroup = groupService.findByName(context, Group.ADMIN); + getClient().perform(get("/api/eperson/epersons/search/isNotMemberOf") + .param("query", eperson.getID().toString()) + .param("group", adminGroup.getID().toString())) + .andExpect(status().isUnauthorized()); + } + + @Test + public void searchIsNotMemberOfForbidden() throws Exception { + Group adminGroup = groupService.findByName(context, Group.ADMIN); + String authToken = getAuthToken(eperson.getEmail(), password); + getClient(authToken).perform(get("/api/eperson/epersons/search/isNotMemberOf") + .param("query", eperson.getID().toString()) + .param("group", adminGroup.getID().toString())) + .andExpect(status().isForbidden()); + } + + @Test + public void searchIsNotMemberOfMissingOrInvalidParameter() throws Exception { + Group adminGroup = groupService.findByName(context, Group.ADMIN); + String authToken = getAuthToken(admin.getEmail(), password); + getClient(authToken).perform(get("/api/eperson/epersons/search/isNotMemberOf")) + .andExpect(status().isBadRequest()); + + getClient(authToken).perform(get("/api/eperson/epersons/search/isNotMemberOf") + .param("query", eperson.getID().toString())) + .andExpect(status().isBadRequest()); + + getClient(authToken).perform(get("/api/eperson/epersons/search/isNotMemberOf") + .param("group", adminGroup.getID().toString())) + .andExpect(status().isBadRequest()); + + // Test invalid group UUID + getClient(authToken).perform(get("/api/eperson/epersons/search/isNotMemberOf") + .param("query", eperson.getID().toString()) + .param("group", "not-a-uuid")) + .andExpect(status().isBadRequest()); + } + @Test public void deleteOne() throws Exception { context.turnOffAuthorisationSystem(); From e5e0eaa9999a96f499c131e02877d4280f7b5263 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 17 Oct 2023 16:27:51 -0500 Subject: [PATCH 488/510] Bug fix to EPersonDAOImpl. Correctly determine if excluded group needs to be preceded by AND or WHERE --- .../java/org/dspace/eperson/dao/impl/EPersonDAOImpl.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/eperson/dao/impl/EPersonDAOImpl.java b/dspace-api/src/main/java/org/dspace/eperson/dao/impl/EPersonDAOImpl.java index 4d64dd967ff8..87d6c5869b09 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/dao/impl/EPersonDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/dao/impl/EPersonDAOImpl.java @@ -238,7 +238,14 @@ protected Query getSearchQuery(Context context, String queryString, String query // If excludeGroup is specified, exclude members of that group from results // This uses a subquery to find the excluded group & verify that it is not in the EPerson list of "groups" if (excludeGroup != null) { - queryBuilder.append(" AND (FROM Group g where g.id = :group_id) NOT IN elements (") + // If query params exist, then we already have a WHERE clause (see above) and just need to append an AND + if (StringUtils.isNotBlank(queryParam)) { + queryBuilder.append(" AND "); + } else { + // no WHERE clause yet, so this is the start of the WHERE + queryBuilder.append(" WHERE "); + } + queryBuilder.append("(FROM Group g where g.id = :group_id) NOT IN elements (") .append(EPerson.class.getSimpleName().toLowerCase()).append(".groups)"); } // Add sort/order by info to query, if specified From dac4df9c1a0b813d2b7578a17c79dd1e9f798a55 Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Tue, 7 Nov 2023 11:03:39 +0100 Subject: [PATCH 489/510] DURACOM-199 improved test to show bug related to restricted content --- .../app/rest/SitemapRestControllerIT.java | 80 ++++++++++++++++++- 1 file changed, 78 insertions(+), 2 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SitemapRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SitemapRestControllerIT.java index cbcf970547f7..7df95aeeb594 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SitemapRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SitemapRestControllerIT.java @@ -8,6 +8,7 @@ package org.dspace.app.rest; import static org.dspace.builder.ItemBuilder.createItem; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; @@ -16,6 +17,7 @@ import javax.servlet.ServletException; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.authorize.service.ResourcePolicyService; import org.dspace.builder.CollectionBuilder; import org.dspace.builder.CommunityBuilder; import org.dspace.content.Collection; @@ -38,10 +40,22 @@ public class SitemapRestControllerIT extends AbstractControllerIntegrationTest { @Autowired ConfigurationService configurationService; + @Autowired + ResourcePolicyService policyService; + private final static String SITEMAPS_ENDPOINT = "sitemaps"; private Item item1; private Item item2; + private Item itemRestricted; + private Item itemUndiscoverable; + private Item entityPublication; + private Item entityPublicationRestricted; + private Item entityPublicationUndiscoverable; + private Community community; + private Community communityRestricted; + private Collection collection; + private Collection collectionRestricted; @Before @Override @@ -52,8 +66,16 @@ public void setUp() throws Exception { context.turnOffAuthorisationSystem(); - Community community = CommunityBuilder.createCommunity(context).build(); - Collection collection = CollectionBuilder.createCollection(context, community).build(); + community = CommunityBuilder.createCommunity(context).build(); + communityRestricted = CommunityBuilder.createCommunity(context).build(); + policyService.removeAllPolicies(context, communityRestricted); + collection = CollectionBuilder.createCollection(context, community).build(); + collectionRestricted = CollectionBuilder.createCollection(context, community).build(); + Collection publicationCollection = CollectionBuilder.createCollection(context, community) + .withEntityType("Publication") + .withName("Publication Collection").build(); + policyService.removeAllPolicies(context, collectionRestricted); + this.item1 = createItem(context, collection) .withTitle("Test 1") .withIssueDate("2010-10-17") @@ -62,6 +84,30 @@ public void setUp() throws Exception { .withTitle("Test 2") .withIssueDate("2015-8-3") .build(); + this.itemRestricted = createItem(context, collection) + .withTitle("Test 3") + .withIssueDate("2015-8-3") + .build(); + policyService.removeAllPolicies(context, itemRestricted); + this.itemUndiscoverable = createItem(context, collection) + .withTitle("Test 4") + .withIssueDate("2015-8-3") + .makeUnDiscoverable() + .build(); + this.entityPublication = createItem(context, publicationCollection) + .withTitle("Item Publication") + .withIssueDate("2015-8-3") + .build(); + this.entityPublicationRestricted = createItem(context, publicationCollection) + .withTitle("Item Publication Restricted") + .withIssueDate("2015-8-3") + .build(); + policyService.removeAllPolicies(context, entityPublicationRestricted); + this.entityPublicationUndiscoverable = createItem(context, publicationCollection) + .withTitle("Item Publication") + .withIssueDate("2015-8-3") + .makeUnDiscoverable() + .build(); runDSpaceScript("generate-sitemaps"); @@ -127,9 +173,39 @@ public void testSitemap_sitemap0Html() throws Exception { .andReturn(); String response = result.getResponse().getContentAsString(); + // contains a link to communities: [dspace.ui.url]/communities/ + assertTrue(response + .contains(configurationService.getProperty("dspace.ui.url") + "/communities/" + community.getID())); + // contains a link to collections: [dspace.ui.url]/collections/ + assertTrue(response + .contains(configurationService.getProperty("dspace.ui.url") + "/collections/" + collection.getID())); // contains a link to items: [dspace.ui.url]/items/ assertTrue(response.contains(configurationService.getProperty("dspace.ui.url") + "/items/" + item1.getID())); assertTrue(response.contains(configurationService.getProperty("dspace.ui.url") + "/items/" + item2.getID())); + // contains proper link to entities items + assertTrue(response.contains(configurationService.getProperty("dspace.ui.url") + "/entities/publication/" + + entityPublication.getID())); + assertFalse(response + .contains(configurationService.getProperty("dspace.ui.url") + "/items/" + entityPublication.getID())); + // does not contain links to restricted content + assertFalse(response.contains( + configurationService.getProperty("dspace.ui.url") + "/communities/" + communityRestricted.getID())); + assertFalse(response.contains( + configurationService.getProperty("dspace.ui.url") + "/collections/" + collectionRestricted.getID())); + assertFalse(response + .contains(configurationService.getProperty("dspace.ui.url") + "/items/" + itemRestricted.getID())); + assertFalse(response.contains(configurationService.getProperty("dspace.ui.url") + "/entities/publication/" + + entityPublicationRestricted.getID())); + assertFalse(response.contains( + configurationService.getProperty("dspace.ui.url") + "/items/" + entityPublicationRestricted.getID())); + // does not contain links to undiscoverable content + assertFalse(response + .contains(configurationService.getProperty("dspace.ui.url") + "/items/" + itemUndiscoverable.getID())); + assertFalse(response.contains(configurationService.getProperty("dspace.ui.url") + "/entities/publication/" + + entityPublicationUndiscoverable.getID())); + assertFalse(response.contains(configurationService.getProperty("dspace.ui.url") + "/items/" + + entityPublicationUndiscoverable.getID())); + } @Test From 6d9ca388dac3a632530eccbdeda955f7842aae84 Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Tue, 7 Nov 2023 15:51:23 +0100 Subject: [PATCH 490/510] DURACOM-199 fix sitemap generator for restricted content and improve performance --- .../dspace/app/sitemap/GenerateSitemaps.java | 189 ++++++++++-------- .../org/dspace/discovery/SolrServiceImpl.java | 5 +- .../app/rest/SitemapRestControllerIT.java | 29 +++ 3 files changed, 133 insertions(+), 90 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 5e9a61556083..90962d12aa75 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 @@ -11,7 +11,6 @@ import java.io.IOException; import java.sql.SQLException; import java.util.Date; -import java.util.Iterator; import java.util.List; import org.apache.commons.cli.CommandLine; @@ -24,9 +23,6 @@ import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; -import org.dspace.content.Collection; -import org.dspace.content.Community; -import org.dspace.content.Item; import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.CollectionService; import org.dspace.content.service.CommunityService; @@ -35,6 +31,7 @@ import org.dspace.core.LogHelper; import org.dspace.discovery.DiscoverQuery; import org.dspace.discovery.DiscoverResult; +import org.dspace.discovery.IndexableObject; import org.dspace.discovery.SearchService; import org.dspace.discovery.SearchServiceException; import org.dspace.discovery.SearchUtils; @@ -60,6 +57,7 @@ public class GenerateSitemaps { private static final ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); private static final SearchService searchService = SearchUtils.getSearchService(); + private static final int PAGE_SIZE = 100; /** * Default constructor @@ -183,96 +181,113 @@ public static void generateSitemaps(boolean makeHTMLMap, boolean makeSitemapOrg) } Context c = new Context(Context.Mode.READ_ONLY); + int offset = 0; + long commsCount = 0; + long collsCount = 0; + long itemsCount = 0; - List comms = communityService.findAll(c); - - for (Community comm : comms) { - String url = uiURLStem + "communities/" + comm.getID(); + try { + DiscoverQuery discoveryQuery = new DiscoverQuery(); + discoveryQuery.setMaxResults(PAGE_SIZE); + discoveryQuery.setQuery("search.resourcetype:Community"); + do { + discoveryQuery.setStart(offset); + DiscoverResult discoverResult = searchService.search(c, discoveryQuery); + List docs = discoverResult.getIndexableObjects(); + commsCount = discoverResult.getTotalSearchResults(); + + for (IndexableObject doc : docs) { + String url = uiURLStem + "communities/" + doc.getID(); + c.uncacheEntity(doc.getIndexedObject()); + + if (makeHTMLMap) { + html.addURL(url, null); + } + if (makeSitemapOrg) { + sitemapsOrg.addURL(url, null); + } + } + offset += PAGE_SIZE; + } while (offset < commsCount); + + offset = 0; + discoveryQuery = new DiscoverQuery(); + discoveryQuery.setMaxResults(PAGE_SIZE); + discoveryQuery.setQuery("search.resourcetype:Collection"); + do { + discoveryQuery.setStart(offset); + DiscoverResult discoverResult = searchService.search(c, discoveryQuery); + List docs = discoverResult.getIndexableObjects(); + collsCount = discoverResult.getTotalSearchResults(); + + for (IndexableObject doc : docs) { + String url = uiURLStem + "collections/" + doc.getID(); + c.uncacheEntity(doc.getIndexedObject()); + + if (makeHTMLMap) { + html.addURL(url, null); + } + if (makeSitemapOrg) { + sitemapsOrg.addURL(url, null); + } + } + offset += PAGE_SIZE; + } while (offset < collsCount); + + offset = 0; + discoveryQuery = new DiscoverQuery(); + discoveryQuery.setMaxResults(PAGE_SIZE); + discoveryQuery.setQuery("search.resourcetype:Item"); + discoveryQuery.addSearchField("search.entitytype"); + do { + + discoveryQuery.setStart(offset); + DiscoverResult discoverResult = searchService.search(c, discoveryQuery); + List docs = discoverResult.getIndexableObjects(); + itemsCount = discoverResult.getTotalSearchResults(); + + for (IndexableObject doc : docs) { + String url; + List entityTypeFieldValues = discoverResult.getSearchDocument(doc).get(0) + .getSearchFieldValues("search.entitytype"); + if (CollectionUtils.isNotEmpty(entityTypeFieldValues)) { + url = uiURLStem + "entities/" + StringUtils.lowerCase(entityTypeFieldValues.get(0)) + "/" + + doc.getID(); + } else { + url = uiURLStem + "items/" + doc.getID(); + } + Date lastMod = doc.getLastModified(); + c.uncacheEntity(doc.getIndexedObject()); + + if (makeHTMLMap) { + html.addURL(url, null); + } + if (makeSitemapOrg) { + sitemapsOrg.addURL(url, null); + } + } + offset += PAGE_SIZE; + } while (offset < itemsCount); if (makeHTMLMap) { - html.addURL(url, null); - } - if (makeSitemapOrg) { - sitemapsOrg.addURL(url, null); + int files = html.finish(); + log.info(LogHelper.getHeader(c, "write_sitemap", + "type=html,num_files=" + files + ",communities=" + + commsCount + ",collections=" + collsCount + + ",items=" + itemsCount)); } - c.uncacheEntity(comm); - } - - List colls = collectionService.findAll(c); - - for (Collection coll : colls) { - String url = uiURLStem + "collections/" + coll.getID(); - - if (makeHTMLMap) { - html.addURL(url, null); - } if (makeSitemapOrg) { - sitemapsOrg.addURL(url, null); - } - - c.uncacheEntity(coll); - } - - Iterator allItems = itemService.findAll(c); - int itemCount = 0; - - while (allItems.hasNext()) { - Item i = allItems.next(); - - DiscoverQuery entityQuery = new DiscoverQuery(); - entityQuery.setQuery("search.uniqueid:\"Item-" + i.getID() + "\" and entityType:*"); - entityQuery.addSearchField("entityType"); - - try { - DiscoverResult discoverResult = searchService.search(c, entityQuery); - - String url; - if (CollectionUtils.isNotEmpty(discoverResult.getIndexableObjects()) - && CollectionUtils.isNotEmpty(discoverResult.getSearchDocument( - discoverResult.getIndexableObjects().get(0)).get(0).getSearchFieldValues("entityType")) - && StringUtils.isNotBlank(discoverResult.getSearchDocument( - discoverResult.getIndexableObjects().get(0)).get(0).getSearchFieldValues("entityType").get(0)) - ) { - url = uiURLStem + "entities/" + StringUtils.lowerCase(discoverResult.getSearchDocument( - discoverResult.getIndexableObjects().get(0)) - .get(0).getSearchFieldValues("entityType").get(0)) + "/" + i.getID(); - } else { - url = uiURLStem + "items/" + i.getID(); - } - Date lastMod = i.getLastModified(); - - if (makeHTMLMap) { - html.addURL(url, lastMod); - } - if (makeSitemapOrg) { - sitemapsOrg.addURL(url, lastMod); - } - } catch (SearchServiceException e) { - log.error("Failed getting entitytype through solr for item " + i.getID() + ": " + e.getMessage()); + int files = sitemapsOrg.finish(); + log.info(LogHelper.getHeader(c, "write_sitemap", + "type=html,num_files=" + files + ",communities=" + + commsCount + ",collections=" + collsCount + + ",items=" + itemsCount)); } - - c.uncacheEntity(i); - - itemCount++; + } catch (SearchServiceException e) { + throw new RuntimeException(e); + } finally { + c.abort(); } - - if (makeHTMLMap) { - int files = html.finish(); - log.info(LogHelper.getHeader(c, "write_sitemap", - "type=html,num_files=" + files + ",communities=" - + comms.size() + ",collections=" + colls.size() - + ",items=" + itemCount)); - } - - if (makeSitemapOrg) { - int files = sitemapsOrg.finish(); - log.info(LogHelper.getHeader(c, "write_sitemap", - "type=html,num_files=" + files + ",communities=" - + comms.size() + ",collections=" + colls.size() - + ",items=" + itemCount)); - } - - c.abort(); } } 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 0cf2aa50af67..cd3797e3e34e 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceImpl.java @@ -1031,9 +1031,8 @@ protected DiscoverResult retrieveResult(Context context, DiscoverQuery query) // Add information about our search fields for (String field : searchFields) { List valuesAsString = new ArrayList<>(); - for (Object o : doc.getFieldValues(field)) { - valuesAsString.add(String.valueOf(o)); - } + Optional.ofNullable(doc.getFieldValues(field)) + .ifPresent(l -> l.forEach(o -> valuesAsString.add(String.valueOf(o)))); resultDoc.addSearchField(field, valuesAsString.toArray(new String[valuesAsString.size()])); } result.addSearchDocument(indexableObject, resultDoc); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SitemapRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SitemapRestControllerIT.java index 7df95aeeb594..175fb34e6cac 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SitemapRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SitemapRestControllerIT.java @@ -236,8 +236,37 @@ public void testSitemap_sitemap0Xml() throws Exception { .andReturn(); String response = result.getResponse().getContentAsString(); + // contains a link to communities: [dspace.ui.url]/communities/ + assertTrue(response + .contains(configurationService.getProperty("dspace.ui.url") + "/communities/" + community.getID())); + // contains a link to collections: [dspace.ui.url]/collections/ + assertTrue(response + .contains(configurationService.getProperty("dspace.ui.url") + "/collections/" + collection.getID())); // contains a link to items: [dspace.ui.url]/items/ assertTrue(response.contains(configurationService.getProperty("dspace.ui.url") + "/items/" + item1.getID())); assertTrue(response.contains(configurationService.getProperty("dspace.ui.url") + "/items/" + item2.getID())); + // contains proper link to entities items + assertTrue(response.contains(configurationService.getProperty("dspace.ui.url") + "/entities/publication/" + + entityPublication.getID())); + assertFalse(response + .contains(configurationService.getProperty("dspace.ui.url") + "/items/" + entityPublication.getID())); + // does not contain links to restricted content + assertFalse(response.contains( + configurationService.getProperty("dspace.ui.url") + "/communities/" + communityRestricted.getID())); + assertFalse(response.contains( + configurationService.getProperty("dspace.ui.url") + "/collections/" + collectionRestricted.getID())); + assertFalse(response + .contains(configurationService.getProperty("dspace.ui.url") + "/items/" + itemRestricted.getID())); + assertFalse(response.contains(configurationService.getProperty("dspace.ui.url") + "/entities/publication/" + + entityPublicationRestricted.getID())); + assertFalse(response.contains( + configurationService.getProperty("dspace.ui.url") + "/items/" + entityPublicationRestricted.getID())); + // does not contain links to undiscoverable content + assertFalse(response + .contains(configurationService.getProperty("dspace.ui.url") + "/items/" + itemUndiscoverable.getID())); + assertFalse(response.contains(configurationService.getProperty("dspace.ui.url") + "/entities/publication/" + + entityPublicationUndiscoverable.getID())); + assertFalse(response.contains(configurationService.getProperty("dspace.ui.url") + "/items/" + + entityPublicationUndiscoverable.getID())); } } From d800d800d5346ea9a526ba2a880fc93a6892da98 Mon Sep 17 00:00:00 2001 From: Jens Vannerum Date: Wed, 8 Nov 2023 12:02:37 +0100 Subject: [PATCH 491/510] 108055: isClosed method should use xml configuration --- .../app/rest/converter/SubmissionFormConverter.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionFormConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionFormConverter.java index 4febcd559479..daea935f53ff 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionFormConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionFormConverter.java @@ -125,7 +125,7 @@ private SubmissionFormFieldRest getField(DCInput dcinput, String formName) { dcinput.getVocabulary())); selMd.setClosed( isClosed(dcinput.getSchema(), dcinput.getElement(), dcinput.getQualifier(), - dcinput.getPairsType(), dcinput.getVocabulary())); + dcinput.getPairsType(), dcinput.getVocabulary(), dcinput.isClosedVocabulary())); } else { inputRest.setType(inputType); } @@ -145,7 +145,7 @@ private SubmissionFormFieldRest getField(DCInput dcinput, String formName) { selMd.setControlledVocabulary(getAuthorityName(dcinput.getSchema(), dcinput.getElement(), pairs.get(idx + 1), dcinput.getPairsType(), dcinput.getVocabulary())); selMd.setClosed(isClosed(dcinput.getSchema(), dcinput.getElement(), - dcinput.getQualifier(), null, dcinput.getVocabulary())); + dcinput.getQualifier(), null, dcinput.getVocabulary(), dcinput.isClosedVocabulary())); } selectableMetadata.add(selMd); } @@ -212,9 +212,11 @@ private String getAuthorityName(String schema, String element, String qualifier, } private boolean isClosed(String schema, String element, String qualifier, String valuePairsName, - String vocabularyName) { - if (StringUtils.isNotBlank(valuePairsName) || StringUtils.isNotBlank(vocabularyName)) { + String vocabularyName, boolean isClosedVocabulary) { + if (StringUtils.isNotBlank(valuePairsName)) { return true; + } else if (StringUtils.isNotBlank(vocabularyName)) { + return isClosedVocabulary; } return authorityUtils.isClosed(schema, element, qualifier); } From 5a7c7956cd4e8b47f6a6f53adbc646adeddb0f88 Mon Sep 17 00:00:00 2001 From: mohamed eskander Date: Wed, 8 Nov 2023 15:41:22 +0200 Subject: [PATCH 492/510] [DURACOM-200] improvement of checker script --- .../java/org/dspace/content/dao/impl/BitstreamDAOImpl.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/dao/impl/BitstreamDAOImpl.java b/dspace-api/src/main/java/org/dspace/content/dao/impl/BitstreamDAOImpl.java index d6d77fe7f0c7..0e051625aaee 100644 --- a/dspace-api/src/main/java/org/dspace/content/dao/impl/BitstreamDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/dao/impl/BitstreamDAOImpl.java @@ -68,9 +68,9 @@ public List findDuplicateInternalIdentifier(Context context, Bitstrea @Override public List findBitstreamsWithNoRecentChecksum(Context context) throws SQLException { - Query query = createQuery(context, - "select b from Bitstream b where b not in (select c.bitstream from " + - "MostRecentChecksum c)"); + Query query = createQuery(context, "SELECT b FROM MostRecentChecksum c RIGHT JOIN Bitstream b " + + "ON c.bitstream = b WHERE c IS NULL" ); + return query.getResultList(); } From e92b4b7bfdc08efab9aee9b8f07506273ee2bfcb Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Wed, 8 Nov 2023 16:00:26 -0800 Subject: [PATCH 493/510] Updated IIIF Controller IT to text bitstream and bundle exclusions --- .../org/dspace/builder/BitstreamBuilder.java | 51 +++++++++++ .../app/rest/iiif/IIIFControllerIT.java | 87 +++++++++++++++++++ 2 files changed, 138 insertions(+) diff --git a/dspace-api/src/test/java/org/dspace/builder/BitstreamBuilder.java b/dspace-api/src/test/java/org/dspace/builder/BitstreamBuilder.java index 2822d3624e99..08045325b8a5 100644 --- a/dspace-api/src/test/java/org/dspace/builder/BitstreamBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/BitstreamBuilder.java @@ -18,7 +18,11 @@ import org.dspace.content.BitstreamFormat; import org.dspace.content.Bundle; import org.dspace.content.Item; +import org.dspace.content.MetadataField; +import org.dspace.content.MetadataValue; +import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.DSpaceObjectService; +import org.dspace.content.service.MetadataValueService; import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.eperson.Group; @@ -55,6 +59,13 @@ public static BitstreamBuilder createBitstream(Context context, Item item, Input return builder.createInRequestedBundle(context, item, is, bundleName); } + public static BitstreamBuilder createBitstream(Context context, Item item, InputStream is, + String bundleName, boolean iiifEnabled) + throws SQLException, AuthorizeException, IOException { + BitstreamBuilder builder = new BitstreamBuilder(context); + return builder.createInRequestedBundleWithIiifDisabled(context, item, is, bundleName, iiifEnabled); + } + private BitstreamBuilder create(Context context, Item item, InputStream is) throws SQLException, AuthorizeException, IOException { this.context = context; @@ -88,6 +99,41 @@ private BitstreamBuilder createInRequestedBundle(Context context, Item item, Inp return this; } + private BitstreamBuilder createInRequestedBundleWithIiifDisabled(Context context, Item item, InputStream is, + String bundleName, boolean iiifEnabled) + throws SQLException, AuthorizeException, IOException { + this.context = context; + this.item = item; + + Bundle bundle = getBundleByNameAndIiiEnabled(item, bundleName, iiifEnabled); + + bitstream = bitstreamService.create(context, bundle, is); + + return this; + } + + private Bundle getBundleByNameAndIiiEnabled(Item item, String bundleName, boolean iiifEnabled) + throws SQLException, AuthorizeException { + List bundles = itemService.getBundles(item, bundleName); + Bundle targetBundle = null; + + if (bundles.size() < 1) { + // not found, create a new one + targetBundle = bundleService.create(context, item, bundleName); + MetadataValueService metadataValueService = ContentServiceFactory.getInstance().getMetadataValueService(); + MetadataField iiifEnabledField = metadataFieldService. + findByString(context, "dspace.iiif.enabled", '.'); + MetadataValue metadataValue = metadataValueService.create(context, targetBundle, iiifEnabledField); + metadataValue.setValue(String.valueOf(iiifEnabled)); + + } else { + // put bitstreams into first bundle + targetBundle = bundles.iterator().next(); + } + return targetBundle; + } + + private Bundle getBundleByName(Item item, String bundleName) throws SQLException, AuthorizeException { List bundles = itemService.getBundles(item, bundleName); Bundle targetBundle = null; @@ -137,6 +183,11 @@ public BitstreamBuilder withProvenance(String provenance) throws SQLException { } + public BitstreamBuilder withIIIFDisabled() throws SQLException { + bitstreamService.addMetadata(context, bitstream, "dspace", "iiif", "enabled", null, "false"); + return this; + } + public BitstreamBuilder withIIIFLabel(String label) throws SQLException { bitstreamService.addMetadata(context, bitstream, "iiif", "label", null, null, label); return this; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/iiif/IIIFControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/iiif/IIIFControllerIT.java index b4d1f785d4c9..d17db108bab6 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/iiif/IIIFControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/iiif/IIIFControllerIT.java @@ -221,6 +221,93 @@ public void findOneIIIFSearchableWithMixedConfigIT() throws Exception { .andExpect(jsonPath("$.service").exists()); } + @Test + public void findOneWithExcludedBitstreamIT() throws Exception { + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1") + .build(); + Item publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Public item 1") + .withIssueDate("2017-10-17") + .withAuthor("Smith, Donald").withAuthor("Doe, John") + .enableIIIF() + .build(); + + String bitstreamContent = "ThisIsSomeText"; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + BitstreamBuilder + .createBitstream(context, publicItem1, is) + .withName("Bitstream1.jpg") + .withMimeType("image/jpeg") + .withIIIFLabel("Custom Label") + .build(); + } + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + BitstreamBuilder + .createBitstream(context, publicItem1, is) + .withName("Bitstream2.jpg") + .withMimeType("image/jpeg") + .withIIIFDisabled() + .build(); + } + context.restoreAuthSystemState(); + // Expect canvas label, width and height to match bitstream description. + getClient().perform(get("/iiif/" + publicItem1.getID() + "/manifest")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sequences[0].canvases", Matchers.hasSize(1))) + .andExpect(jsonPath("$.@context", is("http://iiif.io/api/presentation/2/context.json"))) + .andExpect(jsonPath("$.sequences[0].canvases[0].@id", + Matchers.containsString("/iiif/" + publicItem1.getID() + "/canvas/c0"))) + .andExpect(jsonPath("$.sequences[0].canvases[0].label", is("Custom Label"))); + } + + @Test + public void findOneWithExcludedBitstreamBundleIT() throws Exception { + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1") + .build(); + Item publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Public item 1") + .withIssueDate("2017-10-17") + .withAuthor("Smith, Donald").withAuthor("Doe, John") + .enableIIIF() + .build(); + + String bitstreamContent = "ThisIsSomeText"; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + BitstreamBuilder + .createBitstream(context, publicItem1, is) + .withName("Bitstream1.jpg") + .withMimeType("image/jpeg") + .withIIIFLabel("Custom Label") + .build(); + } + // Add bitstream + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + BitstreamBuilder + .createBitstream(context, publicItem1, is, "ExcludedBundle", false) + .withName("Bitstream2.jpg") + .withMimeType("image/jpeg") + .build(); + } + context.restoreAuthSystemState(); + // Expect canvas label, width and height to match bitstream description. + getClient().perform(get("/iiif/" + publicItem1.getID() + "/manifest")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sequences[0].canvases", Matchers.hasSize(1))) + .andExpect(jsonPath("$.@context", is("http://iiif.io/api/presentation/2/context.json"))) + .andExpect(jsonPath("$.sequences[0].canvases[0].@id", + Matchers.containsString("/iiif/" + publicItem1.getID() + "/canvas/c0"))) + .andExpect(jsonPath("$.sequences[0].canvases[0].label", is("Custom Label"))); + } + + @Test public void findOneIIIFSearchableWithCustomBundleAndConfigIT() throws Exception { context.turnOffAuthorisationSystem(); From 272744a7ea6d1ec8c594bee596af4f8f58d16a59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paulo=20Gra=C3=A7a?= Date: Fri, 10 Nov 2023 18:06:41 +0000 Subject: [PATCH 494/510] Event consumer for submission config reloading when a collection changes (#8864) * initialization with refactoring * also consider SubmissionConfigReaderException * rename consumer file * init submission service factory * set submissionconfig config settings by default * renaming SubmissionConfigReaderService * support for SubmissionConfigService * fixing style errors and renaming submissionConfigService * fixing style errors and unused imports * set default submission event configs * adding force indexing action to Consumer * stylecheck fixes * undo event.dispatcher.noindex.consumers --- .../org/dspace/app/util/DCInputsReader.java | 7 +- .../authority/ChoiceAuthorityServiceImpl.java | 22 ++--- .../service/ChoiceAuthorityService.java | 3 +- .../consumer/SubmissionConfigConsumer.java | 83 +++++++++++++++++++ .../factory/SubmissionServiceFactory.java | 28 +++++++ .../factory/SubmissionServiceFactoryImpl.java | 28 +++++++ .../service/SubmissionConfigService.java | 47 +++++++++++ .../service/SubmissionConfigServiceImpl.java | 80 ++++++++++++++++++ .../dspace/app/util/SubmissionConfigTest.java | 4 +- .../org/dspace/builder/AbstractBuilder.java | 10 +++ .../converter/AInprogressItemConverter.java | 9 +- .../SubmissionDefinitionConverter.java | 2 +- .../converter/SubmissionSectionConverter.java | 20 +++-- .../SubmissionDefinitionRestRepository.java | 15 ++-- .../SubmissionPanelRestRepository.java | 11 +-- .../WorkflowItemRestRepository.java | 7 +- .../WorkspaceItemRestRepository.java | 9 +- .../app/rest/submit/SubmissionService.java | 11 +-- .../app/rest/SubmissionFormsControllerIT.java | 3 +- dspace/config/dspace.cfg | 6 +- .../spring/api/core-factory-services.xml | 4 +- dspace/config/spring/api/core-services.xml | 3 + 22 files changed, 358 insertions(+), 54 deletions(-) create mode 100644 dspace-api/src/main/java/org/dspace/submit/consumer/SubmissionConfigConsumer.java create mode 100644 dspace-api/src/main/java/org/dspace/submit/factory/SubmissionServiceFactory.java create mode 100644 dspace-api/src/main/java/org/dspace/submit/factory/SubmissionServiceFactoryImpl.java create mode 100644 dspace-api/src/main/java/org/dspace/submit/service/SubmissionConfigService.java create mode 100644 dspace-api/src/main/java/org/dspace/submit/service/SubmissionConfigServiceImpl.java 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 6343ef4fe15b..38692c73a6ce 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 @@ -24,6 +24,7 @@ import org.dspace.content.MetadataSchemaEnum; import org.dspace.core.Utils; import org.dspace.services.factory.DSpaceServicesFactory; +import org.dspace.submit.factory.SubmissionServiceFactory; import org.w3c.dom.Document; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; @@ -158,7 +159,8 @@ public List getInputsByCollectionHandle(String collectionHandle) throws DCInputsReaderException { SubmissionConfig config; try { - config = new SubmissionConfigReader().getSubmissionConfigByCollection(collectionHandle); + config = SubmissionServiceFactory.getInstance().getSubmissionConfigService() + .getSubmissionConfigByCollection(collectionHandle); String formName = config.getSubmissionName(); if (formName == null) { throw new DCInputsReaderException("No form designated as default"); @@ -180,7 +182,8 @@ public List getInputsBySubmissionName(String name) throws DCInputsReaderException { SubmissionConfig config; try { - config = new SubmissionConfigReader().getSubmissionConfigByName(name); + config = SubmissionServiceFactory.getInstance().getSubmissionConfigService() + .getSubmissionConfigByName(name); String formName = config.getSubmissionName(); if (formName == null) { throw new DCInputsReaderException("No form designated as default"); 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 f2bc4f0be0f5..34ba9e8c4550 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 @@ -25,7 +25,6 @@ import org.dspace.app.util.DCInputsReader; import org.dspace.app.util.DCInputsReaderException; import org.dspace.app.util.SubmissionConfig; -import org.dspace.app.util.SubmissionConfigReader; import org.dspace.app.util.SubmissionConfigReaderException; import org.dspace.content.Collection; import org.dspace.content.MetadataValue; @@ -35,6 +34,8 @@ import org.dspace.discovery.configuration.DiscoveryConfigurationService; import org.dspace.discovery.configuration.DiscoverySearchFilterFacet; import org.dspace.services.ConfigurationService; +import org.dspace.submit.factory.SubmissionServiceFactory; +import org.dspace.submit.service.SubmissionConfigService; import org.springframework.beans.factory.annotation.Autowired; /** @@ -88,7 +89,7 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService protected Map vocabularyIndexMap = new HashMap<>(); // the item submission reader - private SubmissionConfigReader itemSubmissionConfigReader; + private SubmissionConfigService submissionConfigService; @Autowired(required = true) protected ConfigurationService configurationService; @@ -135,7 +136,7 @@ public Set getChoiceAuthoritiesNames() { private synchronized void init() { if (!initialized) { try { - itemSubmissionConfigReader = new SubmissionConfigReader(); + submissionConfigService = SubmissionServiceFactory.getInstance().getSubmissionConfigService(); } catch (SubmissionConfigReaderException e) { // the system is in an illegal state as the submission definition is not valid throw new IllegalStateException("Error reading the item submission configuration: " + e.getMessage(), @@ -240,7 +241,7 @@ public String getChoiceAuthorityName(String schema, String element, String quali // there is an authority configured for the metadata valid for some collections, // check if it is the requested collection Map controllerFormDef = controllerFormDefinitions.get(fieldKey); - SubmissionConfig submissionConfig = itemSubmissionConfigReader + SubmissionConfig submissionConfig = submissionConfigService .getSubmissionConfigByCollection(collection.getHandle()); String submissionName = submissionConfig.getSubmissionName(); // check if the requested collection has a submission definition that use an authority for the metadata @@ -262,14 +263,14 @@ protected String makeFieldKey(String schema, String element, String qualifier) { } @Override - public void clearCache() { + public void clearCache() throws SubmissionConfigReaderException { controller.clear(); authorities.clear(); presentation.clear(); closed.clear(); controllerFormDefinitions.clear(); authoritiesFormDefinitions.clear(); - itemSubmissionConfigReader = null; + submissionConfigService.reload(); initialized = false; } @@ -319,7 +320,7 @@ private void loadChoiceAuthorityConfigurations() { */ private void autoRegisterChoiceAuthorityFromInputReader() { try { - List submissionConfigs = itemSubmissionConfigReader + List submissionConfigs = submissionConfigService .getAllSubmissionConfigs(Integer.MAX_VALUE, 0); DCInputsReader dcInputsReader = new DCInputsReader(); @@ -490,10 +491,11 @@ private ChoiceAuthority getAuthorityByFieldKeyCollection(String fieldKey, Collec init(); ChoiceAuthority ma = controller.get(fieldKey); if (ma == null && collection != null) { - SubmissionConfigReader configReader; + SubmissionConfigService configReaderService; try { - configReader = new SubmissionConfigReader(); - SubmissionConfig submissionName = configReader.getSubmissionConfigByCollection(collection.getHandle()); + configReaderService = SubmissionServiceFactory.getInstance().getSubmissionConfigService(); + SubmissionConfig submissionName = configReaderService + .getSubmissionConfigByCollection(collection.getHandle()); ma = controllerFormDefinitions.get(fieldKey).get(submissionName.getSubmissionName()); } catch (SubmissionConfigReaderException e) { // the system is in an illegal state as the submission definition is not valid diff --git a/dspace-api/src/main/java/org/dspace/content/authority/service/ChoiceAuthorityService.java b/dspace-api/src/main/java/org/dspace/content/authority/service/ChoiceAuthorityService.java index a9fd24e947b3..94e5ca57a028 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/service/ChoiceAuthorityService.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/service/ChoiceAuthorityService.java @@ -10,6 +10,7 @@ import java.util.List; import java.util.Set; +import org.dspace.app.util.SubmissionConfigReaderException; import org.dspace.content.Collection; import org.dspace.content.MetadataValue; import org.dspace.content.authority.Choice; @@ -174,7 +175,7 @@ public Choices getBestMatch(String fieldKey, String query, Collection collection /** * This method has been created to have a way of clearing the cache kept inside the service */ - public void clearCache(); + public void clearCache() throws SubmissionConfigReaderException; /** * Should we store the authority key (if any) for such field key and collection? diff --git a/dspace-api/src/main/java/org/dspace/submit/consumer/SubmissionConfigConsumer.java b/dspace-api/src/main/java/org/dspace/submit/consumer/SubmissionConfigConsumer.java new file mode 100644 index 000000000000..a593fe8ae066 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/submit/consumer/SubmissionConfigConsumer.java @@ -0,0 +1,83 @@ +/** + * 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.submit.consumer; + +import org.apache.logging.log4j.Logger; +import org.dspace.content.Collection; +import org.dspace.content.DSpaceObject; +import org.dspace.core.Constants; +import org.dspace.core.Context; +import org.dspace.discovery.IndexingService; +import org.dspace.discovery.indexobject.IndexableCollection; +import org.dspace.event.Consumer; +import org.dspace.event.Event; +import org.dspace.services.factory.DSpaceServicesFactory; +import org.dspace.submit.factory.SubmissionServiceFactory; + +/** + * Consumer implementation to be used for Item Submission Configuration + * + * @author paulo.graca at fccn.pt + */ +public class SubmissionConfigConsumer implements Consumer { + /** + * log4j logger + */ + private static Logger log = org.apache.logging.log4j.LogManager.getLogger(SubmissionConfigConsumer.class); + + IndexingService indexer = DSpaceServicesFactory.getInstance().getServiceManager() + .getServiceByName(IndexingService.class.getName(), + IndexingService.class); + + @Override + public void initialize() throws Exception { + // No-op + } + + @Override + public void consume(Context ctx, Event event) throws Exception { + int st = event.getSubjectType(); + int et = event.getEventType(); + + + if ( st == Constants.COLLECTION ) { + switch (et) { + case Event.MODIFY_METADATA: + // Submission configuration it's based on solr + // for collection's entity type but, at this point + // that info isn't indexed yet, we need to force it + DSpaceObject subject = event.getSubject(ctx); + Collection collectionFromDSOSubject = (Collection) subject; + indexer.indexContent(ctx, new IndexableCollection (collectionFromDSOSubject), true, false, false); + indexer.commit(); + + log.debug("SubmissionConfigConsumer occured: " + event.toString()); + // reload submission configurations + SubmissionServiceFactory.getInstance().getSubmissionConfigService().reload(); + break; + + default: + log.debug("SubmissionConfigConsumer occured: " + event.toString()); + // reload submission configurations + SubmissionServiceFactory.getInstance().getSubmissionConfigService().reload(); + break; + } + } + } + + @Override + public void end(Context ctx) throws Exception { + // No-op + } + + @Override + public void finish(Context ctx) throws Exception { + // No-op + } + +} diff --git a/dspace-api/src/main/java/org/dspace/submit/factory/SubmissionServiceFactory.java b/dspace-api/src/main/java/org/dspace/submit/factory/SubmissionServiceFactory.java new file mode 100644 index 000000000000..6020f13b46cc --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/submit/factory/SubmissionServiceFactory.java @@ -0,0 +1,28 @@ +/** + * 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.submit.factory; + +import org.dspace.app.util.SubmissionConfigReaderException; +import org.dspace.services.factory.DSpaceServicesFactory; +import org.dspace.submit.service.SubmissionConfigService; + +/** + * Abstract factory to get services for submission, use SubmissionServiceFactory.getInstance() to retrieve an + * implementation + * + * @author paulo.graca at fccn.pt + */ +public abstract class SubmissionServiceFactory { + + public abstract SubmissionConfigService getSubmissionConfigService() throws SubmissionConfigReaderException; + + public static SubmissionServiceFactory getInstance() { + return DSpaceServicesFactory.getInstance().getServiceManager() + .getServiceByName("submissionServiceFactory", SubmissionServiceFactory.class); + } +} diff --git a/dspace-api/src/main/java/org/dspace/submit/factory/SubmissionServiceFactoryImpl.java b/dspace-api/src/main/java/org/dspace/submit/factory/SubmissionServiceFactoryImpl.java new file mode 100644 index 000000000000..19f050859769 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/submit/factory/SubmissionServiceFactoryImpl.java @@ -0,0 +1,28 @@ +/** + * 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.submit.factory; + +import org.dspace.app.util.SubmissionConfigReaderException; +import org.dspace.submit.service.SubmissionConfigService; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Factory implementation to get services for submission, use SubmissionServiceFactory.getInstance() to + * retrieve an implementation + * + * @author paulo.graca at fccn.pt + */ +public class SubmissionServiceFactoryImpl extends SubmissionServiceFactory { + @Autowired(required = true) + private SubmissionConfigService submissionConfigService; + + @Override + public SubmissionConfigService getSubmissionConfigService() throws SubmissionConfigReaderException { + return submissionConfigService; + } +} diff --git a/dspace-api/src/main/java/org/dspace/submit/service/SubmissionConfigService.java b/dspace-api/src/main/java/org/dspace/submit/service/SubmissionConfigService.java new file mode 100644 index 000000000000..c4b111a38f7e --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/submit/service/SubmissionConfigService.java @@ -0,0 +1,47 @@ +/** + * 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.submit.service; + +import java.sql.SQLException; +import java.util.List; + +import org.dspace.app.util.SubmissionConfig; +import org.dspace.app.util.SubmissionConfigReaderException; +import org.dspace.app.util.SubmissionStepConfig; +import org.dspace.content.Collection; +import org.dspace.core.Context; + +/** + * Item Submission Configuration Service + * enables interaction with a submission config like + * getting a config by a collection name or handle + * as also retrieving submission configuration steps + * + * @author paulo.graca at fccn.pt + */ +public interface SubmissionConfigService { + + public void reload() throws SubmissionConfigReaderException; + + public String getDefaultSubmissionConfigName(); + + public List getAllSubmissionConfigs(Integer limit, Integer offset); + + public int countSubmissionConfigs(); + + public SubmissionConfig getSubmissionConfigByCollection(String collectionHandle); + + public SubmissionConfig getSubmissionConfigByName(String submitName); + + public SubmissionStepConfig getStepConfig(String stepID) + throws SubmissionConfigReaderException; + + public List getCollectionsBySubmissionConfig(Context context, String submitName) + throws IllegalStateException, SQLException, SubmissionConfigReaderException; + +} diff --git a/dspace-api/src/main/java/org/dspace/submit/service/SubmissionConfigServiceImpl.java b/dspace-api/src/main/java/org/dspace/submit/service/SubmissionConfigServiceImpl.java new file mode 100644 index 000000000000..a72bcc2c3bf9 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/submit/service/SubmissionConfigServiceImpl.java @@ -0,0 +1,80 @@ +/** + * 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.submit.service; + +import java.sql.SQLException; +import java.util.List; + +import org.dspace.app.util.SubmissionConfig; +import org.dspace.app.util.SubmissionConfigReader; +import org.dspace.app.util.SubmissionConfigReaderException; +import org.dspace.app.util.SubmissionStepConfig; +import org.dspace.content.Collection; +import org.dspace.core.Context; +import org.springframework.beans.factory.InitializingBean; + +/** + * An implementation for Submission Config service + * + * @author paulo.graca at fccn.pt + */ +public class SubmissionConfigServiceImpl implements SubmissionConfigService, InitializingBean { + + protected SubmissionConfigReader submissionConfigReader; + + public SubmissionConfigServiceImpl () throws SubmissionConfigReaderException { + submissionConfigReader = new SubmissionConfigReader(); + } + + @Override + public void afterPropertiesSet() throws Exception { + submissionConfigReader.reload(); + } + + @Override + public void reload() throws SubmissionConfigReaderException { + submissionConfigReader.reload(); + } + + @Override + public String getDefaultSubmissionConfigName() { + return submissionConfigReader.getDefaultSubmissionConfigName(); + } + + @Override + public List getAllSubmissionConfigs(Integer limit, Integer offset) { + return submissionConfigReader.getAllSubmissionConfigs(limit, offset); + } + + @Override + public int countSubmissionConfigs() { + return submissionConfigReader.countSubmissionConfigs(); + } + + @Override + public SubmissionConfig getSubmissionConfigByCollection(String collectionHandle) { + return submissionConfigReader.getSubmissionConfigByCollection(collectionHandle); + } + + @Override + public SubmissionConfig getSubmissionConfigByName(String submitName) { + return submissionConfigReader.getSubmissionConfigByName(submitName); + } + + @Override + public SubmissionStepConfig getStepConfig(String stepID) throws SubmissionConfigReaderException { + return submissionConfigReader.getStepConfig(stepID); + } + + @Override + public List getCollectionsBySubmissionConfig(Context context, String submitName) + throws IllegalStateException, SQLException { + return submissionConfigReader.getCollectionsBySubmissionConfig(context, submitName); + } + +} diff --git a/dspace-api/src/test/java/org/dspace/app/util/SubmissionConfigTest.java b/dspace-api/src/test/java/org/dspace/app/util/SubmissionConfigTest.java index be4d6a12dae2..cb1f828b93c4 100644 --- a/dspace-api/src/test/java/org/dspace/app/util/SubmissionConfigTest.java +++ b/dspace-api/src/test/java/org/dspace/app/util/SubmissionConfigTest.java @@ -14,6 +14,7 @@ import java.util.List; import org.dspace.AbstractUnitTest; +import org.dspace.submit.factory.SubmissionServiceFactory; import org.junit.After; import org.junit.AfterClass; import org.junit.Before; @@ -65,7 +66,8 @@ public void testReadAndProcessTypeBindSubmissionConfig() // Get submission configuration SubmissionConfig submissionConfig = - new SubmissionConfigReader().getSubmissionConfigByCollection(typeBindHandle); + SubmissionServiceFactory.getInstance().getSubmissionConfigService() + .getSubmissionConfigByCollection(typeBindHandle); // Submission name should match name defined in item-submission.xml assertEquals(typeBindSubmissionName, submissionConfig.getSubmissionName()); // Step 0 - our process only has one step. It should not be null and have the ID typebindtest diff --git a/dspace-api/src/test/java/org/dspace/builder/AbstractBuilder.java b/dspace-api/src/test/java/org/dspace/builder/AbstractBuilder.java index ca2d11c68d88..f84d17fc7afa 100644 --- a/dspace-api/src/test/java/org/dspace/builder/AbstractBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/AbstractBuilder.java @@ -16,6 +16,7 @@ import org.dspace.alerts.service.SystemWideAlertService; import org.dspace.app.requestitem.factory.RequestItemServiceFactory; import org.dspace.app.requestitem.service.RequestItemService; +import org.dspace.app.util.SubmissionConfigReaderException; import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.factory.AuthorizeServiceFactory; import org.dspace.authorize.service.AuthorizeService; @@ -51,6 +52,8 @@ import org.dspace.scripts.factory.ScriptServiceFactory; import org.dspace.scripts.service.ProcessService; import org.dspace.services.factory.DSpaceServicesFactory; +import org.dspace.submit.factory.SubmissionServiceFactory; +import org.dspace.submit.service.SubmissionConfigService; import org.dspace.supervision.factory.SupervisionOrderServiceFactory; import org.dspace.supervision.service.SupervisionOrderService; import org.dspace.versioning.factory.VersionServiceFactory; @@ -107,6 +110,7 @@ public abstract class AbstractBuilder { static OrcidQueueService orcidQueueService; static OrcidTokenService orcidTokenService; static SystemWideAlertService systemWideAlertService; + static SubmissionConfigService submissionConfigService; static SubscribeService subscribeService; static SupervisionOrderService supervisionOrderService; @@ -171,6 +175,11 @@ public static void init() { orcidTokenService = OrcidServiceFactory.getInstance().getOrcidTokenService(); systemWideAlertService = DSpaceServicesFactory.getInstance().getServiceManager() .getServicesByType(SystemWideAlertService.class).get(0); + try { + submissionConfigService = SubmissionServiceFactory.getInstance().getSubmissionConfigService(); + } catch (SubmissionConfigReaderException e) { + log.error(e.getMessage(), e); + } subscribeService = ContentServiceFactory.getInstance().getSubscribeService(); supervisionOrderService = SupervisionOrderServiceFactory.getInstance().getSupervisionOrderService(); } @@ -207,6 +216,7 @@ public static void destroy() { versioningService = null; orcidTokenService = null; systemWideAlertService = null; + submissionConfigService = null; subscribeService = null; supervisionOrderService = null; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/AInprogressItemConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/AInprogressItemConverter.java index fa1d145011f7..a5431d90004f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/AInprogressItemConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/AInprogressItemConverter.java @@ -19,13 +19,14 @@ import org.dspace.app.rest.submit.DataProcessingStep; import org.dspace.app.rest.submit.RestProcessingStep; import org.dspace.app.rest.submit.SubmissionService; -import org.dspace.app.util.SubmissionConfigReader; import org.dspace.app.util.SubmissionConfigReaderException; import org.dspace.app.util.SubmissionStepConfig; import org.dspace.content.Collection; import org.dspace.content.InProgressSubmission; import org.dspace.content.Item; import org.dspace.eperson.EPerson; +import org.dspace.submit.factory.SubmissionServiceFactory; +import org.dspace.submit.service.SubmissionConfigService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; @@ -53,13 +54,13 @@ public abstract class AInprogressItemConverter collections = panelConverter.getSubmissionConfigReader() + List collections = panelConverter.getSubmissionConfigService() .getCollectionsBySubmissionConfig(context, obj.getSubmissionName()); DSpaceConverter cc = converter.getConverter(Collection.class); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionSectionConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionSectionConverter.java index bf683be8a419..0391cbce7a2d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionSectionConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionSectionConverter.java @@ -7,14 +7,17 @@ */ package org.dspace.app.rest.converter; +import java.sql.SQLException; + import org.apache.logging.log4j.Logger; import org.dspace.app.rest.model.SubmissionSectionRest; import org.dspace.app.rest.model.SubmissionVisibilityRest; import org.dspace.app.rest.model.VisibilityEnum; import org.dspace.app.rest.projection.Projection; -import org.dspace.app.util.SubmissionConfigReader; import org.dspace.app.util.SubmissionConfigReaderException; import org.dspace.app.util.SubmissionStepConfig; +import org.dspace.submit.factory.SubmissionServiceFactory; +import org.dspace.submit.service.SubmissionConfigService; import org.springframework.stereotype.Component; /** @@ -28,7 +31,7 @@ public class SubmissionSectionConverter implements DSpaceConverter getModelClass() { return SubmissionStepConfig.class; } - public SubmissionConfigReader getSubmissionConfigReader() throws SubmissionConfigReaderException { - if (submissionConfigReader == null) { - submissionConfigReader = new SubmissionConfigReader(); + public SubmissionConfigService getSubmissionConfigService() + throws SubmissionConfigReaderException, SQLException, IllegalStateException { + if (submissionConfigService == null) { + submissionConfigService = SubmissionServiceFactory.getInstance().getSubmissionConfigService(); } - return submissionConfigReader; + return submissionConfigService; } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionDefinitionRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionDefinitionRestRepository.java index b358b9684743..d964994928eb 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionDefinitionRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionDefinitionRestRepository.java @@ -15,12 +15,13 @@ import org.dspace.app.rest.SearchRestMethod; import org.dspace.app.rest.model.SubmissionDefinitionRest; import org.dspace.app.util.SubmissionConfig; -import org.dspace.app.util.SubmissionConfigReader; import org.dspace.app.util.SubmissionConfigReaderException; import org.dspace.content.Collection; import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.CollectionService; import org.dspace.core.Context; +import org.dspace.submit.factory.SubmissionServiceFactory; +import org.dspace.submit.service.SubmissionConfigService; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.security.access.prepost.PreAuthorize; @@ -33,18 +34,18 @@ */ @Component(SubmissionDefinitionRest.CATEGORY + "." + SubmissionDefinitionRest.NAME) public class SubmissionDefinitionRestRepository extends DSpaceRestRepository { - private SubmissionConfigReader submissionConfigReader; + private SubmissionConfigService submissionConfigService; private CollectionService collectionService = ContentServiceFactory.getInstance().getCollectionService(); public SubmissionDefinitionRestRepository() throws SubmissionConfigReaderException { - submissionConfigReader = new SubmissionConfigReader(); + submissionConfigService = SubmissionServiceFactory.getInstance().getSubmissionConfigService(); } @PreAuthorize("hasAuthority('AUTHENTICATED')") @Override public SubmissionDefinitionRest findOne(Context context, String submitName) { - SubmissionConfig subConfig = submissionConfigReader.getSubmissionConfigByName(submitName); + SubmissionConfig subConfig = submissionConfigService.getSubmissionConfigByName(submitName); if (subConfig == null) { return null; } @@ -54,8 +55,8 @@ public SubmissionDefinitionRest findOne(Context context, String submitName) { @PreAuthorize("hasAuthority('AUTHENTICATED')") @Override public Page findAll(Context context, Pageable pageable) { - int total = submissionConfigReader.countSubmissionConfigs(); - List subConfs = submissionConfigReader.getAllSubmissionConfigs( + int total = submissionConfigService.countSubmissionConfigs(); + List subConfs = submissionConfigService.getAllSubmissionConfigs( pageable.getPageSize(), Math.toIntExact(pageable.getOffset())); return converter.toRestPage(subConfs, pageable, total, utils.obtainProjection()); } @@ -69,7 +70,7 @@ public SubmissionDefinitionRest findByCollection(@Parameter(value = "uuid", requ return null; } SubmissionDefinitionRest def = converter - .toRest(submissionConfigReader.getSubmissionConfigByCollection(col.getHandle()), + .toRest(submissionConfigService.getSubmissionConfigByCollection(col.getHandle()), utils.obtainProjection()); return def; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionPanelRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionPanelRestRepository.java index 2046a816eb0a..62d104c0a6d3 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionPanelRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionPanelRestRepository.java @@ -13,10 +13,11 @@ import org.dspace.app.rest.model.SubmissionDefinitionRest; import org.dspace.app.rest.model.SubmissionSectionRest; import org.dspace.app.util.SubmissionConfig; -import org.dspace.app.util.SubmissionConfigReader; import org.dspace.app.util.SubmissionConfigReaderException; import org.dspace.app.util.SubmissionStepConfig; import org.dspace.core.Context; +import org.dspace.submit.factory.SubmissionServiceFactory; +import org.dspace.submit.service.SubmissionConfigService; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.security.access.prepost.PreAuthorize; @@ -30,17 +31,17 @@ @Component(SubmissionDefinitionRest.CATEGORY + "." + SubmissionSectionRest.NAME) public class SubmissionPanelRestRepository extends DSpaceRestRepository { - private SubmissionConfigReader submissionConfigReader; + private SubmissionConfigService submissionConfigService; public SubmissionPanelRestRepository() throws SubmissionConfigReaderException { - submissionConfigReader = new SubmissionConfigReader(); + submissionConfigService = SubmissionServiceFactory.getInstance().getSubmissionConfigService(); } @PreAuthorize("hasAuthority('AUTHENTICATED')") @Override public SubmissionSectionRest findOne(Context context, String id) { try { - SubmissionStepConfig step = submissionConfigReader.getStepConfig(id); + SubmissionStepConfig step = submissionConfigService.getStepConfig(id); return converter.toRest(step, utils.obtainProjection()); } catch (SubmissionConfigReaderException e) { //TODO wrap with a specific exception @@ -51,7 +52,7 @@ public SubmissionSectionRest findOne(Context context, String id) { @PreAuthorize("hasAuthority('AUTHENTICATED')") @Override public Page findAll(Context context, Pageable pageable) { - List subConfs = submissionConfigReader.getAllSubmissionConfigs( + List subConfs = submissionConfigService.getAllSubmissionConfigs( pageable.getPageSize(), Math.toIntExact(pageable.getOffset())); long total = 0; List stepConfs = new ArrayList<>(); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkflowItemRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkflowItemRestRepository.java index ee8dc12e7354..de39ff69fb9c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkflowItemRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkflowItemRestRepository.java @@ -27,7 +27,6 @@ import org.dspace.app.rest.model.patch.Operation; import org.dspace.app.rest.model.patch.Patch; import org.dspace.app.rest.submit.SubmissionService; -import org.dspace.app.util.SubmissionConfigReader; import org.dspace.app.util.SubmissionConfigReaderException; import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.service.AuthorizeService; @@ -40,6 +39,8 @@ import org.dspace.eperson.EPerson; import org.dspace.eperson.EPersonServiceImpl; import org.dspace.services.ConfigurationService; +import org.dspace.submit.factory.SubmissionServiceFactory; +import org.dspace.submit.service.SubmissionConfigService; import org.dspace.workflow.WorkflowException; import org.dspace.workflow.WorkflowService; import org.dspace.xmlworkflow.WorkflowConfigurationException; @@ -109,10 +110,10 @@ public class WorkflowItemRestRepository extends DSpaceRestRepository upload(Context context, HttpServletRequest re } SubmissionConfig submissionConfig = - submissionConfigReader.getSubmissionConfigByCollection(collection.getHandle()); + submissionConfigService.getSubmissionConfigByCollection(collection.getHandle()); List result = null; List records = new ArrayList<>(); try { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/SubmissionService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/SubmissionService.java index 76de36dde65b..e3dbd566a8c9 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/SubmissionService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/SubmissionService.java @@ -37,7 +37,6 @@ import org.dspace.app.rest.repository.WorkspaceItemRestRepository; import org.dspace.app.rest.utils.ContextUtil; import org.dspace.app.util.SubmissionConfig; -import org.dspace.app.util.SubmissionConfigReader; import org.dspace.app.util.SubmissionConfigReaderException; import org.dspace.app.util.SubmissionStepConfig; import org.dspace.authorize.AuthorizeException; @@ -58,6 +57,8 @@ import org.dspace.services.ConfigurationService; import org.dspace.services.RequestService; import org.dspace.services.model.Request; +import org.dspace.submit.factory.SubmissionServiceFactory; +import org.dspace.submit.service.SubmissionConfigService; import org.dspace.workflow.WorkflowException; import org.dspace.workflow.WorkflowItemService; import org.dspace.workflow.WorkflowService; @@ -100,10 +101,10 @@ public class SubmissionService { private ConverterService converter; @Autowired private org.dspace.app.rest.utils.Utils utils; - private SubmissionConfigReader submissionConfigReader; + private SubmissionConfigService submissionConfigService; public SubmissionService() throws SubmissionConfigReaderException { - submissionConfigReader = new SubmissionConfigReader(); + submissionConfigService = SubmissionServiceFactory.getInstance().getSubmissionConfigService(); } /** @@ -329,7 +330,7 @@ public List uploadFileToInprogressSubmission(Context context, HttpSer AInprogressSubmissionRest wsi, InProgressSubmission source, MultipartFile file) { List errors = new ArrayList(); SubmissionConfig submissionConfig = - submissionConfigReader.getSubmissionConfigByName(wsi.getSubmissionDefinition().getName()); + submissionConfigService.getSubmissionConfigByName(wsi.getSubmissionDefinition().getName()); List stepInstancesAndConfigs = new ArrayList(); // we need to run the preProcess of all the appropriate steps and move on to the // upload and postProcess step @@ -396,7 +397,7 @@ public List uploadFileToInprogressSubmission(Context context, HttpSer public void evaluatePatchToInprogressSubmission(Context context, HttpServletRequest request, InProgressSubmission source, AInprogressSubmissionRest wsi, String section, Operation op) { boolean sectionExist = false; - SubmissionConfig submissionConfig = submissionConfigReader + SubmissionConfig submissionConfig = submissionConfigService .getSubmissionConfigByName(wsi.getSubmissionDefinition().getName()); List stepInstancesAndConfigs = new ArrayList(); // we need to run the preProcess of all the appropriate steps and move on to the diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionFormsControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionFormsControllerIT.java index cf1e0c7c76a9..006c22dae113 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionFormsControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionFormsControllerIT.java @@ -24,6 +24,7 @@ import org.dspace.app.rest.repository.SubmissionFormRestRepository; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.app.util.DCInputsReaderException; +import org.dspace.app.util.SubmissionConfigReaderException; import org.dspace.builder.EPersonBuilder; import org.dspace.content.authority.DCInputAuthority; import org.dspace.content.authority.service.ChoiceAuthorityService; @@ -666,7 +667,7 @@ public void noExternalSourcesTest() throws Exception { ; } - private void resetLocalesConfiguration() throws DCInputsReaderException { + private void resetLocalesConfiguration() throws DCInputsReaderException, SubmissionConfigReaderException { configurationService.setProperty("default.locale","en"); configurationService.setProperty("webui.supported.locales",null); submissionFormRestRepository.reload(); diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 381d079ca621..d38ffd64339a 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -780,7 +780,7 @@ event.dispatcher.default.class = org.dspace.event.BasicDispatcher # Add rdf here, if you are using dspace-rdf to export your repository content as RDF. # Add iiif here, if you are using dspace-iiif. # Add orcidqueue here, if the integration with ORCID is configured and wish to enable the synchronization queue functionality -event.dispatcher.default.consumers = versioning, discovery, eperson +event.dispatcher.default.consumers = versioning, discovery, eperson, submissionconfig # The noindex dispatcher will not create search or browse indexes (useful for batch item imports) event.dispatcher.noindex.class = org.dspace.event.BasicDispatcher @@ -822,6 +822,10 @@ event.consumer.iiif.filters = Item+Modify:Item+Modify_Metadata:Item+Delete:Item+ event.consumer.orcidqueue.class = org.dspace.orcid.consumer.OrcidQueueConsumer event.consumer.orcidqueue.filters = Item+Install|Modify|Modify_Metadata|Delete|Remove +# item submission config reload consumer +event.consumer.submissionconfig.class = org.dspace.submit.consumer.SubmissionConfigConsumer +event.consumer.submissionconfig.filters = Collection+Modify_Metadata + # ...set to true to enable testConsumer messages to standard output #testConsumer.verbose = true diff --git a/dspace/config/spring/api/core-factory-services.xml b/dspace/config/spring/api/core-factory-services.xml index cef906adc842..acfa0efe6d6c 100644 --- a/dspace/config/spring/api/core-factory-services.xml +++ b/dspace/config/spring/api/core-factory-services.xml @@ -52,9 +52,11 @@ - + + + diff --git a/dspace/config/spring/api/core-services.xml b/dspace/config/spring/api/core-services.xml index 3ede01647c3d..a13b1bb867ec 100644 --- a/dspace/config/spring/api/core-services.xml +++ b/dspace/config/spring/api/core-services.xml @@ -152,5 +152,8 @@ + + + From 9f3f5175a3093181dbce4b9533ba5570ea051010 Mon Sep 17 00:00:00 2001 From: Philipp Rumpf Date: Tue, 14 Nov 2023 09:38:35 +0000 Subject: [PATCH 495/510] CrossRefImport: ignore empty responses rather than generating empty phantom ImportRecords Fixes https://github.com/DSpace/DSpace/issues/9202 . --- .../CrossRefImportMetadataSourceServiceImpl.java | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefImportMetadataSourceServiceImpl.java index 7dde330b27ec..71b088ff162b 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefImportMetadataSourceServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefImportMetadataSourceServiceImpl.java @@ -162,7 +162,9 @@ public List call() throws Exception { Iterator nodes = jsonNode.at("/message/items").iterator(); while (nodes.hasNext()) { JsonNode node = nodes.next(); - results.add(transformSourceRecords(node.toString())); + if (!node.isMissingNode()) { + results.add(transformSourceRecords(node.toString())); + } } return results; } @@ -196,7 +198,9 @@ public List call() throws Exception { String responseString = liveImportClient.executeHttpGetRequest(1000, uriBuilder.toString(), params); JsonNode jsonNode = convertStringJsonToJsonNode(responseString); JsonNode messageNode = jsonNode.at("/message"); - results.add(transformSourceRecords(messageNode.toString())); + if (!messageNode.isMissingNode()) { + results.add(transformSourceRecords(messageNode.toString())); + } return results; } } @@ -250,7 +254,9 @@ public List call() throws Exception { Iterator nodes = jsonNode.at("/message/items").iterator(); while (nodes.hasNext()) { JsonNode node = nodes.next(); - results.add(transformSourceRecords(node.toString())); + if (!node.isMissingNode()) { + results.add(transformSourceRecords(node.toString())); + } } return results; } @@ -333,4 +339,4 @@ public void setUrl(String url) { this.url = url; } -} \ No newline at end of file +} From 50b47b707ccc4f0d7ed3887f08f0a88a39686f29 Mon Sep 17 00:00:00 2001 From: Sascha Szott Date: Tue, 14 Nov 2023 20:36:52 +0100 Subject: [PATCH 496/510] subscription email: do not send email if nothing has changed (#8981) * improved subscriptions email template * do not send emails without content * fixed coding style violations * removed unnecessary isEmpty check as suggested by reviewer * moved null check on indexableObjects in generateBodyMail * fixed unhandled IOException * fixed typo in bodyCommunities * do not use != to compare strings * fixed improper handling of empty list --- .../subscriptions/ContentGenerator.java | 34 +++++++++++-------- dspace/config/emails/subscriptions_content | 16 +++++---- 2 files changed, 29 insertions(+), 21 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/subscriptions/ContentGenerator.java b/dspace-api/src/main/java/org/dspace/subscriptions/ContentGenerator.java index a913f2504a50..c3035614343b 100644 --- a/dspace-api/src/main/java/org/dspace/subscriptions/ContentGenerator.java +++ b/dspace-api/src/main/java/org/dspace/subscriptions/ContentGenerator.java @@ -56,8 +56,16 @@ public void notifyForSubscriptions(Context context, EPerson ePerson, Locale supportedLocale = I18nUtil.getEPersonLocale(ePerson); Email email = Email.getEmail(I18nUtil.getEmailFilename(supportedLocale, "subscriptions_content")); email.addRecipient(ePerson.getEmail()); - email.addArgument(generateBodyMail(context, indexableComm)); - email.addArgument(generateBodyMail(context, indexableColl)); + + String bodyCommunities = generateBodyMail(context, indexableComm); + String bodyCollections = generateBodyMail(context, indexableColl); + if (bodyCommunities.equals(EMPTY) && bodyCollections.equals(EMPTY)) { + log.debug("subscription(s) of eperson {} do(es) not match any new items: nothing to send" + + " - exit silently", ePerson::getID); + return; + } + email.addArgument(bodyCommunities); + email.addArgument(bodyCollections); email.send(); } } catch (Exception e) { @@ -67,21 +75,19 @@ public void notifyForSubscriptions(Context context, EPerson ePerson, } private String generateBodyMail(Context context, List indexableObjects) { + if (indexableObjects == null || indexableObjects.isEmpty()) { + return EMPTY; + } try { ByteArrayOutputStream out = new ByteArrayOutputStream(); out.write("\n".getBytes(UTF_8)); - if (indexableObjects.size() > 0) { - for (IndexableObject indexableObject : indexableObjects) { - out.write("\n".getBytes(UTF_8)); - Item item = (Item) indexableObject.getIndexedObject(); - String entityType = itemService.getEntityTypeLabel(item); - Optional.ofNullable(entityType2Disseminator.get(entityType)) - .orElseGet(() -> entityType2Disseminator.get("Item")) - .disseminate(context, item, out); - } - return out.toString(); - } else { - out.write("No items".getBytes(UTF_8)); + for (IndexableObject indexableObject : indexableObjects) { + out.write("\n".getBytes(UTF_8)); + Item item = (Item) indexableObject.getIndexedObject(); + String entityType = itemService.getEntityTypeLabel(item); + Optional.ofNullable(entityType2Disseminator.get(entityType)) + .orElseGet(() -> entityType2Disseminator.get("Item")) + .disseminate(context, item, out); } return out.toString(); } catch (Exception e) { diff --git a/dspace/config/emails/subscriptions_content b/dspace/config/emails/subscriptions_content index a330c5953716..9b8c91e559df 100644 --- a/dspace/config/emails/subscriptions_content +++ b/dspace/config/emails/subscriptions_content @@ -2,15 +2,17 @@ ## ## Parameters: {0} Collections updates ## {1} Communities updates -#set($subject = "${config.get('dspace.name')} Subscription") - +#set($subject = "${config.get('dspace.name')} Subscriptions") This email is sent from ${config.get('dspace.name')} based on the chosen subscription preferences. -Communities ------------ +#if( not( "$params[0]" == "" )) +Community Subscriptions: +------------------------ List of changed items : ${params[0]} -Collections ------------ +#end +#if( not( "$params[1]" == "" )) +Collection Subscriptions: +------------------------- List of changed items : ${params[1]} - +#end From a68755ee4f65061cef7f632115fb36bf15888603 Mon Sep 17 00:00:00 2001 From: Philipp Rumpf Date: Thu, 16 Nov 2023 14:15:32 +0000 Subject: [PATCH 497/510] CrossRefImportMetadataSourceServiceIT: Test empty responses don't result in ... results. --- .../CrossRefImportMetadataSourceServiceIT.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CrossRefImportMetadataSourceServiceIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CrossRefImportMetadataSourceServiceIT.java index 72524709ec65..31c22692f008 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CrossRefImportMetadataSourceServiceIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CrossRefImportMetadataSourceServiceIT.java @@ -48,6 +48,24 @@ public class CrossRefImportMetadataSourceServiceIT extends AbstractLiveImportInt @Autowired private CrossRefImportMetadataSourceServiceImpl crossRefServiceImpl; + @Test + public void crossRefImportMetadataGetNoRecordsTest() throws Exception { + context.turnOffAuthorisationSystem(); + CloseableHttpClient originalHttpClient = liveImportClientImpl.getHttpClient(); + CloseableHttpClient httpClient = Mockito.mock(CloseableHttpClient.class); + try { + liveImportClientImpl.setHttpClient(httpClient); + CloseableHttpResponse response = mockResponse("" , 404, "Not Found"); + when(httpClient.execute(ArgumentMatchers.any())).thenReturn(response); + + context.restoreAuthSystemState(); + Collection recordsImported = crossRefServiceImpl.getRecords("test query", 0, 2); + assertEquals(0, recordsImported.size()); + } finally { + liveImportClientImpl.setHttpClient(originalHttpClient); + } + } + @Test public void crossRefImportMetadataGetRecordsTest() throws Exception { context.turnOffAuthorisationSystem(); From 2aae4cd78de318e816591932187049eb135fa60d Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 22 Nov 2023 14:06:05 -0600 Subject: [PATCH 498/510] Update GitHub action plugin versions. Minor fixes including using built-in Maven caching & fix to CodeCov action --- .github/workflows/build.yml | 22 ++--- .github/workflows/codescan.yml | 2 +- .github/workflows/docker.yml | 84 +++++++++---------- .../workflows/port_merged_pull_request.yml | 4 +- .github/workflows/pull_request_opened.yml | 2 +- 5 files changed, 54 insertions(+), 60 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 99c9efe0190f..d6913078e471 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -45,7 +45,7 @@ jobs: steps: # https://github.com/actions/checkout - name: Checkout codebase - uses: actions/checkout@v3 + uses: actions/checkout@v4 # https://github.com/actions/setup-java - name: Install JDK ${{ matrix.java }} @@ -53,16 +53,7 @@ jobs: with: java-version: ${{ matrix.java }} distribution: 'temurin' - - # https://github.com/actions/cache - - name: Cache Maven dependencies - uses: actions/cache@v3 - with: - # Cache entire ~/.m2/repository - path: ~/.m2/repository - # Cache key is hash of all pom.xml files. Therefore any changes to POMs will invalidate cache - key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} - restore-keys: ${{ runner.os }}-maven- + cache: 'maven' # Run parallel Maven builds based on the above 'strategy.matrix' - name: Run Maven ${{ matrix.type }} @@ -96,7 +87,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 # Download artifacts from previous 'tests' job - name: Download coverage artifacts @@ -108,10 +99,13 @@ jobs: # Retry action: https://github.com/marketplace/actions/retry-action # Codecov action: https://github.com/codecov/codecov-action - name: Upload coverage to Codecov.io - uses: Wandalen/wretry.action@v1.0.36 + uses: Wandalen/wretry.action@v1.3.0 with: action: codecov/codecov-action@v3 - # Try upload 5 times max + # Ensure codecov-action throws an error when it fails to upload + with: | + fail_ci_if_error: true + # Try re-running action 5 times max attempt_limit: 5 # Run again in 30 seconds attempt_delay: 30000 diff --git a/.github/workflows/codescan.yml b/.github/workflows/codescan.yml index 9e6dcc0b23af..13bb0d2278ad 100644 --- a/.github/workflows/codescan.yml +++ b/.github/workflows/codescan.yml @@ -35,7 +35,7 @@ jobs: steps: # https://github.com/actions/checkout - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 # https://github.com/actions/setup-java - name: Install JDK diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index f1ae184fd5c0..c538d324cdea 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -39,7 +39,7 @@ env: jobs: #################################################### # Build/Push the 'dspace/dspace-dependencies' image. - # This image is used by all other jobs. + # This image is used by all other DSpace build jobs. #################################################### dspace-dependencies: # Ensure this job never runs on forked repos. It's only executed for 'dspace/dspace' @@ -49,21 +49,21 @@ jobs: steps: # https://github.com/actions/checkout - name: Checkout codebase - uses: actions/checkout@v3 + uses: actions/checkout@v4 # https://github.com/docker/setup-buildx-action - name: Setup Docker Buildx - uses: docker/setup-buildx-action@v2 + uses: docker/setup-buildx-action@v3 # https://github.com/docker/setup-qemu-action - name: Set up QEMU emulation to build for multiple architectures - uses: docker/setup-qemu-action@v2 + uses: docker/setup-qemu-action@v3 # https://github.com/docker/login-action - name: Login to DockerHub # Only login if not a PR, as PRs only trigger a Docker build and not a push if: github.event_name != 'pull_request' - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_ACCESS_TOKEN }} @@ -72,7 +72,7 @@ jobs: # Get Metadata for docker_build_deps step below - name: Sync metadata (tags, labels) from GitHub to Docker for 'dspace-dependencies' image id: meta_build_deps - uses: docker/metadata-action@v4 + uses: docker/metadata-action@v5 with: images: dspace/dspace-dependencies tags: ${{ env.IMAGE_TAGS }} @@ -81,7 +81,7 @@ jobs: # https://github.com/docker/build-push-action - name: Build and push 'dspace-dependencies' image id: docker_build_deps - uses: docker/build-push-action@v4 + uses: docker/build-push-action@v5 with: context: . file: ./Dockerfile.dependencies @@ -106,21 +106,21 @@ jobs: steps: # https://github.com/actions/checkout - name: Checkout codebase - uses: actions/checkout@v3 + uses: actions/checkout@v4 # https://github.com/docker/setup-buildx-action - name: Setup Docker Buildx - uses: docker/setup-buildx-action@v2 + uses: docker/setup-buildx-action@v3 # https://github.com/docker/setup-qemu-action - name: Set up QEMU emulation to build for multiple architectures - uses: docker/setup-qemu-action@v2 + uses: docker/setup-qemu-action@v3 # https://github.com/docker/login-action - name: Login to DockerHub # Only login if not a PR, as PRs only trigger a Docker build and not a push if: github.event_name != 'pull_request' - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_ACCESS_TOKEN }} @@ -128,7 +128,7 @@ jobs: # Get Metadata for docker_build step below - name: Sync metadata (tags, labels) from GitHub to Docker for 'dspace' image id: meta_build - uses: docker/metadata-action@v4 + uses: docker/metadata-action@v5 with: images: dspace/dspace tags: ${{ env.IMAGE_TAGS }} @@ -136,7 +136,7 @@ jobs: - name: Build and push 'dspace' image id: docker_build - uses: docker/build-push-action@v4 + uses: docker/build-push-action@v5 with: context: . file: ./Dockerfile @@ -161,21 +161,21 @@ jobs: steps: # https://github.com/actions/checkout - name: Checkout codebase - uses: actions/checkout@v3 + uses: actions/checkout@v4 # https://github.com/docker/setup-buildx-action - name: Setup Docker Buildx - uses: docker/setup-buildx-action@v2 + uses: docker/setup-buildx-action@v3 # https://github.com/docker/setup-qemu-action - name: Set up QEMU emulation to build for multiple architectures - uses: docker/setup-qemu-action@v2 + uses: docker/setup-qemu-action@v3 # https://github.com/docker/login-action - name: Login to DockerHub # Only login if not a PR, as PRs only trigger a Docker build and not a push if: github.event_name != 'pull_request' - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_ACCESS_TOKEN }} @@ -183,7 +183,7 @@ jobs: # Get Metadata for docker_build_test step below - name: Sync metadata (tags, labels) from GitHub to Docker for 'dspace-test' image id: meta_build_test - uses: docker/metadata-action@v4 + uses: docker/metadata-action@5 with: images: dspace/dspace tags: ${{ env.IMAGE_TAGS }} @@ -194,7 +194,7 @@ jobs: - name: Build and push 'dspace-test' image id: docker_build_test - uses: docker/build-push-action@v4 + uses: docker/build-push-action@v5 with: context: . file: ./Dockerfile.test @@ -219,21 +219,21 @@ jobs: steps: # https://github.com/actions/checkout - name: Checkout codebase - uses: actions/checkout@v3 + uses: actions/checkout@v4 # https://github.com/docker/setup-buildx-action - name: Setup Docker Buildx - uses: docker/setup-buildx-action@v2 + uses: docker/setup-buildx-action@v3 # https://github.com/docker/setup-qemu-action - name: Set up QEMU emulation to build for multiple architectures - uses: docker/setup-qemu-action@v2 + uses: docker/setup-qemu-action@v3 # https://github.com/docker/login-action - name: Login to DockerHub # Only login if not a PR, as PRs only trigger a Docker build and not a push if: github.event_name != 'pull_request' - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_ACCESS_TOKEN }} @@ -241,7 +241,7 @@ jobs: # Get Metadata for docker_build_test step below - name: Sync metadata (tags, labels) from GitHub to Docker for 'dspace-cli' image id: meta_build_cli - uses: docker/metadata-action@v4 + uses: docker/metadata-action@v5 with: images: dspace/dspace-cli tags: ${{ env.IMAGE_TAGS }} @@ -249,7 +249,7 @@ jobs: - name: Build and push 'dspace-cli' image id: docker_build_cli - uses: docker/build-push-action@v4 + uses: docker/build-push-action@v5 with: context: . file: ./Dockerfile.cli @@ -276,17 +276,17 @@ jobs: # https://github.com/docker/setup-buildx-action - name: Setup Docker Buildx - uses: docker/setup-buildx-action@v2 + uses: docker/setup-buildx-action@v3 # https://github.com/docker/setup-qemu-action - name: Set up QEMU emulation to build for multiple architectures - uses: docker/setup-qemu-action@v2 + uses: docker/setup-qemu-action@v3 # https://github.com/docker/login-action - name: Login to DockerHub # Only login if not a PR, as PRs only trigger a Docker build and not a push if: github.event_name != 'pull_request' - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_ACCESS_TOKEN }} @@ -294,7 +294,7 @@ jobs: # Get Metadata for docker_build_solr step below - name: Sync metadata (tags, labels) from GitHub to Docker for 'dspace-solr' image id: meta_build_solr - uses: docker/metadata-action@v4 + uses: docker/metadata-action@v5 with: images: dspace/dspace-solr tags: ${{ env.IMAGE_TAGS }} @@ -302,7 +302,7 @@ jobs: - name: Build and push 'dspace-solr' image id: docker_build_solr - uses: docker/build-push-action@v4 + uses: docker/build-push-action@v5 with: context: . file: ./dspace/src/main/docker/dspace-solr/Dockerfile @@ -325,21 +325,21 @@ jobs: steps: # https://github.com/actions/checkout - name: Checkout codebase - uses: actions/checkout@v3 + uses: actions/checkout@v4 # https://github.com/docker/setup-buildx-action - name: Setup Docker Buildx - uses: docker/setup-buildx-action@v2 + uses: docker/setup-buildx-action@v3 # https://github.com/docker/setup-qemu-action - name: Set up QEMU emulation to build for multiple architectures - uses: docker/setup-qemu-action@v2 + uses: docker/setup-qemu-action@v3 # https://github.com/docker/login-action - name: Login to DockerHub # Only login if not a PR, as PRs only trigger a Docker build and not a push if: github.event_name != 'pull_request' - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_ACCESS_TOKEN }} @@ -347,7 +347,7 @@ jobs: # Get Metadata for docker_build_postgres step below - name: Sync metadata (tags, labels) from GitHub to Docker for 'dspace-postgres-pgcrypto' image id: meta_build_postgres - uses: docker/metadata-action@v4 + uses: docker/metadata-action@v5 with: images: dspace/dspace-postgres-pgcrypto tags: ${{ env.IMAGE_TAGS }} @@ -355,7 +355,7 @@ jobs: - name: Build and push 'dspace-postgres-pgcrypto' image id: docker_build_postgres - uses: docker/build-push-action@v4 + uses: docker/build-push-action@v5 with: # Must build out of subdirectory to have access to install script for pgcrypto context: ./dspace/src/main/docker/dspace-postgres-pgcrypto/ @@ -379,21 +379,21 @@ jobs: steps: # https://github.com/actions/checkout - name: Checkout codebase - uses: actions/checkout@v3 + uses: actions/checkout@v4 # https://github.com/docker/setup-buildx-action - name: Setup Docker Buildx - uses: docker/setup-buildx-action@v2 + uses: docker/setup-buildx-action@v3 # https://github.com/docker/setup-qemu-action - name: Set up QEMU emulation to build for multiple architectures - uses: docker/setup-qemu-action@v2 + uses: docker/setup-qemu-action@v3 # https://github.com/docker/login-action - name: Login to DockerHub # Only login if not a PR, as PRs only trigger a Docker build and not a push if: github.event_name != 'pull_request' - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_ACCESS_TOKEN }} @@ -401,7 +401,7 @@ jobs: # Get Metadata for docker_build_postgres_loadsql step below - name: Sync metadata (tags, labels) from GitHub to Docker for 'dspace-postgres-pgcrypto-loadsql' image id: meta_build_postgres_loadsql - uses: docker/metadata-action@v4 + uses: docker/metadata-action@v5 with: images: dspace/dspace-postgres-pgcrypto tags: ${{ env.IMAGE_TAGS }} @@ -412,7 +412,7 @@ jobs: - name: Build and push 'dspace-postgres-pgcrypto-loadsql' image id: docker_build_postgres_loadsql - uses: docker/build-push-action@v4 + uses: docker/build-push-action@v5 with: # Must build out of subdirectory to have access to install script for pgcrypto context: ./dspace/src/main/docker/dspace-postgres-pgcrypto-curl/ diff --git a/.github/workflows/port_merged_pull_request.yml b/.github/workflows/port_merged_pull_request.yml index 109835d14d3c..857f22755e49 100644 --- a/.github/workflows/port_merged_pull_request.yml +++ b/.github/workflows/port_merged_pull_request.yml @@ -23,11 +23,11 @@ jobs: if: github.event.pull_request.merged steps: # Checkout code - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 # Port PR to other branch (ONLY if labeled with "port to") # See https://github.com/korthout/backport-action - name: Create backport pull requests - uses: korthout/backport-action@v1 + uses: korthout/backport-action@v2 with: # Trigger based on a "port to [branch]" label on PR # (This label must specify the branch name to port to) diff --git a/.github/workflows/pull_request_opened.yml b/.github/workflows/pull_request_opened.yml index 9b61af72d187..f16e81c9fd25 100644 --- a/.github/workflows/pull_request_opened.yml +++ b/.github/workflows/pull_request_opened.yml @@ -21,4 +21,4 @@ jobs: # Assign the PR to whomever created it. This is useful for visualizing assignments on project boards # See https://github.com/toshimaru/auto-author-assign - name: Assign PR to creator - uses: toshimaru/auto-author-assign@v1.6.2 + uses: toshimaru/auto-author-assign@v2.0.1 From 538833f8a8573e55b49cb28ee6bfbc5330ad6bc4 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 22 Nov 2023 14:07:05 -0600 Subject: [PATCH 499/510] Minor fixes to Dockerfiles. No longer need 'git'. Use Maven flags to slightly speed up build/install steps. --- .github/workflows/docker.yml | 2 +- Dockerfile | 5 ++++- Dockerfile.dependencies | 10 ++++------ 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index c538d324cdea..8be8ac13fe6b 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -183,7 +183,7 @@ jobs: # Get Metadata for docker_build_test step below - name: Sync metadata (tags, labels) from GitHub to Docker for 'dspace-test' image id: meta_build_test - uses: docker/metadata-action@5 + uses: docker/metadata-action@v5 with: images: dspace/dspace tags: ${{ env.IMAGE_TAGS }} diff --git a/Dockerfile b/Dockerfile index dd633def28d9..bef894d79b64 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,7 +21,10 @@ USER dspace ADD --chown=dspace . /app/ # Build DSpace (note: this build doesn't include the optional, deprecated "dspace-rest" webapp) # Copy the dspace-installer directory to /install. Clean up the build to keep the docker image small -RUN mvn --no-transfer-progress package && \ +# Maven flags here ensure that we skip building test environment and skip all code verification checks. +# These flags speed up this compilation as much as reasonably possible. +ENV MAVEN_FLAGS="-P-test-environment -Denforcer.skip=true -Dcheckstyle.skip=true -Dlicense.skip=true -Dxml.skip=true" +RUN mvn --no-transfer-progress package ${MAVEN_FLAGS} && \ mv /app/dspace/target/${TARGET_DIR}/* /install && \ mvn clean diff --git a/Dockerfile.dependencies b/Dockerfile.dependencies index a55b323339dc..6f72ab058536 100644 --- a/Dockerfile.dependencies +++ b/Dockerfile.dependencies @@ -15,11 +15,6 @@ RUN useradd dspace \ && mkdir -p /home/dspace \ && chown -Rv dspace: /home/dspace RUN chown -Rv dspace: /app -# Need git to support buildnumber-maven-plugin, which lets us know what version of DSpace is being run. -RUN apt-get update \ - && apt-get install -y --no-install-recommends git \ - && apt-get purge -y --auto-remove \ - && rm -rf /var/lib/apt/lists/* # Switch to dspace user & run below commands as that user USER dspace @@ -28,7 +23,10 @@ USER dspace ADD --chown=dspace . /app/ # Trigger the installation of all maven dependencies (hide download progress messages) -RUN mvn --no-transfer-progress package +# Maven flags here ensure that we skip final assembly, skip building test environment and skip all code verification checks. +# These flags speed up this installation as much as reasonably possible. +ENV MAVEN_FLAGS="-P-assembly -P-test-environment -Denforcer.skip=true -Dcheckstyle.skip=true -Dlicense.skip=true -Dxml.skip=true" +RUN mvn --no-transfer-progress install ${MAVEN_FLAGS} # Clear the contents of the /app directory (including all maven builds), so no artifacts remain. # This ensures when dspace:dspace is built, it will use the Maven local cache (~/.m2) for dependencies From 0e88bfdae7844ea2313238a96975b3d8c36f0a68 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 22 Nov 2023 15:54:28 -0600 Subject: [PATCH 500/510] Refactor docker.yml to use a separate reusable-docker-build.yml script for each image build. --- .github/workflows/docker.yml | 408 ++++---------------- .github/workflows/reusable-docker-build.yml | 217 +++++++++++ 2 files changed, 288 insertions(+), 337 deletions(-) create mode 100644 .github/workflows/reusable-docker-build.yml diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 8be8ac13fe6b..2adceaa4d3c3 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -3,6 +3,7 @@ name: Docker images # Run this Build for all pushes to 'main' or maintenance branches, or tagged releases. # Also run for PRs to ensure PR doesn't break Docker build process +# NOTE: uses "reusable-docker-build.yml" to actually build each of the Docker images. on: push: branches: @@ -30,11 +31,6 @@ env: # We manage the 'latest' tag ourselves to the 'main' branch (see settings above) TAGS_FLAVOR: | latest=false - # Architectures / Platforms for which we will build Docker images - # If this is a PR, we ONLY build for AMD64. For PRs we only do a sanity check test to ensure Docker builds work. - # If this is NOT a PR (e.g. a tag or merge commit), also build for ARM64. NOTE: The ARM64 build takes MUCH - # longer (around 45mins or so) which is why we only run it when pushing a new Docker image. - PLATFORMS: linux/amd64${{ github.event_name != 'pull_request' && ', linux/arm64' || '' }} jobs: #################################################### @@ -44,54 +40,14 @@ jobs: dspace-dependencies: # Ensure this job never runs on forked repos. It's only executed for 'dspace/dspace' if: github.repository == 'dspace/dspace' - runs-on: ubuntu-latest - - steps: - # https://github.com/actions/checkout - - name: Checkout codebase - uses: actions/checkout@v4 - - # https://github.com/docker/setup-buildx-action - - name: Setup Docker Buildx - uses: docker/setup-buildx-action@v3 - - # https://github.com/docker/setup-qemu-action - - name: Set up QEMU emulation to build for multiple architectures - uses: docker/setup-qemu-action@v3 - - # https://github.com/docker/login-action - - name: Login to DockerHub - # Only login if not a PR, as PRs only trigger a Docker build and not a push - if: github.event_name != 'pull_request' - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_ACCESS_TOKEN }} - - # https://github.com/docker/metadata-action - # Get Metadata for docker_build_deps step below - - name: Sync metadata (tags, labels) from GitHub to Docker for 'dspace-dependencies' image - id: meta_build_deps - uses: docker/metadata-action@v5 - with: - images: dspace/dspace-dependencies - tags: ${{ env.IMAGE_TAGS }} - flavor: ${{ env.TAGS_FLAVOR }} - - # https://github.com/docker/build-push-action - - name: Build and push 'dspace-dependencies' image - id: docker_build_deps - uses: docker/build-push-action@v5 - with: - context: . - file: ./Dockerfile.dependencies - platforms: ${{ env.PLATFORMS }} - # For pull requests, we run the Docker build (to ensure no PR changes break the build), - # but we ONLY do an image push to DockerHub if it's NOT a PR - push: ${{ github.event_name != 'pull_request' }} - # Use tags / labels provided by 'docker/metadata-action' above - tags: ${{ steps.meta_build_deps.outputs.tags }} - labels: ${{ steps.meta_build_deps.outputs.labels }} + uses: ./.github/workflows/reusable-docker-build.yml + with: + build_id: dspace-dependencies + image_name: dspace/dspace-dependencies + dockerfile_path: ./Dockerfile.dependencies + secrets: + DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} + DOCKER_ACCESS_TOKEN: ${{ secrets.DOCKER_ACCESS_TOKEN }} ####################################### # Build/Push the 'dspace/dspace' image @@ -101,52 +57,18 @@ jobs: if: github.repository == 'dspace/dspace' # Must run after 'dspace-dependencies' job above needs: dspace-dependencies - runs-on: ubuntu-latest - - steps: - # https://github.com/actions/checkout - - name: Checkout codebase - uses: actions/checkout@v4 - - # https://github.com/docker/setup-buildx-action - - name: Setup Docker Buildx - uses: docker/setup-buildx-action@v3 - - # https://github.com/docker/setup-qemu-action - - name: Set up QEMU emulation to build for multiple architectures - uses: docker/setup-qemu-action@v3 - - # https://github.com/docker/login-action - - name: Login to DockerHub - # Only login if not a PR, as PRs only trigger a Docker build and not a push - if: github.event_name != 'pull_request' - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_ACCESS_TOKEN }} - - # Get Metadata for docker_build step below - - name: Sync metadata (tags, labels) from GitHub to Docker for 'dspace' image - id: meta_build - uses: docker/metadata-action@v5 - with: - images: dspace/dspace - tags: ${{ env.IMAGE_TAGS }} - flavor: ${{ env.TAGS_FLAVOR }} - - - name: Build and push 'dspace' image - id: docker_build - uses: docker/build-push-action@v5 - with: - context: . - file: ./Dockerfile - platforms: ${{ env.PLATFORMS }} - # For pull requests, we run the Docker build (to ensure no PR changes break the build), - # but we ONLY do an image push to DockerHub if it's NOT a PR - push: ${{ github.event_name != 'pull_request' }} - # Use tags / labels provided by 'docker/metadata-action' above - tags: ${{ steps.meta_build.outputs.tags }} - labels: ${{ steps.meta_build.outputs.labels }} + uses: ./.github/workflows/reusable-docker-build.yml + with: + build_id: dspace + image_name: dspace/dspace + dockerfile_path: ./Dockerfile + secrets: + DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} + DOCKER_ACCESS_TOKEN: ${{ secrets.DOCKER_ACCESS_TOKEN }} + # Enable redeploy of sandbox & demo if the branch for this image matches the deployment branch of + # these sites as specified in reusable-docker-build.xml + REDEPLOY_SANDBOX_URL: ${{ secrets.REDEPLOY_SANDBOX_URL }} + REDEPLOY_DEMO_URL: ${{ secrets.REDEPLOY_DEMO_URL }} ############################################################# # Build/Push the 'dspace/dspace' image ('-test' tag) @@ -156,55 +78,17 @@ jobs: if: github.repository == 'dspace/dspace' # Must run after 'dspace-dependencies' job above needs: dspace-dependencies - runs-on: ubuntu-latest - - steps: - # https://github.com/actions/checkout - - name: Checkout codebase - uses: actions/checkout@v4 - - # https://github.com/docker/setup-buildx-action - - name: Setup Docker Buildx - uses: docker/setup-buildx-action@v3 - - # https://github.com/docker/setup-qemu-action - - name: Set up QEMU emulation to build for multiple architectures - uses: docker/setup-qemu-action@v3 - - # https://github.com/docker/login-action - - name: Login to DockerHub - # Only login if not a PR, as PRs only trigger a Docker build and not a push - if: github.event_name != 'pull_request' - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_ACCESS_TOKEN }} - - # Get Metadata for docker_build_test step below - - name: Sync metadata (tags, labels) from GitHub to Docker for 'dspace-test' image - id: meta_build_test - uses: docker/metadata-action@v5 - with: - images: dspace/dspace - tags: ${{ env.IMAGE_TAGS }} - # As this is a test/development image, its tags are all suffixed with "-test". Otherwise, it uses the same - # tagging logic as the primary 'dspace/dspace' image above. - flavor: ${{ env.TAGS_FLAVOR }} - suffix=-test - - - name: Build and push 'dspace-test' image - id: docker_build_test - uses: docker/build-push-action@v5 - with: - context: . - file: ./Dockerfile.test - platforms: ${{ env.PLATFORMS }} - # For pull requests, we run the Docker build (to ensure no PR changes break the build), - # but we ONLY do an image push to DockerHub if it's NOT a PR - push: ${{ github.event_name != 'pull_request' }} - # Use tags / labels provided by 'docker/metadata-action' above - tags: ${{ steps.meta_build_test.outputs.tags }} - labels: ${{ steps.meta_build_test.outputs.labels }} + uses: ./.github/workflows/reusable-docker-build.yml + with: + build_id: dspace-test + image_name: dspace/dspace + dockerfile_path: ./Dockerfile.test + # As this is a test/development image, its tags are all suffixed with "-test". Otherwise, it uses the same + # tagging logic as the primary 'dspace/dspace' image above. + tags_flavor: suffix=-test + secrets: + DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} + DOCKER_ACCESS_TOKEN: ${{ secrets.DOCKER_ACCESS_TOKEN }} ########################################### # Build/Push the 'dspace/dspace-cli' image @@ -214,52 +98,14 @@ jobs: if: github.repository == 'dspace/dspace' # Must run after 'dspace-dependencies' job above needs: dspace-dependencies - runs-on: ubuntu-latest - - steps: - # https://github.com/actions/checkout - - name: Checkout codebase - uses: actions/checkout@v4 - - # https://github.com/docker/setup-buildx-action - - name: Setup Docker Buildx - uses: docker/setup-buildx-action@v3 - - # https://github.com/docker/setup-qemu-action - - name: Set up QEMU emulation to build for multiple architectures - uses: docker/setup-qemu-action@v3 - - # https://github.com/docker/login-action - - name: Login to DockerHub - # Only login if not a PR, as PRs only trigger a Docker build and not a push - if: github.event_name != 'pull_request' - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_ACCESS_TOKEN }} - - # Get Metadata for docker_build_test step below - - name: Sync metadata (tags, labels) from GitHub to Docker for 'dspace-cli' image - id: meta_build_cli - uses: docker/metadata-action@v5 - with: - images: dspace/dspace-cli - tags: ${{ env.IMAGE_TAGS }} - flavor: ${{ env.TAGS_FLAVOR }} - - - name: Build and push 'dspace-cli' image - id: docker_build_cli - uses: docker/build-push-action@v5 - with: - context: . - file: ./Dockerfile.cli - platforms: ${{ env.PLATFORMS }} - # For pull requests, we run the Docker build (to ensure no PR changes break the build), - # but we ONLY do an image push to DockerHub if it's NOT a PR - push: ${{ github.event_name != 'pull_request' }} - # Use tags / labels provided by 'docker/metadata-action' above - tags: ${{ steps.meta_build_cli.outputs.tags }} - labels: ${{ steps.meta_build_cli.outputs.labels }} + uses: ./.github/workflows/reusable-docker-build.yml + with: + build_id: dspace-cli + image_name: dspace/dspace-cli + dockerfile_path: ./Dockerfile.cli + secrets: + DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} + DOCKER_ACCESS_TOKEN: ${{ secrets.DOCKER_ACCESS_TOKEN }} ########################################### # Build/Push the 'dspace/dspace-solr' image @@ -267,52 +113,14 @@ jobs: dspace-solr: # Ensure this job never runs on forked repos. It's only executed for 'dspace/dspace' if: github.repository == 'dspace/dspace' - runs-on: ubuntu-latest - - steps: - # https://github.com/actions/checkout - - name: Checkout codebase - uses: actions/checkout@v3 - - # https://github.com/docker/setup-buildx-action - - name: Setup Docker Buildx - uses: docker/setup-buildx-action@v3 - - # https://github.com/docker/setup-qemu-action - - name: Set up QEMU emulation to build for multiple architectures - uses: docker/setup-qemu-action@v3 - - # https://github.com/docker/login-action - - name: Login to DockerHub - # Only login if not a PR, as PRs only trigger a Docker build and not a push - if: github.event_name != 'pull_request' - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_ACCESS_TOKEN }} - - # Get Metadata for docker_build_solr step below - - name: Sync metadata (tags, labels) from GitHub to Docker for 'dspace-solr' image - id: meta_build_solr - uses: docker/metadata-action@v5 - with: - images: dspace/dspace-solr - tags: ${{ env.IMAGE_TAGS }} - flavor: ${{ env.TAGS_FLAVOR }} - - - name: Build and push 'dspace-solr' image - id: docker_build_solr - uses: docker/build-push-action@v5 - with: - context: . - file: ./dspace/src/main/docker/dspace-solr/Dockerfile - platforms: ${{ env.PLATFORMS }} - # For pull requests, we run the Docker build (to ensure no PR changes break the build), - # but we ONLY do an image push to DockerHub if it's NOT a PR - push: ${{ github.event_name != 'pull_request' }} - # Use tags / labels provided by 'docker/metadata-action' above - tags: ${{ steps.meta_build_solr.outputs.tags }} - labels: ${{ steps.meta_build_solr.outputs.labels }} + uses: ./.github/workflows/reusable-docker-build.yml + with: + build_id: dspace-solr + image_name: dspace/dspace-solr + dockerfile_path: ./dspace/src/main/docker/dspace-solr/Dockerfile + secrets: + DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} + DOCKER_ACCESS_TOKEN: ${{ secrets.DOCKER_ACCESS_TOKEN }} ########################################################### # Build/Push the 'dspace/dspace-postgres-pgcrypto' image @@ -320,53 +128,16 @@ jobs: dspace-postgres-pgcrypto: # Ensure this job never runs on forked repos. It's only executed for 'dspace/dspace' if: github.repository == 'dspace/dspace' - runs-on: ubuntu-latest - - steps: - # https://github.com/actions/checkout - - name: Checkout codebase - uses: actions/checkout@v4 - - # https://github.com/docker/setup-buildx-action - - name: Setup Docker Buildx - uses: docker/setup-buildx-action@v3 - - # https://github.com/docker/setup-qemu-action - - name: Set up QEMU emulation to build for multiple architectures - uses: docker/setup-qemu-action@v3 - - # https://github.com/docker/login-action - - name: Login to DockerHub - # Only login if not a PR, as PRs only trigger a Docker build and not a push - if: github.event_name != 'pull_request' - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_ACCESS_TOKEN }} - - # Get Metadata for docker_build_postgres step below - - name: Sync metadata (tags, labels) from GitHub to Docker for 'dspace-postgres-pgcrypto' image - id: meta_build_postgres - uses: docker/metadata-action@v5 - with: - images: dspace/dspace-postgres-pgcrypto - tags: ${{ env.IMAGE_TAGS }} - flavor: ${{ env.TAGS_FLAVOR }} - - - name: Build and push 'dspace-postgres-pgcrypto' image - id: docker_build_postgres - uses: docker/build-push-action@v5 - with: - # Must build out of subdirectory to have access to install script for pgcrypto - context: ./dspace/src/main/docker/dspace-postgres-pgcrypto/ - dockerfile: Dockerfile - platforms: ${{ env.PLATFORMS }} - # For pull requests, we run the Docker build (to ensure no PR changes break the build), - # but we ONLY do an image push to DockerHub if it's NOT a PR - push: ${{ github.event_name != 'pull_request' }} - # Use tags / labels provided by 'docker/metadata-action' above - tags: ${{ steps.meta_build_postgres.outputs.tags }} - labels: ${{ steps.meta_build_postgres.outputs.labels }} + uses: ./.github/workflows/reusable-docker-build.yml + with: + build_id: dspace-postgres-pgcrypto + image_name: dspace/dspace-postgres-pgcrypto + # Must build out of subdirectory to have access to install script for pgcrypto. + # NOTE: this context will build the image based on the Dockerfile in the specified directory + dockerfile_context: ./dspace/src/main/docker/dspace-postgres-pgcrypto/ + secrets: + DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} + DOCKER_ACCESS_TOKEN: ${{ secrets.DOCKER_ACCESS_TOKEN }} ######################################################################## # Build/Push the 'dspace/dspace-postgres-pgcrypto' image (-loadsql tag) @@ -374,53 +145,16 @@ jobs: dspace-postgres-pgcrypto-loadsql: # Ensure this job never runs on forked repos. It's only executed for 'dspace/dspace' if: github.repository == 'dspace/dspace' - runs-on: ubuntu-latest - - steps: - # https://github.com/actions/checkout - - name: Checkout codebase - uses: actions/checkout@v4 - - # https://github.com/docker/setup-buildx-action - - name: Setup Docker Buildx - uses: docker/setup-buildx-action@v3 - - # https://github.com/docker/setup-qemu-action - - name: Set up QEMU emulation to build for multiple architectures - uses: docker/setup-qemu-action@v3 - - # https://github.com/docker/login-action - - name: Login to DockerHub - # Only login if not a PR, as PRs only trigger a Docker build and not a push - if: github.event_name != 'pull_request' - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_ACCESS_TOKEN }} - - # Get Metadata for docker_build_postgres_loadsql step below - - name: Sync metadata (tags, labels) from GitHub to Docker for 'dspace-postgres-pgcrypto-loadsql' image - id: meta_build_postgres_loadsql - uses: docker/metadata-action@v5 - with: - images: dspace/dspace-postgres-pgcrypto - tags: ${{ env.IMAGE_TAGS }} - # Suffix all tags with "-loadsql". Otherwise, it uses the same - # tagging logic as the primary 'dspace/dspace-postgres-pgcrypto' image above. - flavor: ${{ env.TAGS_FLAVOR }} - suffix=-loadsql - - - name: Build and push 'dspace-postgres-pgcrypto-loadsql' image - id: docker_build_postgres_loadsql - uses: docker/build-push-action@v5 - with: - # Must build out of subdirectory to have access to install script for pgcrypto - context: ./dspace/src/main/docker/dspace-postgres-pgcrypto-curl/ - dockerfile: Dockerfile - platforms: ${{ env.PLATFORMS }} - # For pull requests, we run the Docker build (to ensure no PR changes break the build), - # but we ONLY do an image push to DockerHub if it's NOT a PR - push: ${{ github.event_name != 'pull_request' }} - # Use tags / labels provided by 'docker/metadata-action' above - tags: ${{ steps.meta_build_postgres_loadsql.outputs.tags }} - labels: ${{ steps.meta_build_postgres_loadsql.outputs.labels }} \ No newline at end of file + uses: ./.github/workflows/reusable-docker-build.yml + with: + build_id: dspace-postgres-pgcrypto-loadsql + image_name: dspace/dspace-postgres-pgcrypto + # Must build out of subdirectory to have access to install script for pgcrypto. + # NOTE: this context will build the image based on the Dockerfile in the specified directory + dockerfile_context: ./dspace/src/main/docker/dspace-postgres-pgcrypto-curl/ + # Suffix all tags with "-loadsql". Otherwise, it uses the same + # tagging logic as the primary 'dspace/dspace-postgres-pgcrypto' image above. + tags_flavor: suffix=-loadsql + secrets: + DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} + DOCKER_ACCESS_TOKEN: ${{ secrets.DOCKER_ACCESS_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/reusable-docker-build.yml b/.github/workflows/reusable-docker-build.yml new file mode 100644 index 000000000000..46bdab3b6827 --- /dev/null +++ b/.github/workflows/reusable-docker-build.yml @@ -0,0 +1,217 @@ +# +# DSpace's reusable Docker build/push workflow. +# +# This is used by docker.yml for all Docker image builds +name: Reusable DSpace Docker Build + +on: + workflow_call: + # Possible Inputs to this reusable job + inputs: + # Build name/id for this Docker build. Used for digest storage to avoid digest overlap between builds. + build_id: + required: true + type: string + # Requires the image name to build (e.g dspace/dspace-test) + image_name: + required: true + type: string + # Optionally the path to the Dockerfile to use for the build. (Default is [dockerfile_context]/Dockerfile) + dockerfile_path: + required: false + type: string + # Optionally the context directory to build the Dockerfile within. Defaults to "." (current directory) + dockerfile_context: + required: false + type: string + # If Docker image should have additional tag flavor details (e.g. a suffix), it may be passed in. + tags_flavor: + required: false + type: string + secrets: + # Requires that Docker login info be passed in as secrets. + DOCKER_USERNAME: + required: true + DOCKER_ACCESS_TOKEN: + required: true + # These URL secrets are optional. When specified & branch checks match, the redeployment code below will trigger. + # Therefore builds which need to trigger redeployment MUST specify these URLs. All others should leave them empty. + REDEPLOY_SANDBOX_URL: + required: false + REDEPLOY_DEMO_URL: + required: false + +# Define shared default settings as environment variables +env: + IMAGE_NAME: ${{ inputs.image_name }} + # Define tags to use for Docker images based on Git tags/branches (for docker/metadata-action) + # For a new commit on default branch (main), use the literal tag 'latest' on Docker image. + # For a new commit on other branches, use the branch name as the tag for Docker image. + # For a new tag, copy that tag name as the tag for Docker image. + IMAGE_TAGS: | + type=raw,value=latest,enable=${{ github.ref_name == github.event.repository.default_branch }} + type=ref,event=branch,enable=${{ github.ref_name != github.event.repository.default_branch }} + type=ref,event=tag + # Define default tag "flavor" for docker/metadata-action per + # https://github.com/docker/metadata-action#flavor-input + # We manage the 'latest' tag ourselves to the 'main' branch (see settings above) + TAGS_FLAVOR: | + latest=false + ${{ inputs.tags_flavor }} + # When these URL variables are specified & required branch matches, then the sandbox or demo site will be redeployed. + # See "Redeploy" steps below for more details. + REDEPLOY_SANDBOX_URL: ${{ secrets.REDEPLOY_SANDBOX_URL }} + REDEPLOY_DEMO_URL: ${{ secrets.REDEPLOY_DEMO_URL }} + # Current DSpace maintenance branch (and architecture) which is deployed to demo.dspace.org / sandbox.dspace.org + # (NOTE: No deployment branch specified for sandbox.dspace.org as it uses the default_branch) + DEPLOY_DEMO_BRANCH: 'dspace-7_x' + DEPLOY_ARCH: 'linux/amd64' + +jobs: + docker-build: + + strategy: + matrix: + # Architectures / Platforms for which we will build Docker images + arch: [ 'linux/amd64', 'linux/arm64' ] + os: [ ubuntu-latest ] + isPr: + - ${{ github.event_name == 'pull_request' }} + # If this is a PR, we ONLY build for AMD64. For PRs we only do a sanity check test to ensure Docker builds work. + # The below exclude therefore ensures we do NOT build ARM64 for PRs. + exclude: + - isPr: true + os: ubuntu-latest + arch: linux/arm64 + + runs-on: ${{ matrix.os }} + + steps: + # https://github.com/actions/checkout + - name: Checkout codebase + uses: actions/checkout@v4 + + # https://github.com/docker/setup-buildx-action + - name: Setup Docker Buildx + uses: docker/setup-buildx-action@v3 + + # https://github.com/docker/setup-qemu-action + - name: Set up QEMU emulation to build for multiple architectures + uses: docker/setup-qemu-action@v3 + + # https://github.com/docker/login-action + - name: Login to DockerHub + # Only login if not a PR, as PRs only trigger a Docker build and not a push + if: ${{ ! matrix.isPr }} + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_ACCESS_TOKEN }} + + # https://github.com/docker/metadata-action + # Get Metadata for docker_build_deps step below + - name: Sync metadata (tags, labels) from GitHub to Docker for image + id: meta_build + uses: docker/metadata-action@v5 + with: + images: ${{ env.IMAGE_NAME }} + tags: ${{ env.IMAGE_TAGS }} + flavor: ${{ env.TAGS_FLAVOR }} + + # https://github.com/docker/build-push-action + - name: Build and push image + id: docker_build + uses: docker/build-push-action@v5 + with: + context: ${{ inputs.dockerfile_context || '.' }} + file: ${{ inputs.dockerfile_path }} + platforms: ${{ matrix.arch }} + # For pull requests, we run the Docker build (to ensure no PR changes break the build), + # but we ONLY do an image push to DockerHub if it's NOT a PR + push: ${{ ! matrix.isPr }} + # Use tags / labels provided by 'docker/metadata-action' above + tags: ${{ steps.meta_build.outputs.tags }} + labels: ${{ steps.meta_build.outputs.labels }} + + # Export the digest of Docker build locally (for non PRs only) + - name: Export Docker build digest + if: ${{ ! matrix.isPr }} + run: | + mkdir -p /tmp/digests + digest="${{ steps.docker_build.outputs.digest }}" + touch "/tmp/digests/${digest#sha256:}" + + # Upload digest to an artifact, so that it can be used in manifest below + - name: Upload Docker build digest to artifact + if: ${{ ! matrix.isPr }} + uses: actions/upload-artifact@v3 + with: + name: digests-${{ inputs.build_id }} + path: /tmp/digests/* + if-no-files-found: error + retention-days: 1 + + # If this build is NOT a PR and passed in a REDEPLOY_SANDBOX_URL secret, + # Then redeploy https://sandbox.dspace.org if this build is for our deployment architecture and 'main' branch. + - name: Redeploy sandbox.dspace.org (based on main branch) + if: | + !matrix.isPR && + env.REDEPLOY_SANDBOX_URL != '' && + matrix.arch == env.DEPLOY_ARCH && + github.ref_name == github.event.repository.default_branch + run: | + curl -X POST $REDEPLOY_SANDBOX_URL + + # If this build is NOT a PR and passed in a REDEPLOY_DEMO_URL secret, + # Then redeploy https://demo.dspace.org if this build is for our deployment architecture and demo branch. + - name: Redeploy demo.dspace.org (based on maintenace branch) + if: | + !matrix.isPR && + env.REDEPLOY_DEMO_URL != '' && + matrix.arch == env.DEPLOY_ARCH && + github.ref_name == env.DEPLOY_DEMO_BRANCH + run: | + curl -X POST $REDEPLOY_DEMO_URL + + # Merge Docker digests (from various architectures) into a manifest. + # This runs after all Docker builds complete above, and it tells hub.docker.com + # that these builds should be all included in the manifest for this tag. + # (e.g. AMD64 and ARM64 should be listed as options under the same tagged Docker image) + docker-build_manifest: + if: ${{ github.event_name != 'pull_request' }} + runs-on: ubuntu-latest + needs: + - docker-build + steps: + - name: Download Docker build digests + uses: actions/download-artifact@v3 + with: + name: digests-${{ inputs.build_id }} + path: /tmp/digests + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Add Docker metadata for image + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.IMAGE_NAME }} + tags: ${{ env.IMAGE_TAGS }} + flavor: ${{ env.TAGS_FLAVOR }} + + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_ACCESS_TOKEN }} + + - name: Create manifest list from digests and push + working-directory: /tmp/digests + run: | + docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ + $(printf '${{ env.IMAGE_NAME }}@sha256:%s ' *) + + - name: Inspect image + run: | + docker buildx imagetools inspect ${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }} From e247f89325461767cde7e9fa4d9c39149e874312 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 28 Nov 2023 14:28:14 -0600 Subject: [PATCH 501/510] Ensure dspace-solr redeploys the Solr instances for Demo/Sandbox --- .github/workflows/docker.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 2adceaa4d3c3..dd44e92f0470 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -121,6 +121,10 @@ jobs: secrets: DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} DOCKER_ACCESS_TOKEN: ${{ secrets.DOCKER_ACCESS_TOKEN }} + # Enable redeploy of sandbox & demo SOLR instance whenever dspace-solr image changes for deployed branch. + # These URLs MUST use different secrets than 'dspace/dspace' image build above as they are deployed separately. + REDEPLOY_SANDBOX_URL: ${{ secrets.REDEPLOY_SANDBOX_SOLR_URL }} + REDEPLOY_DEMO_URL: ${{ secrets.REDEPLOY_DEMO_SOLR_URL }} ########################################################### # Build/Push the 'dspace/dspace-postgres-pgcrypto' image From b2dfa9f018061c8ff375fa6c5394d77abc7d5049 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 28 Nov 2023 16:59:41 -0600 Subject: [PATCH 502/510] Remove unused env variables from docker.yml build script --- .github/workflows/docker.yml | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index dd44e92f0470..338c7371f61a 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -16,22 +16,6 @@ on: permissions: contents: read # to fetch code (actions/checkout) -# Define shared environment variables for all jobs below -env: - # Define tags to use for Docker images based on Git tags/branches (for docker/metadata-action) - # For a new commit on default branch (main), use the literal tag 'latest' on Docker image. - # For a new commit on other branches, use the branch name as the tag for Docker image. - # For a new tag, copy that tag name as the tag for Docker image. - IMAGE_TAGS: | - type=raw,value=latest,enable=${{ endsWith(github.ref, github.event.repository.default_branch) }} - type=ref,event=branch,enable=${{ !endsWith(github.ref, github.event.repository.default_branch) }} - type=ref,event=tag - # Define default tag "flavor" for docker/metadata-action per - # https://github.com/docker/metadata-action#flavor-input - # We manage the 'latest' tag ourselves to the 'main' branch (see settings above) - TAGS_FLAVOR: | - latest=false - jobs: #################################################### # Build/Push the 'dspace/dspace-dependencies' image. From 44fc15f74baf6aa23c70f3b73975f3c82401e499 Mon Sep 17 00:00:00 2001 From: Shankeerthan Kasilingam Date: Tue, 5 Dec 2023 16:22:01 +0530 Subject: [PATCH 503/510] fix: Failure of org.dspace.app.rest.SitemapRestControllerIT when running locally --- .../dspace/app/rest/SitemapRestControllerIT.java | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SitemapRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SitemapRestControllerIT.java index 175fb34e6cac..04d22718e846 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SitemapRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SitemapRestControllerIT.java @@ -216,7 +216,12 @@ public void testSitemap_sitemapIndexXml() throws Exception { //** THEN ** .andExpect(status().isOk()) //We expect the content type to match - .andExpect(content().contentType("application/xml;charset=UTF-8")) + .andExpect(res -> { + String actual = res.getResponse().getContentType(); + assertTrue("Content Type", + "text/xml;charset=UTF-8".equals(actual) || + "application/xml;charset=UTF-8".equals(actual)); + }) .andReturn(); String response = result.getResponse().getContentAsString(); @@ -232,7 +237,12 @@ public void testSitemap_sitemap0Xml() throws Exception { //** THEN ** .andExpect(status().isOk()) //We expect the content type to match - .andExpect(content().contentType("application/xml;charset=UTF-8")) + .andExpect(res -> { + String actual = res.getResponse().getContentType(); + assertTrue("Content Type", + "text/xml;charset=UTF-8".equals(actual) || + "application/xml;charset=UTF-8".equals(actual)); + }) .andReturn(); String response = result.getResponse().getContentAsString(); From ff5f3fa74f2b3ac0144d6e59ed824ada016167cc Mon Sep 17 00:00:00 2001 From: frabacche Date: Tue, 12 Dec 2023 16:15:25 +0100 Subject: [PATCH 504/510] CST-5249 rename OpenAIRE to Openaire, other minor issues --- .../org/dspace/content/ItemServiceImpl.java | 4 +- .../dspace/eperson/EPersonServiceImpl.java | 4 +- ...nector.java => OpenaireRestConnector.java} | 40 +++++------ ...ERestToken.java => OpenaireRestToken.java} | 6 +- ....java => OpenaireFundingDataProvider.java} | 30 ++++----- .../QAEventsDeleteCascadeConsumer.java | 2 +- .../java/org/dspace/qaevent/QASource.java | 2 +- .../{QAEventsDao.java => QAEventsDAO.java} | 2 +- ...ventsDaoImpl.java => QAEventsDAOImpl.java} | 8 +-- .../qaevent/script/OpenaireEventsImport.java | 4 +- ...enaireEventsImportScriptConfiguration.java | 4 +- .../service/dto/OpenaireMessageDTO.java | 2 +- .../service/impl/QAEventServiceImpl.java | 8 +-- .../config/spring/api/external-openaire.xml | 8 +-- .../config/spring/api/item-filters.xml | 4 +- .../config/spring/api/scripts.xml | 2 +- .../org/dspace/content/CollectionTest.java | 67 +++++++++++++++++++ ...or.java => MockOpenaireRestConnector.java} | 6 +- ...a => OpenaireFundingDataProviderTest.java} | 48 ++++++------- .../app/rest/RestResourceController.java | 11 +++ .../repository/QAEventRestRepository.java | 5 +- .../dspaceFolder/config/item-submission.xml | 32 ++++----- .../config/spring/api/external-openaire.xml | 10 +-- .../config/spring/api/test-discovery.xml | 4 +- .../app/rest/DiscoveryVersioningIT.java | 4 +- .../rest/ExternalSourcesRestControllerIT.java | 2 +- ... => OpenaireFundingExternalSourcesIT.java} | 26 +++---- .../app/rest/QAEventRestRepositoryIT.java | 15 ++++- ...a => MockOpenaireFundingDataProvider.java} | 8 +-- .../crosswalks/oai/transformers/openaire4.xsl | 4 +- dspace/config/crosswalks/oai/xoai.xml | 36 +++++----- dspace/config/item-submission.xml | 32 ++++----- dspace/config/modules/openaire-client.cfg | 8 +-- dspace/config/registries/openaire4-types.xml | 8 +-- .../registries/relationship-formats.xml | 2 +- .../registries/schema-organization-types.xml | 4 +- .../config/registries/schema-person-types.xml | 10 +-- .../registries/schema-project-types.xml | 4 +- dspace/config/spring/api/discovery.xml | 6 +- .../config/spring/api/external-openaire.xml | 8 +-- dspace/config/spring/api/item-filters.xml | 4 +- dspace/config/spring/api/qaevents.xml | 2 +- dspace/config/spring/api/scripts.xml | 2 +- dspace/config/spring/rest/scripts.xml | 2 +- dspace/config/submission-forms.xml | 20 +++--- dspace/solr/qaevent/conf/schema.xml | 23 ------- 46 files changed, 306 insertions(+), 237 deletions(-) rename dspace-api/src/main/java/org/dspace/external/{OpenAIRERestConnector.java => OpenaireRestConnector.java} (92%) rename dspace-api/src/main/java/org/dspace/external/{OpenAIRERestToken.java => OpenaireRestToken.java} (89%) rename dspace-api/src/main/java/org/dspace/external/provider/impl/{OpenAIREFundingDataProvider.java => OpenaireFundingDataProvider.java} (94%) rename dspace-api/src/main/java/org/dspace/qaevent/dao/{QAEventsDao.java => QAEventsDAO.java} (98%) rename dspace-api/src/main/java/org/dspace/qaevent/dao/impl/{QAEventsDaoImpl.java => QAEventsDAOImpl.java} (89%) rename dspace-api/src/test/java/org/dspace/external/{MockOpenAIRERestConnector.java => MockOpenaireRestConnector.java} (90%) rename dspace-api/src/test/java/org/dspace/external/provider/impl/{OpenAIREFundingDataProviderTest.java => OpenaireFundingDataProviderTest.java} (59%) rename dspace-server-webapp/src/test/java/org/dspace/app/rest/{OpenAIREFundingExternalSourcesIT.java => OpenaireFundingExternalSourcesIT.java} (83%) rename dspace-server-webapp/src/test/java/org/dspace/external/provider/impl/{MockOpenAIREFundingDataProvider.java => MockOpenaireFundingDataProvider.java} (91%) diff --git a/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java index e09e4725cada..f6144961c6f0 100644 --- a/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java @@ -77,7 +77,7 @@ import org.dspace.orcid.service.OrcidSynchronizationService; import org.dspace.orcid.service.OrcidTokenService; import org.dspace.profile.service.ResearcherProfileService; -import org.dspace.qaevent.dao.QAEventsDao; +import org.dspace.qaevent.dao.QAEventsDAO; import org.dspace.services.ConfigurationService; import org.dspace.versioning.service.VersioningService; import org.dspace.workflow.WorkflowItemService; @@ -172,7 +172,7 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl implements It protected SubscribeService subscribeService; @Autowired - private QAEventsDao qaEventsDao; + private QAEventsDAO qaEventsDao; protected ItemServiceImpl() { super(); diff --git a/dspace-api/src/main/java/org/dspace/eperson/EPersonServiceImpl.java b/dspace-api/src/main/java/org/dspace/eperson/EPersonServiceImpl.java index 0a8f6d6f3d39..b9fde450c2e1 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/EPersonServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/EPersonServiceImpl.java @@ -48,7 +48,7 @@ import org.dspace.eperson.service.SubscribeService; import org.dspace.event.Event; import org.dspace.orcid.service.OrcidTokenService; -import org.dspace.qaevent.dao.QAEventsDao; +import org.dspace.qaevent.dao.QAEventsDAO; import org.dspace.services.ConfigurationService; import org.dspace.util.UUIDUtils; import org.dspace.versioning.Version; @@ -109,7 +109,7 @@ public class EPersonServiceImpl extends DSpaceObjectServiceImpl impleme @Autowired protected OrcidTokenService orcidTokenService; @Autowired - protected QAEventsDao qaEventsDao; + protected QAEventsDAO qaEventsDao; protected EPersonServiceImpl() { super(); diff --git a/dspace-api/src/main/java/org/dspace/external/OpenAIRERestConnector.java b/dspace-api/src/main/java/org/dspace/external/OpenaireRestConnector.java similarity index 92% rename from dspace-api/src/main/java/org/dspace/external/OpenAIRERestConnector.java rename to dspace-api/src/main/java/org/dspace/external/OpenaireRestConnector.java index b0aa4aba13a9..2ec08be0551e 100644 --- a/dspace-api/src/main/java/org/dspace/external/OpenAIRERestConnector.java +++ b/dspace-api/src/main/java/org/dspace/external/OpenaireRestConnector.java @@ -40,20 +40,20 @@ import org.springframework.beans.factory.annotation.Autowired; /** - * based on OrcidRestConnector it's a rest connector for OpenAIRE API providing + * based on OrcidRestConnector it's a rest connector for Openaire API providing * ways to perform searches and token grabbing * * @author paulo-graca * */ -public class OpenAIRERestConnector { +public class OpenaireRestConnector { /** * log4j logger */ - private static Logger log = org.apache.logging.log4j.LogManager.getLogger(OpenAIRERestConnector.class); + private static Logger log = org.apache.logging.log4j.LogManager.getLogger(OpenaireRestConnector.class); /** - * OpenAIRE API Url + * Openaire API Url * and can be configured with: openaire.api.url */ private String url = "https://api.openaire.eu"; @@ -65,30 +65,30 @@ public class OpenAIRERestConnector { boolean tokenEnabled = false; /** - * OpenAIRE Authorization and Authentication Token Service URL + * Openaire Authorization and Authentication Token Service URL * and can be configured with: openaire.token.url */ private String tokenServiceUrl; /** - * OpenAIRE clientId + * Openaire clientId * and can be configured with: openaire.token.clientId */ private String clientId; /** - * OpenAIRERest access token + * OpenaireRest access token */ - private OpenAIRERestToken accessToken; + private OpenaireRestToken accessToken; /** - * OpenAIRE clientSecret + * Openaire clientSecret * and can be configured with: openaire.token.clientSecret */ private String clientSecret; - public OpenAIRERestConnector(String url) { + public OpenaireRestConnector(String url) { this.url = url; } @@ -99,11 +99,11 @@ public OpenAIRERestConnector(String url) { * * @throws IOException */ - public OpenAIRERestToken grabNewAccessToken() throws IOException { + public OpenaireRestToken grabNewAccessToken() throws IOException { if (StringUtils.isBlank(tokenServiceUrl) || StringUtils.isBlank(clientId) || StringUtils.isBlank(clientSecret)) { - throw new IOException("Cannot grab OpenAIRE token with nulls service url, client id or secret"); + throw new IOException("Cannot grab Openaire token with nulls service url, client id or secret"); } String auth = clientId + ":" + clientSecret; @@ -145,13 +145,13 @@ public OpenAIRERestToken grabNewAccessToken() throws IOException { 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(), + return new OpenaireRestToken(responseObject.get("access_token").toString(), Long.valueOf(responseObject.get("expires_in").toString())); } /** - * Perform a GET request to the OpenAIRE API + * Perform a GET request to the Openaire API * * @param file * @param accessToken @@ -218,12 +218,12 @@ public InputStream get(String file, String accessToken) { } /** - * Perform an OpenAIRE Project Search By Keywords + * Perform an Openaire Project Search By Keywords * * @param page * @param size * @param keywords - * @return OpenAIRE Response + * @return Openaire Response */ public Response searchProjectByKeywords(int page, int size, String... keywords) { String path = "search/projects?keywords=" + String.join("+", keywords); @@ -231,13 +231,13 @@ public Response searchProjectByKeywords(int page, int size, String... keywords) } /** - * Perform an OpenAIRE Project Search By ID and by Funder + * Perform an Openaire Project Search By ID and by Funder * * @param projectID * @param projectFunder * @param page * @param size - * @return OpenAIRE Response + * @return Openaire Response */ public Response searchProjectByIDAndFunder(String projectID, String projectFunder, int page, int size) { String path = "search/projects?grantID=" + projectID + "&funder=" + projectFunder; @@ -245,12 +245,12 @@ public Response searchProjectByIDAndFunder(String projectID, String projectFunde } /** - * Perform an OpenAIRE Search request + * Perform an Openaire Search request * * @param path * @param page * @param size - * @return OpenAIRE Response + * @return Openaire Response */ public Response search(String path, int page, int size) { String[] queryStringPagination = { "page=" + page, "size=" + size }; diff --git a/dspace-api/src/main/java/org/dspace/external/OpenAIRERestToken.java b/dspace-api/src/main/java/org/dspace/external/OpenaireRestToken.java similarity index 89% rename from dspace-api/src/main/java/org/dspace/external/OpenAIRERestToken.java rename to dspace-api/src/main/java/org/dspace/external/OpenaireRestToken.java index 203f09b3c657..f5dc2b27f8ab 100644 --- a/dspace-api/src/main/java/org/dspace/external/OpenAIRERestToken.java +++ b/dspace-api/src/main/java/org/dspace/external/OpenaireRestToken.java @@ -8,13 +8,13 @@ package org.dspace.external; /** - * OpenAIRE rest API token to be used when grabbing an accessToken.
    + * Openaire rest API token to be used when grabbing an accessToken.
    * Based on https://develop.openaire.eu/basic.html * * @author paulo-graca * */ -public class OpenAIRERestToken { +public class OpenaireRestToken { /** * Stored access token @@ -32,7 +32,7 @@ public class OpenAIRERestToken { * @param accessToken * @param expiresIn */ - public OpenAIRERestToken(String accessToken, Long expiresIn) { + public OpenaireRestToken(String accessToken, Long expiresIn) { this.accessToken = accessToken; this.setExpirationDate(expiresIn); } diff --git a/dspace-api/src/main/java/org/dspace/external/provider/impl/OpenAIREFundingDataProvider.java b/dspace-api/src/main/java/org/dspace/external/provider/impl/OpenaireFundingDataProvider.java similarity index 94% rename from dspace-api/src/main/java/org/dspace/external/provider/impl/OpenAIREFundingDataProvider.java rename to dspace-api/src/main/java/org/dspace/external/provider/impl/OpenaireFundingDataProvider.java index 8ca5b7c0ea5c..62cef508c556 100644 --- a/dspace-api/src/main/java/org/dspace/external/provider/impl/OpenAIREFundingDataProvider.java +++ b/dspace-api/src/main/java/org/dspace/external/provider/impl/OpenaireFundingDataProvider.java @@ -31,7 +31,7 @@ import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; import org.dspace.content.dto.MetadataValueDTO; -import org.dspace.external.OpenAIRERestConnector; +import org.dspace.external.OpenaireRestConnector; import org.dspace.external.model.ExternalDataObject; import org.dspace.external.provider.AbstractExternalDataProvider; import org.dspace.importer.external.metadatamapping.MetadataFieldConfig; @@ -39,13 +39,13 @@ /** * This class is the implementation of the ExternalDataProvider interface that - * will deal with the OpenAIRE External Data lookup + * will deal with the Openaire External Data lookup * * @author paulo-graca */ -public class OpenAIREFundingDataProvider extends AbstractExternalDataProvider { +public class OpenaireFundingDataProvider extends AbstractExternalDataProvider { - private static Logger log = org.apache.logging.log4j.LogManager.getLogger(OpenAIREFundingDataProvider.class); + private static Logger log = org.apache.logging.log4j.LogManager.getLogger(OpenaireFundingDataProvider.class); /** * GrantAgreement prefix @@ -75,7 +75,7 @@ public class OpenAIREFundingDataProvider extends AbstractExternalDataProvider { /** * Connector to handle token and requests */ - protected OpenAIRERestConnector connector; + protected OpenaireRestConnector connector; protected Map metadataFields; @@ -93,7 +93,7 @@ public Optional getExternalDataObject(String id) { // characters that must be escaped for the <:entry-id> String decodedId = new String(Base64.getDecoder().decode(id)); if (!isValidProjectURI(decodedId)) { - log.error("Invalid ID for OpenAIREFunding - " + id); + log.error("Invalid ID for OpenaireFunding - " + id); return Optional.empty(); } Response response = searchByProjectURI(decodedId); @@ -101,7 +101,7 @@ public Optional getExternalDataObject(String id) { try { if (response.getHeader() != null && Integer.parseInt(response.getHeader().getTotal()) > 0) { Project project = response.getResults().getResult().get(0).getMetadata().getEntity().getProject(); - ExternalDataObject externalDataObject = new OpenAIREFundingDataProvider + ExternalDataObject externalDataObject = new OpenaireFundingDataProvider .ExternalDataObjectBuilder(project) .setId(generateProjectURI(project)) .setSource(sourceIdentifier) @@ -123,7 +123,7 @@ public List searchExternalDataObjects(String query, int star limit = LIMIT_DEFAULT; } - // OpenAIRE uses pages and first page starts with 1 + // Openaire uses pages and first page starts with 1 int page = (start / limit) + 1; // escaping query @@ -148,7 +148,7 @@ public List searchExternalDataObjects(String query, int star if (projects.size() > 0) { return projects.stream() - .map(project -> new OpenAIREFundingDataProvider + .map(project -> new OpenaireFundingDataProvider .ExternalDataObjectBuilder(project) .setId(generateProjectURI(project)) .setSource(sourceIdentifier) @@ -176,24 +176,24 @@ public int getNumberOfResults(String query) { * Generic setter for the sourceIdentifier * * @param sourceIdentifier The sourceIdentifier to be set on this - * OpenAIREFunderDataProvider + * OpenaireFunderDataProvider */ @Autowired(required = true) public void setSourceIdentifier(String sourceIdentifier) { this.sourceIdentifier = sourceIdentifier; } - public OpenAIRERestConnector getConnector() { + public OpenaireRestConnector getConnector() { return connector; } /** - * Generic setter for OpenAIRERestConnector + * Generic setter for OpenaireRestConnector * * @param connector */ @Autowired(required = true) - public void setConnector(OpenAIRERestConnector connector) { + public void setConnector(OpenaireRestConnector connector) { this.connector = connector; } @@ -219,7 +219,7 @@ private static boolean isValidProjectURI(String projectURI) { } /** - * This method returns an URI based on OpenAIRE 3.0 guidelines + * This method returns an URI based on Openaire 3.0 guidelines * https://guidelines.openaire.eu/en/latest/literature/field_projectid.html that * can be used as an ID if is there any missing part, that part it will be * replaced by the character '+' @@ -281,7 +281,7 @@ public void setMetadataFields(Map metadataFields) { } /** - * OpenAIRE Funding External Data Builder Class + * Openaire Funding External Data Builder Class * * @author pgraca */ diff --git a/dspace-api/src/main/java/org/dspace/qaevent/QAEventsDeleteCascadeConsumer.java b/dspace-api/src/main/java/org/dspace/qaevent/QAEventsDeleteCascadeConsumer.java index 68976430e626..6460c360ecbe 100644 --- a/dspace-api/src/main/java/org/dspace/qaevent/QAEventsDeleteCascadeConsumer.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/QAEventsDeleteCascadeConsumer.java @@ -16,7 +16,7 @@ import org.dspace.utils.DSpace; /** - * Consumer to delete qaevents once the target item is deleted + * Consumer to delete qaevents from solr due to the target item deletion * * @author Andrea Bollini (andrea.bollini at 4science.it) * diff --git a/dspace-api/src/main/java/org/dspace/qaevent/QASource.java b/dspace-api/src/main/java/org/dspace/qaevent/QASource.java index b3f7be5f5221..e22f7d32a770 100644 --- a/dspace-api/src/main/java/org/dspace/qaevent/QASource.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/QASource.java @@ -10,7 +10,7 @@ import java.util.Date; /** - * This model class represent the source/provider of the QA events (as OpenAIRE). + * This model class represent the source/provider of the QA events (as Openaire). * * @author Luca Giamminonni (luca.giamminonni at 4Science) * diff --git a/dspace-api/src/main/java/org/dspace/qaevent/dao/QAEventsDao.java b/dspace-api/src/main/java/org/dspace/qaevent/dao/QAEventsDAO.java similarity index 98% rename from dspace-api/src/main/java/org/dspace/qaevent/dao/QAEventsDao.java rename to dspace-api/src/main/java/org/dspace/qaevent/dao/QAEventsDAO.java index 9de2d124602c..98c38ca3f5a9 100644 --- a/dspace-api/src/main/java/org/dspace/qaevent/dao/QAEventsDao.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/dao/QAEventsDAO.java @@ -22,7 +22,7 @@ * @author Andrea Bollini (andrea.bollini at 4science.it) * */ -public interface QAEventsDao extends GenericDAO { +public interface QAEventsDAO extends GenericDAO { /** * Returns all the stored QAEventProcessed entities. diff --git a/dspace-api/src/main/java/org/dspace/qaevent/dao/impl/QAEventsDaoImpl.java b/dspace-api/src/main/java/org/dspace/qaevent/dao/impl/QAEventsDAOImpl.java similarity index 89% rename from dspace-api/src/main/java/org/dspace/qaevent/dao/impl/QAEventsDaoImpl.java rename to dspace-api/src/main/java/org/dspace/qaevent/dao/impl/QAEventsDAOImpl.java index 3263ae9de9b7..ac9b96045e42 100644 --- a/dspace-api/src/main/java/org/dspace/qaevent/dao/impl/QAEventsDaoImpl.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/dao/impl/QAEventsDAOImpl.java @@ -17,16 +17,16 @@ import org.dspace.core.AbstractHibernateDAO; import org.dspace.core.Context; import org.dspace.eperson.EPerson; -import org.dspace.qaevent.dao.QAEventsDao; +import org.dspace.qaevent.dao.QAEventsDAO; /** - * Implementation of {@link QAEventsDao} that store processed events using an + * Implementation of {@link QAEventsDAO} that store processed events using an * SQL DBMS. * * @author Andrea Bollini (andrea.bollini at 4science.it) * */ -public class QAEventsDaoImpl extends AbstractHibernateDAO implements QAEventsDao { +public class QAEventsDAOImpl extends AbstractHibernateDAO implements QAEventsDAO { @Override public List findAll(Context context) throws SQLException { @@ -60,7 +60,7 @@ public boolean isEventStored(Context context, String checksum) throws SQLExcepti public List searchByEventId(Context context, String eventId, Integer start, Integer size) throws SQLException { Query query = createQuery(context, - "SELECT * " + "FROM QAEventProcessed qaevent WHERE qaevent.qaevent_id = :event_id "); + "SELECT * FROM QAEventProcessed qaevent WHERE qaevent.qaevent_id = :event_id "); query.setFirstResult(start); query.setMaxResults(size); query.setParameter("event_id", eventId); diff --git a/dspace-api/src/main/java/org/dspace/qaevent/script/OpenaireEventsImport.java b/dspace-api/src/main/java/org/dspace/qaevent/script/OpenaireEventsImport.java index e45dc3e159c7..8bd864d7da9f 100644 --- a/dspace-api/src/main/java/org/dspace/qaevent/script/OpenaireEventsImport.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/script/OpenaireEventsImport.java @@ -42,7 +42,9 @@ /** * Implementation of {@link DSpaceRunnable} to perfom a QAEvents import from a * json file. The JSON file contains an array of JSON Events, where each event - * has the following structure + * has the following structure. The message attribute follows the structure + * documented at + * @see see * *
    * {
    diff --git a/dspace-api/src/main/java/org/dspace/qaevent/script/OpenaireEventsImportScriptConfiguration.java b/dspace-api/src/main/java/org/dspace/qaevent/script/OpenaireEventsImportScriptConfiguration.java index 14737de6350b..60001e73507d 100644 --- a/dspace-api/src/main/java/org/dspace/qaevent/script/OpenaireEventsImportScriptConfiguration.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/script/OpenaireEventsImportScriptConfiguration.java @@ -54,12 +54,12 @@ public Options getOptions() { if (options == null) { Options options = new Options(); - options.addOption("f", "file", true, "Import data from OpenAIRE quality assurance broker JSON file." + options.addOption("f", "file", true, "Import data from Openaire quality assurance broker JSON file." + " This parameter is mutually exclusive to the email parameter."); options.getOption("f").setType(InputStream.class); options.getOption("f").setRequired(false); - options.addOption("e", "email", true, "Email related to the subscriptions to import data from OpenAIRE " + options.addOption("e", "email", true, "Email related to the subscriptions to import data from Openaire " + "broker. This parameter is mutually exclusive to the file parameter."); options.getOption("e").setType(String.class); options.getOption("e").setRequired(false); diff --git a/dspace-api/src/main/java/org/dspace/qaevent/service/dto/OpenaireMessageDTO.java b/dspace-api/src/main/java/org/dspace/qaevent/service/dto/OpenaireMessageDTO.java index 117b764ca02f..59b4acf9db21 100644 --- a/dspace-api/src/main/java/org/dspace/qaevent/service/dto/OpenaireMessageDTO.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/service/dto/OpenaireMessageDTO.java @@ -11,7 +11,7 @@ /** * Implementation of {@link QAMessageDTO} that model message coming from OPENAIRE. - * + * @see see * @author Luca Giamminonni (luca.giamminonni at 4science.it) * */ diff --git a/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventServiceImpl.java b/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventServiceImpl.java index bbb6990bb6b3..9be4af2d0871 100644 --- a/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventServiceImpl.java @@ -43,8 +43,8 @@ import org.dspace.handle.service.HandleService; import org.dspace.qaevent.QASource; import org.dspace.qaevent.QATopic; -import org.dspace.qaevent.dao.QAEventsDao; -import org.dspace.qaevent.dao.impl.QAEventsDaoImpl; +import org.dspace.qaevent.dao.QAEventsDAO; +import org.dspace.qaevent.dao.impl.QAEventsDAOImpl; import org.dspace.qaevent.service.QAEventService; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; @@ -54,7 +54,7 @@ * Implementation of {@link QAEventService} that use Solr to store events. When * the user performs an action on the event (such as accepting the suggestion or * rejecting it) then the event is removed from solr and saved in the database - * (see {@link QAEventsDao}) so that it is no longer proposed. + * (see {@link QAEventsDAO}) so that it is no longer proposed. * * @author Andrea Bollini (andrea.bollini at 4science.it) * @@ -71,7 +71,7 @@ public class QAEventServiceImpl implements QAEventService { private HandleService handleService; @Autowired - private QAEventsDaoImpl qaEventsDao; + private QAEventsDAOImpl qaEventsDao; private ObjectMapper jsonMapper; diff --git a/dspace-api/src/test/data/dspaceFolder/config/spring/api/external-openaire.xml b/dspace-api/src/test/data/dspaceFolder/config/spring/api/external-openaire.xml index f1e6c30d1398..8a5277ab2dac 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/spring/api/external-openaire.xml +++ b/dspace-api/src/test/data/dspaceFolder/config/spring/api/external-openaire.xml @@ -7,7 +7,7 @@ http://www.springframework.org/schema/util/spring-util.xsd" default-lazy-init="true"> - + @@ -15,10 +15,10 @@ - - + + diff --git a/dspace-api/src/test/data/dspaceFolder/config/spring/api/item-filters.xml b/dspace-api/src/test/data/dspaceFolder/config/spring/api/item-filters.xml index 836d4f089677..8bae32eaefd7 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/spring/api/item-filters.xml +++ b/dspace-api/src/test/data/dspaceFolder/config/spring/api/item-filters.xml @@ -286,7 +286,7 @@ - @@ -329,7 +329,7 @@ - diff --git a/dspace-api/src/test/data/dspaceFolder/config/spring/api/scripts.xml b/dspace-api/src/test/data/dspaceFolder/config/spring/api/scripts.xml index b80330564fa0..a197b2910bd6 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/spring/api/scripts.xml +++ b/dspace-api/src/test/data/dspaceFolder/config/spring/api/scripts.xml @@ -66,7 +66,7 @@ - + diff --git a/dspace-api/src/test/java/org/dspace/content/CollectionTest.java b/dspace-api/src/test/java/org/dspace/content/CollectionTest.java index 13d037abf823..a177571ffa46 100644 --- a/dspace-api/src/test/java/org/dspace/content/CollectionTest.java +++ b/dspace-api/src/test/java/org/dspace/content/CollectionTest.java @@ -1200,4 +1200,71 @@ public void testGetParentObject() throws SQLException { equalTo(owningCommunity)); } + /** + * Test of retrieveCollectionWithSubmitByEntityType method getting the closest + * collection of non-item type starting from an item + */ + @Test + public void testRetrieveCollectionWithSubmitByEntityType() throws SQLException, AuthorizeException { + context.setDispatcher("default"); + context.turnOffAuthorisationSystem(); + Community com = communityService.create(null, context); + Group submitters = groupService.create(context); + Collection collection = collectionService.create(context, com); + collectionService.addMetadata(context, collection, "dspace", "entity", "type", + null, "Publication"); + com.addCollection(collection); + WorkspaceItem workspaceItem = workspaceItemService.create(context, collection, false); + Item item = installItemService.installItem(context, workspaceItem); + EPerson epersonA = ePersonService.create(context); + Collection collectionPerson = collectionService.create(context, com); + collectionService.addMetadata(context, collectionPerson, "dspace", "entity", "type", + null, "Person"); + collectionPerson.setSubmitters(submitters); + groupService.addMember(context, submitters, epersonA); + context.setCurrentUser(epersonA); + context.commit(); + context.restoreAuthSystemState(); + Collection resultCollection = collectionService.retrieveCollectionWithSubmitByEntityType + (context, item, "Person"); + + assertThat("testRetrieveCollectionWithSubmitByEntityType 0", resultCollection, notNullValue()); + assertThat("testRetrieveCollectionWithSubmitByEntityType 1", resultCollection, equalTo(collectionPerson)); + + context.setDispatcher("exclude-discovery"); + } + + /** + * Test of rretrieveCollectionWithSubmitByCommunityAndEntityType method getting the closest + * collection of non-community type starting from an community + */ + @Test + public void testRetrieveCollectionWithSubmitByCommunityAndEntityType() throws SQLException, AuthorizeException { + context.setDispatcher("default"); + context.turnOffAuthorisationSystem(); + Community com = communityService.create(null, context); + Group submitters = groupService.create(context); + Collection collection = collectionService.create(context, com); + collectionService.addMetadata(context, collection, "dspace", "entity", "type", + null, "Publication"); + com.addCollection(collection); + WorkspaceItem workspaceItem = workspaceItemService.create(context, collection, false); + Item item = installItemService.installItem(context, workspaceItem); + EPerson epersonA = ePersonService.create(context); + Collection collectionPerson = collectionService.create(context, com); + collectionService.addMetadata(context, collectionPerson, "dspace", "entity", "type", + null, "Person"); + collectionPerson.setSubmitters(submitters); + groupService.addMember(context, submitters, epersonA); + context.setCurrentUser(epersonA); + context.commit(); + context.restoreAuthSystemState(); + Collection resultCollection = collectionService.retrieveCollectionWithSubmitByCommunityAndEntityType + (context, com, "Person"); + + assertThat("testRetrieveCollectionWithSubmitByEntityType 0", resultCollection, notNullValue()); + assertThat("testRetrieveCollectionWithSubmitByEntityType 1", resultCollection, equalTo(collectionPerson)); + + context.setDispatcher("exclude-discovery"); + } } diff --git a/dspace-api/src/test/java/org/dspace/external/MockOpenAIRERestConnector.java b/dspace-api/src/test/java/org/dspace/external/MockOpenaireRestConnector.java similarity index 90% rename from dspace-api/src/test/java/org/dspace/external/MockOpenAIRERestConnector.java rename to dspace-api/src/test/java/org/dspace/external/MockOpenaireRestConnector.java index bac80e196cf1..c67402dfdcc6 100644 --- a/dspace-api/src/test/java/org/dspace/external/MockOpenAIRERestConnector.java +++ b/dspace-api/src/test/java/org/dspace/external/MockOpenaireRestConnector.java @@ -14,15 +14,15 @@ import eu.openaire.jaxb.model.Response; /** - * Mock the OpenAIRE rest connector for unit testing
    + * Mock the Openaire rest connector for unit testing
    * will be resolved against static test xml files * * @author pgraca * */ -public class MockOpenAIRERestConnector extends OpenAIRERestConnector { +public class MockOpenaireRestConnector extends OpenaireRestConnector { - public MockOpenAIRERestConnector(String url) { + public MockOpenaireRestConnector(String url) { super(url); } diff --git a/dspace-api/src/test/java/org/dspace/external/provider/impl/OpenAIREFundingDataProviderTest.java b/dspace-api/src/test/java/org/dspace/external/provider/impl/OpenaireFundingDataProviderTest.java similarity index 59% rename from dspace-api/src/test/java/org/dspace/external/provider/impl/OpenAIREFundingDataProviderTest.java rename to dspace-api/src/test/java/org/dspace/external/provider/impl/OpenaireFundingDataProviderTest.java index 5e96f06ac8ae..d14dc990353d 100644 --- a/dspace-api/src/test/java/org/dspace/external/provider/impl/OpenAIREFundingDataProviderTest.java +++ b/dspace-api/src/test/java/org/dspace/external/provider/impl/OpenaireFundingDataProviderTest.java @@ -23,15 +23,15 @@ import org.junit.Test; /** - * Unit tests for OpenAIREFundingDataProvider + * Unit tests for OpenaireFundingDataProvider * * @author pgraca * */ -public class OpenAIREFundingDataProviderTest extends AbstractDSpaceTest { +public class OpenaireFundingDataProviderTest extends AbstractDSpaceTest { ExternalDataService externalDataService; - ExternalDataProvider openAIREFundingDataProvider; + ExternalDataProvider openaireFundingDataProvider; /** * This method will be run before every test as per @Before. It will initialize @@ -44,38 +44,38 @@ public class OpenAIREFundingDataProviderTest extends AbstractDSpaceTest { public void init() { // Set up External Service Factory and set data providers externalDataService = ExternalServiceFactory.getInstance().getExternalDataService(); - openAIREFundingDataProvider = externalDataService.getExternalDataProvider("openAIREFunding"); + openaireFundingDataProvider = externalDataService.getExternalDataProvider("openaireFunding"); } @Test public void testNumberOfResultsWSingleKeyword() { - assertNotNull("openAIREFundingDataProvider is not null", openAIREFundingDataProvider); - assertEquals("openAIREFunding.numberOfResults.query:mock", 77, - openAIREFundingDataProvider.getNumberOfResults("mock")); + assertNotNull("openaireFundingDataProvider is not null", openaireFundingDataProvider); + assertEquals("openaireFunding.numberOfResults.query:mock", 77, + openaireFundingDataProvider.getNumberOfResults("mock")); } @Test public void testNumberOfResultsWKeywords() { - assertNotNull("openAIREFundingDataProvider is not null", openAIREFundingDataProvider); - assertEquals("openAIREFunding.numberOfResults.query:mock+test", 77, - openAIREFundingDataProvider.getNumberOfResults("mock+test")); + assertNotNull("openaireFundingDataProvider is not null", openaireFundingDataProvider); + assertEquals("openaireFunding.numberOfResults.query:mock+test", 77, + openaireFundingDataProvider.getNumberOfResults("mock+test")); } @Test public void testQueryResultsWSingleKeyword() { - assertNotNull("openAIREFundingDataProvider is not null", openAIREFundingDataProvider); - List results = openAIREFundingDataProvider.searchExternalDataObjects("mock", 0, 10); - assertEquals("openAIREFunding.searchExternalDataObjects.size", 10, results.size()); + assertNotNull("openaireFundingDataProvider is not null", openaireFundingDataProvider); + List results = openaireFundingDataProvider.searchExternalDataObjects("mock", 0, 10); + assertEquals("openaireFunding.searchExternalDataObjects.size", 10, results.size()); } @Test public void testQueryResultsWKeywords() { String value = "Mushroom Robo-Pic - Development of an autonomous robotic mushroom picking system"; - assertNotNull("openAIREFundingDataProvider is not null", openAIREFundingDataProvider); - List results = openAIREFundingDataProvider.searchExternalDataObjects("mock+test", 0, 10); - assertEquals("openAIREFunding.searchExternalDataObjects.size", 10, results.size()); - assertTrue("openAIREFunding.searchExternalDataObjects.first.value", value.equals(results.get(0).getValue())); + assertNotNull("openaireFundingDataProvider is not null", openaireFundingDataProvider); + List results = openaireFundingDataProvider.searchExternalDataObjects("mock+test", 0, 10); + assertEquals("openaireFunding.searchExternalDataObjects.size", 10, results.size()); + assertTrue("openaireFunding.searchExternalDataObjects.first.value", value.equals(results.get(0).getValue())); } @Test @@ -84,22 +84,22 @@ public void testGetDataObject() { String value = "Portuguese Wild Mushrooms: Chemical characterization and functional study" + " of antiproliferative and proapoptotic properties in cancer cell lines"; - assertNotNull("openAIREFundingDataProvider is not null", openAIREFundingDataProvider); + assertNotNull("openaireFundingDataProvider is not null", openaireFundingDataProvider); - Optional result = openAIREFundingDataProvider.getExternalDataObject(id); + Optional result = openaireFundingDataProvider.getExternalDataObject(id); - assertTrue("openAIREFunding.getExternalDataObject.exists", result.isPresent()); - assertTrue("openAIREFunding.getExternalDataObject.value", value.equals(result.get().getValue())); + assertTrue("openaireFunding.getExternalDataObject.exists", result.isPresent()); + assertTrue("openaireFunding.getExternalDataObject.value", value.equals(result.get().getValue())); } @Test public void testGetDataObjectWInvalidId() { String id = "WRONGID"; - assertNotNull("openAIREFundingDataProvider is not null", openAIREFundingDataProvider); + assertNotNull("openaireFundingDataProvider is not null", openaireFundingDataProvider); - Optional result = openAIREFundingDataProvider.getExternalDataObject(id); + Optional result = openaireFundingDataProvider.getExternalDataObject(id); - assertTrue("openAIREFunding.getExternalDataObject.notExists:WRONGID", result.isEmpty()); + assertTrue("openaireFunding.getExternalDataObject.notExists:WRONGID", result.isEmpty()); } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/RestResourceController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/RestResourceController.java index e1b7cc89cf6b..03bea35597ba 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/RestResourceController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/RestResourceController.java @@ -1093,6 +1093,17 @@ private String getEncodedParameterStringFromRequestParams( return uriComponentsBuilder.encode().build().toString(); } + /** + * Method to delete an entity by ID + * Note that the regular expression in the request mapping accept a number as identifier; + * + * @param request + * @param apiCategory + * @param model + * @param id + * @return + * @throws HttpRequestMethodNotSupportedException + */ @RequestMapping(method = RequestMethod.DELETE, value = REGEX_REQUESTMAPPING_IDENTIFIER_AS_DIGIT) public ResponseEntity> delete(HttpServletRequest request, @PathVariable String apiCategory, @PathVariable String model, @PathVariable Integer id) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventRestRepository.java index bd9b31e14cad..dc0654ee494a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QAEventRestRepository.java @@ -23,7 +23,7 @@ import org.dspace.content.service.ItemService; import org.dspace.core.Context; import org.dspace.eperson.EPerson; -import org.dspace.qaevent.dao.QAEventsDao; +import org.dspace.qaevent.dao.QAEventsDAO; import org.dspace.qaevent.service.QAEventService; import org.dspace.util.UUIDUtils; import org.springframework.beans.factory.annotation.Autowired; @@ -48,7 +48,7 @@ public class QAEventRestRepository extends DSpaceRestRepository findByTopic(Context context, @Parameter(value = "topic" } @Override + @PreAuthorize("hasAuthority('ADMIN')") protected void delete(Context context, String eventId) throws AuthorizeException { Item item = findTargetItem(context, eventId); EPerson eperson = context.getCurrentUser(); diff --git a/dspace-server-webapp/src/test/data/dspaceFolder/config/item-submission.xml b/dspace-server-webapp/src/test/data/dspaceFolder/config/item-submission.xml index eca9acf79fd7..e42b7386abe3 100644 --- a/dspace-server-webapp/src/test/data/dspaceFolder/config/item-submission.xml +++ b/dspace-server-webapp/src/test/data/dspaceFolder/config/item-submission.xml @@ -135,28 +135,28 @@ fake.workflow.readonly org.dspace.submit.step.SampleStep sample workflow --> - - + + submit.progressbar.describe.stepone org.dspace.app.rest.submit.step.DescribeStep submission-form - + submit.progressbar.describe.stepone org.dspace.app.rest.submit.step.DescribeStep submission-form - + submit.progressbar.describe.stepone org.dspace.app.rest.submit.step.DescribeStep submission-form - + submit.progressbar.describe.stepone org.dspace.app.rest.submit.step.DescribeStep submission-form - + submit.progressbar.describe.stepone org.dspace.app.rest.submit.step.DescribeStep submission-form @@ -259,13 +259,13 @@ - - + + - - + + @@ -274,17 +274,17 @@ - + - + - + - + - + - + diff --git a/dspace-server-webapp/src/test/data/dspaceFolder/config/spring/api/external-openaire.xml b/dspace-server-webapp/src/test/data/dspaceFolder/config/spring/api/external-openaire.xml index 9db02440e4c3..b045865fe07a 100644 --- a/dspace-server-webapp/src/test/data/dspaceFolder/config/spring/api/external-openaire.xml +++ b/dspace-server-webapp/src/test/data/dspaceFolder/config/spring/api/external-openaire.xml @@ -4,8 +4,8 @@ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd" default-lazy-init="true"> - + - - + + Project diff --git a/dspace-server-webapp/src/test/data/dspaceFolder/config/spring/api/test-discovery.xml b/dspace-server-webapp/src/test/data/dspaceFolder/config/spring/api/test-discovery.xml index 4a91ef051e88..ea654ac55cd4 100644 --- a/dspace-server-webapp/src/test/data/dspaceFolder/config/spring/api/test-discovery.xml +++ b/dspace-server-webapp/src/test/data/dspaceFolder/config/spring/api/test-discovery.xml @@ -86,8 +86,8 @@ - - + + diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryVersioningIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryVersioningIT.java index 083b27d0e547..7b9cad70e91f 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryVersioningIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryVersioningIT.java @@ -1057,8 +1057,8 @@ public void test_discoveryXml_personOrOrgunit_expectLatestVersionsOnly() throws } @Test - public void test_discoveryXml_openAIREFundingAgency_expectLatestVersionsOnly() throws Exception { - final String configuration = "openAIREFundingAgency"; + public void test_discoveryXml_openaireFundingAgency_expectLatestVersionsOnly() throws Exception { + final String configuration = "openaireFundingAgency"; Collection collection = createCollection("OrgUnit"); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ExternalSourcesRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ExternalSourcesRestControllerIT.java index 65492cbbe869..2b03b50c559c 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ExternalSourcesRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ExternalSourcesRestControllerIT.java @@ -51,7 +51,7 @@ public void findAllExternalSources() throws Exception { ExternalSourceMatcher.matchExternalSource( "pubmed", "pubmed", false), ExternalSourceMatcher.matchExternalSource( - "openAIREFunding", "openAIREFunding", false) + "openaireFunding", "openaireFunding", false) ))) .andExpect(jsonPath("$.page.totalElements", Matchers.is(10))); } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/OpenAIREFundingExternalSourcesIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/OpenaireFundingExternalSourcesIT.java similarity index 83% rename from dspace-server-webapp/src/test/java/org/dspace/app/rest/OpenAIREFundingExternalSourcesIT.java rename to dspace-server-webapp/src/test/java/org/dspace/app/rest/OpenaireFundingExternalSourcesIT.java index b8f886b2e42b..8c76c7adea0e 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/OpenAIREFundingExternalSourcesIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/OpenaireFundingExternalSourcesIT.java @@ -19,7 +19,7 @@ import org.hamcrest.Matchers; import org.junit.Test; -public class OpenAIREFundingExternalSourcesIT extends AbstractControllerIntegrationTest { +public class OpenaireFundingExternalSourcesIT extends AbstractControllerIntegrationTest { /** * Test openaire funding external source @@ -27,10 +27,10 @@ public class OpenAIREFundingExternalSourcesIT extends AbstractControllerIntegrat * @throws Exception */ @Test - public void findOneOpenAIREFundingExternalSourceTest() throws Exception { + public void findOneOpenaireFundingExternalSourceTest() throws Exception { getClient().perform(get("/api/integration/externalsources")).andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.externalsources", Matchers.hasItem( - ExternalSourceMatcher.matchExternalSource("openAIREFunding", "openAIREFunding", false)))); + ExternalSourceMatcher.matchExternalSource("openaireFunding", "openaireFunding", false)))); } /** @@ -39,9 +39,9 @@ public void findOneOpenAIREFundingExternalSourceTest() throws Exception { * @throws Exception */ @Test - public void findOneOpenAIREFundingExternalSourceEntriesEmptyWithQueryTest() throws Exception { + public void findOneOpenaireFundingExternalSourceEntriesEmptyWithQueryTest() throws Exception { - getClient().perform(get("/api/integration/externalsources/openAIREFunding/entries").param("query", "empty")) + getClient().perform(get("/api/integration/externalsources/openaireFunding/entries").param("query", "empty")) .andExpect(status().isOk()).andExpect(jsonPath("$.page.number", is(0))); } @@ -52,11 +52,11 @@ public void findOneOpenAIREFundingExternalSourceEntriesEmptyWithQueryTest() thro * @throws Exception */ @Test - public void findOneOpenAIREFundingExternalSourceEntriesWithQueryMultipleKeywordsTest() throws Exception { + public void findOneOpenaireFundingExternalSourceEntriesWithQueryMultipleKeywordsTest() throws Exception { getClient() .perform( - get("/api/integration/externalsources/openAIREFunding/entries").param("query", "empty+results")) + get("/api/integration/externalsources/openaireFunding/entries").param("query", "empty+results")) .andExpect(status().isOk()).andExpect(jsonPath("$.page.number", is(0))); } @@ -66,14 +66,14 @@ public void findOneOpenAIREFundingExternalSourceEntriesWithQueryMultipleKeywords * @throws Exception */ @Test - public void findOneOpenAIREFundingExternalSourceEntriesWithQueryTest() throws Exception { - getClient().perform(get("/api/integration/externalsources/openAIREFunding/entries").param("query", "mushroom")) + public void findOneOpenaireFundingExternalSourceEntriesWithQueryTest() throws Exception { + getClient().perform(get("/api/integration/externalsources/openaireFunding/entries").param("query", "mushroom")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.externalSourceEntries", Matchers.hasItem(ExternalSourceEntryMatcher.matchExternalSourceEntry( "aW5mbzpldS1yZXBvL2dyYW50QWdyZWVtZW50L05XTy8rLzIzMDAxNDc3MjgvTkw=", "Master switches of initiation of mushroom formation", - "Master switches of initiation of mushroom formation", "openAIREFunding")))); + "Master switches of initiation of mushroom formation", "openaireFunding")))); } @@ -83,19 +83,19 @@ public void findOneOpenAIREFundingExternalSourceEntriesWithQueryTest() throws Ex * @throws Exception */ @Test - public void findOneOpenAIREFundingExternalSourceEntryValueTest() throws Exception { + public void findOneOpenaireFundingExternalSourceEntryValueTest() throws Exception { // "info:eu-repo/grantAgreement/mock/mock/mock/mock" base64 encoded String projectID = "aW5mbzpldS1yZXBvL2dyYW50QWdyZWVtZW50L0ZDVC81ODc2LVBQQ0RUSS8xMTAwNjIvUFQ="; String projectName = "Portuguese Wild Mushrooms: Chemical characterization and functional study" + " of antiproliferative and proapoptotic properties in cancer cell lines"; - getClient().perform(get("/api/integration/externalsources/openAIREFunding/entryValues/" + projectID)) + getClient().perform(get("/api/integration/externalsources/openaireFunding/entryValues/" + projectID)) .andExpect(status().isOk()) .andExpect(jsonPath("$", Matchers.allOf(hasJsonPath("$.id", is(projectID)), hasJsonPath("$.display", is(projectName)), hasJsonPath("$.value", is(projectName)), - hasJsonPath("$.externalSource", is("openAIREFunding")), + hasJsonPath("$.externalSource", is("openaireFunding")), hasJsonPath("$.type", is("externalSourceEntry"))))); } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/QAEventRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/QAEventRestRepositoryIT.java index dc021f2904a1..592b65945767 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/QAEventRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/QAEventRestRepositoryIT.java @@ -44,7 +44,7 @@ import org.dspace.content.Item; import org.dspace.content.QAEvent; import org.dspace.content.QAEventProcessed; -import org.dspace.qaevent.dao.QAEventsDao; +import org.dspace.qaevent.dao.QAEventsDAO; import org.hamcrest.Matchers; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -58,7 +58,7 @@ public class QAEventRestRepositoryIT extends AbstractControllerIntegrationTest { @Autowired - private QAEventsDao qaEventsDao; + private QAEventsDAO qaEventsDao; @Test public void findAllNotImplementedTest() throws Exception { @@ -759,6 +759,11 @@ public void testEventDeletion() throws Exception { .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144300\"}") .build(); + QAEvent event2 = QAEventBuilder.createTarget(context, col1, "Science and Freedom") + .withTopic("ENRICH/MISSING/PID") + .withMessage("{\"pids[0].type\":\"doi\",\"pids[0].value\":\"10.2307/2144300\"}") + .build(); + context.restoreAuthSystemState(); String authToken = getAuthToken(admin.getEmail(), password); @@ -786,5 +791,11 @@ public void testEventDeletion() throws Exception { assertThat(processedEvent.getEventTimestamp(), notNullValue()); assertThat(processedEvent.getEperson().getID(), is(admin.getID())); + getClient(authToken).perform(delete("/api/integration/qualityassuranceevents/" + event.getEventId())) + .andExpect(status().isInternalServerError()); + + authToken = getAuthToken(eperson.getEmail(), password); + getClient(authToken).perform(delete("/api/integration/qualityassuranceevents/" + event2.getEventId())) + .andExpect(status().isForbidden()); } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/external/provider/impl/MockOpenAIREFundingDataProvider.java b/dspace-server-webapp/src/test/java/org/dspace/external/provider/impl/MockOpenaireFundingDataProvider.java similarity index 91% rename from dspace-server-webapp/src/test/java/org/dspace/external/provider/impl/MockOpenAIREFundingDataProvider.java rename to dspace-server-webapp/src/test/java/org/dspace/external/provider/impl/MockOpenaireFundingDataProvider.java index baf1041ab5c8..34f1ae16c74c 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/external/provider/impl/MockOpenAIREFundingDataProvider.java +++ b/dspace-server-webapp/src/test/java/org/dspace/external/provider/impl/MockOpenaireFundingDataProvider.java @@ -14,7 +14,7 @@ import eu.openaire.jaxb.helper.OpenAIREHandler; import eu.openaire.jaxb.model.Response; -import org.dspace.external.OpenAIRERestConnector; +import org.dspace.external.OpenaireRestConnector; import org.mockito.AdditionalMatchers; import org.mockito.ArgumentMatchers; import org.mockito.Mockito; @@ -22,14 +22,14 @@ import org.mockito.stubbing.Answer; /** - * Mock the OpenAIRE external source using a mock rest connector so that query + * Mock the Openaire external source using a mock rest connector so that query * will be resolved against static test files * */ -public class MockOpenAIREFundingDataProvider extends OpenAIREFundingDataProvider { +public class MockOpenaireFundingDataProvider extends OpenaireFundingDataProvider { @Override public void init() throws IOException { - OpenAIRERestConnector restConnector = Mockito.mock(OpenAIRERestConnector.class); + OpenaireRestConnector restConnector = Mockito.mock(OpenaireRestConnector.class); when(restConnector.searchProjectByKeywords(ArgumentMatchers.anyInt(), ArgumentMatchers.anyInt(), ArgumentMatchers.startsWith("mushroom"))).thenAnswer(new Answer() { diff --git a/dspace/config/crosswalks/oai/transformers/openaire4.xsl b/dspace/config/crosswalks/oai/transformers/openaire4.xsl index cece890450b7..8ac703609d6f 100644 --- a/dspace/config/crosswalks/oai/transformers/openaire4.xsl +++ b/dspace/config/crosswalks/oai/transformers/openaire4.xsl @@ -1,5 +1,5 @@ - + @@ -12,7 +12,7 @@ - + - + @@ -66,12 +66,12 @@ - This contexts complies with OpenAIRE Guidelines for Literature Repositories v3.0. + This contexts complies with Openaire Guidelines for Literature Repositories v3.0. - + - - + + - This contexts complies with OpenAIRE Guidelines for Literature Repositories v4.0. + This contexts complies with Openaire Guidelines for Literature Repositories v4.0. @@ -176,7 +176,7 @@ http://irdb.nii.ac.jp/oai http://irdb.nii.ac.jp/oai/junii2-3-1.xsd - @@ -250,13 +250,13 @@ - @@ -319,7 +319,7 @@ - - + @@ -457,7 +457,7 @@ + OR "openAccess", which is required by Openaire. --> org.dspace.xoai.filter.DSpaceAtLeastOneMetadataFilter @@ -483,7 +483,7 @@ + which specifies the openaire project ID. --> org.dspace.xoai.filter.DSpaceAtLeastOneMetadataFilter @@ -526,7 +526,7 @@ openaire - OpenAIRE + Openaire diff --git a/dspace/config/item-submission.xml b/dspace/config/item-submission.xml index 1060a3303119..e5175c3c4e21 100644 --- a/dspace/config/item-submission.xml +++ b/dspace/config/item-submission.xml @@ -185,28 +185,28 @@ extract - - + + submit.progressbar.describe.stepone org.dspace.app.rest.submit.step.DescribeStep submission-form - + submit.progressbar.describe.stepone org.dspace.app.rest.submit.step.DescribeStep submission-form - + submit.progressbar.describe.stepone org.dspace.app.rest.submit.step.DescribeStep submission-form - + submit.progressbar.describe.stepone org.dspace.app.rest.submit.step.DescribeStep submission-form - + submit.progressbar.describe.stepone org.dspace.app.rest.submit.step.DescribeStep submission-form @@ -361,13 +361,13 @@ - - + + - - + + @@ -376,17 +376,17 @@ - + - + - + - + - + - + diff --git a/dspace/config/modules/openaire-client.cfg b/dspace/config/modules/openaire-client.cfg index ded99d0120ef..b43ef781ee47 100644 --- a/dspace/config/modules/openaire-client.cfg +++ b/dspace/config/modules/openaire-client.cfg @@ -16,20 +16,20 @@ # The accessToken it only has a validity of one hour # For more details about the token, please check: https://develop.openaire.eu/personalToken.html # -# the current OpenAIRE Rest client implementation uses basic authentication +# the current Openaire Rest client implementation uses basic authentication # Described here: https://develop.openaire.eu/basic.html # # ---- Token usage required definitions ---- # you can override this settings in your local.cfg file - can be true/false openaire.token.enabled = false -# URL of the OpenAIRE authentication and authorization service +# URL of the Openaire authentication and authorization service openaire.token.url = https://aai.openaire.eu/oidc/token -# you will be required to register at OpenAIRE (https://services.openaire.eu/uoa-user-management/registeredServices) +# you will be required to register at Openaire (https://services.openaire.eu/uoa-user-management/registeredServices) # and create your service in order to get the following data: openaire.token.clientId = CLIENT_ID_HERE openaire.token.clientSecret = CLIENT_SECRET_HERE -# URL of OpenAIRE Rest API +# URL of Openaire Rest API openaire.api.url = https://api.openaire.eu \ No newline at end of file diff --git a/dspace/config/registries/openaire4-types.xml b/dspace/config/registries/openaire4-types.xml index b3290ac12038..b46802a46faa 100644 --- a/dspace/config/registries/openaire4-types.xml +++ b/dspace/config/registries/openaire4-types.xml @@ -2,13 +2,13 @@ - OpenAIRE4 fields definition + Openaire4 fields definition @@ -108,7 +108,7 @@ datacite @@ -116,7 +116,7 @@ Spatial region or named place where the data was gathered or about which the data is focused. - + datacite subject diff --git a/dspace/config/registries/relationship-formats.xml b/dspace/config/registries/relationship-formats.xml index f2f50182facd..53d15d4d43e6 100644 --- a/dspace/config/registries/relationship-formats.xml +++ b/dspace/config/registries/relationship-formats.xml @@ -237,7 +237,7 @@ Contains all uuids of PUBLICATIONS which link to the current ISSUE via a "latest" relationship. In other words, this stores all relationships pointing to the current ISSUE from any PUBLICATION, implying that the ISSUE is marked as "latest". Internally used by DSpace to support versioning. Do not manually add, remove or edit values. - + relation diff --git a/dspace/config/registries/schema-organization-types.xml b/dspace/config/registries/schema-organization-types.xml index 91ee203ae9b7..0b31851078f4 100644 --- a/dspace/config/registries/schema-organization-types.xml +++ b/dspace/config/registries/schema-organization-types.xml @@ -42,10 +42,10 @@ - + - + organization diff --git a/dspace/config/registries/schema-person-types.xml b/dspace/config/registries/schema-person-types.xml index 3a8f79732d41..0a40060e5101 100644 --- a/dspace/config/registries/schema-person-types.xml +++ b/dspace/config/registries/schema-person-types.xml @@ -17,7 +17,7 @@ --> @@ -29,7 +29,7 @@ @@ -74,7 +74,7 @@ @@ -84,10 +84,10 @@ The organizational or institutional affiliation of the creator - + - + person identifier diff --git a/dspace/config/registries/schema-project-types.xml b/dspace/config/registries/schema-project-types.xml index c2f844d2b269..4da07b6a7948 100644 --- a/dspace/config/registries/schema-project-types.xml +++ b/dspace/config/registries/schema-project-types.xml @@ -18,7 +18,7 @@ --> @@ -29,7 +29,7 @@ diff --git a/dspace/config/spring/api/discovery.xml b/dspace/config/spring/api/discovery.xml index fb25f11598fa..83896feb4550 100644 --- a/dspace/config/spring/api/discovery.xml +++ b/dspace/config/spring/api/discovery.xml @@ -110,8 +110,8 @@ - - + + @@ -2027,7 +2027,7 @@
    - diff --git a/dspace/config/spring/api/external-openaire.xml b/dspace/config/spring/api/external-openaire.xml index 25a23a173900..9800e42bd112 100644 --- a/dspace/config/spring/api/external-openaire.xml +++ b/dspace/config/spring/api/external-openaire.xml @@ -7,7 +7,7 @@ http://www.springframework.org/schema/util/spring-util.xsd" default-lazy-init="true"> - + @@ -15,9 +15,9 @@ - - - + + + diff --git a/dspace/config/spring/api/item-filters.xml b/dspace/config/spring/api/item-filters.xml index 24c463fb531e..1460c19fe423 100644 --- a/dspace/config/spring/api/item-filters.xml +++ b/dspace/config/spring/api/item-filters.xml @@ -286,7 +286,7 @@ - @@ -329,7 +329,7 @@ - diff --git a/dspace/config/spring/api/qaevents.xml b/dspace/config/spring/api/qaevents.xml index 25bb282672fa..300a47c8f229 100644 --- a/dspace/config/spring/api/qaevents.xml +++ b/dspace/config/spring/api/qaevents.xml @@ -9,7 +9,7 @@ - + diff --git a/dspace/config/spring/api/scripts.xml b/dspace/config/spring/api/scripts.xml index 8c81ed392b04..fdb22bad48cd 100644 --- a/dspace/config/spring/api/scripts.xml +++ b/dspace/config/spring/api/scripts.xml @@ -5,7 +5,7 @@ http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"> - + diff --git a/dspace/config/spring/rest/scripts.xml b/dspace/config/spring/rest/scripts.xml index c32ea8d40b99..e6a667697d9b 100644 --- a/dspace/config/spring/rest/scripts.xml +++ b/dspace/config/spring/rest/scripts.xml @@ -9,7 +9,7 @@ - + diff --git a/dspace/config/submission-forms.xml b/dspace/config/submission-forms.xml index 39a4778356c0..d888e0990e69 100644 --- a/dspace/config/submission-forms.xml +++ b/dspace/config/submission-forms.xml @@ -819,9 +819,9 @@ - + -

    + dc @@ -1071,7 +1071,7 @@ -
    + dc @@ -1129,7 +1129,7 @@ relation onebox - openAIREFunding + openaireFunding @@ -1182,7 +1182,7 @@
    -
    + person @@ -1246,7 +1246,7 @@
    -
    + dc @@ -1269,7 +1269,7 @@ isFundingAgencyOfProject - openAIREFundingAgency + openaireFundingAgency false false @@ -1302,7 +1302,7 @@ -
    + organization @@ -1537,7 +1537,7 @@ - + @@ -1579,7 +1579,7 @@ - - - - - - - - - - - - - - - - - - - - - From 5f992e0b71c9d3da4fc94c138dd616c2a388e73f Mon Sep 17 00:00:00 2001 From: frabacche Date: Wed, 13 Dec 2023 16:14:36 +0100 Subject: [PATCH 505/510] CST-5249 add openaire to custom BrokerClient instance and factory --- .../dspace/qaevent/script/OpenaireEventsImport.java | 4 ++-- ...erClientFactory.java => OpenaireClientFactory.java} | 6 +++--- ...FactoryImpl.java => OpenaireClientFactoryImpl.java} | 6 +++--- .../dspace/qaevent/script/OpenaireEventsImportIT.java | 10 +++++----- dspace/config/spring/api/qaevents.xml | 2 +- 5 files changed, 14 insertions(+), 14 deletions(-) rename dspace-api/src/main/java/org/dspace/qaevent/service/{BrokerClientFactory.java => OpenaireClientFactory.java} (81%) rename dspace-api/src/main/java/org/dspace/qaevent/service/impl/{BrokerClientFactoryImpl.java => OpenaireClientFactoryImpl.java} (78%) diff --git a/dspace-api/src/main/java/org/dspace/qaevent/script/OpenaireEventsImport.java b/dspace-api/src/main/java/org/dspace/qaevent/script/OpenaireEventsImport.java index 8bd864d7da9f..9087606aa6e5 100644 --- a/dspace-api/src/main/java/org/dspace/qaevent/script/OpenaireEventsImport.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/script/OpenaireEventsImport.java @@ -32,7 +32,7 @@ import org.dspace.core.Context; import org.dspace.eperson.EPerson; import org.dspace.eperson.factory.EPersonServiceFactory; -import org.dspace.qaevent.service.BrokerClientFactory; +import org.dspace.qaevent.service.OpenaireClientFactory; import org.dspace.qaevent.service.QAEventService; import org.dspace.scripts.DSpaceRunnable; import org.dspace.services.ConfigurationService; @@ -105,7 +105,7 @@ public void setup() throws ParseException { qaEventService = new DSpace().getSingletonService(QAEventService.class); configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); - brokerClient = BrokerClientFactory.getInstance().getBrokerClient(); + brokerClient = OpenaireClientFactory.getInstance().getBrokerClient(); topicsToImport = configurationService.getArrayProperty("qaevents.openaire.import.topic"); openaireBrokerURL = getOpenaireBrokerUri(); diff --git a/dspace-api/src/main/java/org/dspace/qaevent/service/BrokerClientFactory.java b/dspace-api/src/main/java/org/dspace/qaevent/service/OpenaireClientFactory.java similarity index 81% rename from dspace-api/src/main/java/org/dspace/qaevent/service/BrokerClientFactory.java rename to dspace-api/src/main/java/org/dspace/qaevent/service/OpenaireClientFactory.java index c17a7a3ff5ae..e7a7be33c1b0 100644 --- a/dspace-api/src/main/java/org/dspace/qaevent/service/BrokerClientFactory.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/service/OpenaireClientFactory.java @@ -16,7 +16,7 @@ * @author Luca Giamminonni (luca.giamminonni at 4science.it) * */ -public interface BrokerClientFactory { +public interface OpenaireClientFactory { /** * Returns an instance of the {@link BrokerClient}. @@ -25,7 +25,7 @@ public interface BrokerClientFactory { */ public BrokerClient getBrokerClient(); - public static BrokerClientFactory getInstance() { - return new DSpace().getServiceManager().getServiceByName("brokerClientFactory", BrokerClientFactory.class); + public static OpenaireClientFactory getInstance() { + return new DSpace().getServiceManager().getServiceByName("openaireClientFactory", OpenaireClientFactory.class); } } diff --git a/dspace-api/src/main/java/org/dspace/qaevent/service/impl/BrokerClientFactoryImpl.java b/dspace-api/src/main/java/org/dspace/qaevent/service/impl/OpenaireClientFactoryImpl.java similarity index 78% rename from dspace-api/src/main/java/org/dspace/qaevent/service/impl/BrokerClientFactoryImpl.java rename to dspace-api/src/main/java/org/dspace/qaevent/service/impl/OpenaireClientFactoryImpl.java index 8d3f2cdaac43..5839f5e8776f 100644 --- a/dspace-api/src/main/java/org/dspace/qaevent/service/impl/BrokerClientFactoryImpl.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/service/impl/OpenaireClientFactoryImpl.java @@ -8,17 +8,17 @@ package org.dspace.qaevent.service.impl; import eu.dnetlib.broker.BrokerClient; -import org.dspace.qaevent.service.BrokerClientFactory; +import org.dspace.qaevent.service.OpenaireClientFactory; import org.springframework.beans.factory.annotation.Autowired; /** - * Implementation of {@link BrokerClientFactory} that returns the instance of + * Implementation of {@link OpenaireClientFactory} that returns the instance of * {@link BrokerClient} managed by the Spring context. * * @author Luca Giamminonni (luca.giamminonni at 4science.it) * */ -public class BrokerClientFactoryImpl implements BrokerClientFactory { +public class OpenaireClientFactoryImpl implements OpenaireClientFactory { @Autowired private BrokerClient brokerClient; diff --git a/dspace-api/src/test/java/org/dspace/qaevent/script/OpenaireEventsImportIT.java b/dspace-api/src/test/java/org/dspace/qaevent/script/OpenaireEventsImportIT.java index dbe44fd2e72f..35d9231ea5bb 100644 --- a/dspace-api/src/test/java/org/dspace/qaevent/script/OpenaireEventsImportIT.java +++ b/dspace-api/src/test/java/org/dspace/qaevent/script/OpenaireEventsImportIT.java @@ -45,9 +45,9 @@ import org.dspace.content.Item; import org.dspace.matcher.QASourceMatcher; import org.dspace.matcher.QATopicMatcher; -import org.dspace.qaevent.service.BrokerClientFactory; +import org.dspace.qaevent.service.OpenaireClientFactory; import org.dspace.qaevent.service.QAEventService; -import org.dspace.qaevent.service.impl.BrokerClientFactoryImpl; +import org.dspace.qaevent.service.impl.OpenaireClientFactoryImpl; import org.dspace.utils.DSpace; import org.junit.After; import org.junit.Before; @@ -67,7 +67,7 @@ public class OpenaireEventsImportIT extends AbstractIntegrationTestWithDatabase private Collection collection; - private BrokerClient brokerClient = BrokerClientFactory.getInstance().getBrokerClient(); + private BrokerClient brokerClient = OpenaireClientFactory.getInstance().getBrokerClient(); private BrokerClient mockBrokerClient = mock(BrokerClient.class); @@ -86,12 +86,12 @@ public void setup() { context.restoreAuthSystemState(); - ((BrokerClientFactoryImpl) BrokerClientFactory.getInstance()).setBrokerClient(mockBrokerClient); + ((OpenaireClientFactoryImpl) OpenaireClientFactory.getInstance()).setBrokerClient(mockBrokerClient); } @After public void after() { - ((BrokerClientFactoryImpl) BrokerClientFactory.getInstance()).setBrokerClient(brokerClient); + ((OpenaireClientFactoryImpl) OpenaireClientFactory.getInstance()).setBrokerClient(brokerClient); } @Test diff --git a/dspace/config/spring/api/qaevents.xml b/dspace/config/spring/api/qaevents.xml index 300a47c8f229..8c52b8b1f2dd 100644 --- a/dspace/config/spring/api/qaevents.xml +++ b/dspace/config/spring/api/qaevents.xml @@ -13,7 +13,7 @@ - + From 95056d509c0e37630560a14cf4536ff85be766c8 Mon Sep 17 00:00:00 2001 From: frabacche Date: Thu, 14 Dec 2023 17:04:16 +0100 Subject: [PATCH 506/510] CST-5249 find Topic order by QAevent.key, means by the topic name --- .../dspace/qaevent/service/QAEventService.java | 7 +++---- .../service/impl/QAEventServiceImpl.java | 10 +++++++--- .../rest/repository/QATopicRestRepository.java | 17 +++++++++++++++-- 3 files changed, 25 insertions(+), 9 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/qaevent/service/QAEventService.java b/dspace-api/src/main/java/org/dspace/qaevent/service/QAEventService.java index ea923251b826..2332a55caf52 100644 --- a/dspace-api/src/main/java/org/dspace/qaevent/service/QAEventService.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/service/QAEventService.java @@ -27,11 +27,9 @@ public interface QAEventService { * Find all the event's topics. * * @param offset the offset to apply - * @param pageSize the page size * @return the topics list */ - public List findAllTopics(long offset, long pageSize); - + public List findAllTopics(long offset, long count, String orderField, boolean ascending); /** * Find all the event's topics related to the given source. * @@ -40,7 +38,8 @@ public interface QAEventService { * @param count the page size * @return the topics list */ - public List findAllTopicsBySource(String source, long offset, long count); + public List findAllTopicsBySource(String source, long offset, long count, + String orderField, boolean ascending); /** * Count all the event's topics. diff --git a/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventServiceImpl.java b/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventServiceImpl.java index 9be4af2d0871..1dfcc1b6d96a 100644 --- a/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/qaevent/service/impl/QAEventServiceImpl.java @@ -36,6 +36,7 @@ import org.apache.solr.common.SolrDocument; import org.apache.solr.common.SolrDocumentList; import org.apache.solr.common.SolrInputDocument; +import org.apache.solr.common.params.FacetParams; import org.dspace.content.Item; import org.dspace.content.QAEvent; import org.dspace.content.service.ItemService; @@ -188,12 +189,13 @@ public QATopic findTopicByTopicId(String topicId) { } @Override - public List findAllTopics(long offset, long count) { - return findAllTopicsBySource(null, offset, count); + public List findAllTopics(long offset, long count, String orderField, boolean ascending) { + return findAllTopicsBySource(null, offset, count, orderField, ascending); } @Override - public List findAllTopicsBySource(String source, long offset, long count) { + public List findAllTopicsBySource(String source, long offset, long count, + String orderField, boolean ascending) { if (source != null && isNotSupportedSource(source)) { return null; @@ -201,6 +203,8 @@ public List findAllTopicsBySource(String source, long offset, long coun SolrQuery solrQuery = new SolrQuery(); solrQuery.setRows(0); + solrQuery.setSort(orderField, ascending ? ORDER.asc : ORDER.desc); + solrQuery.setFacetSort(FacetParams.FACET_SORT_INDEX); solrQuery.setQuery("*:*"); solrQuery.setFacet(true); solrQuery.setFacetMinCount(1); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QATopicRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QATopicRestRepository.java index a279cac83a4e..97a0ee978123 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QATopicRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/QATopicRestRepository.java @@ -18,6 +18,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort.Direction; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Component; @@ -30,6 +31,8 @@ @Component(QATopicRest.CATEGORY + "." + QATopicRest.NAME) public class QATopicRestRepository extends DSpaceRestRepository { + final static String ORDER_FIELD = "topic"; + @Autowired private QAEventService qaEventService; @@ -46,7 +49,13 @@ public QATopicRest findOne(Context context, String id) { @Override @PreAuthorize("hasAuthority('ADMIN')") public Page findAll(Context context, Pageable pageable) { - List topics = qaEventService.findAllTopics(pageable.getOffset(), pageable.getPageSize()); + boolean ascending = false; + if (pageable.getSort() != null && pageable.getSort().getOrderFor(ORDER_FIELD) != null) { + ascending = pageable.getSort() + .getOrderFor(ORDER_FIELD).getDirection() == Direction.ASC; + } + List topics = qaEventService.findAllTopics(pageable.getOffset(), pageable.getPageSize(), + ORDER_FIELD, ascending); long count = qaEventService.countTopics(); if (topics == null) { return null; @@ -58,8 +67,12 @@ public Page findAll(Context context, Pageable pageable) { @PreAuthorize("hasAuthority('ADMIN')") public Page findBySource(Context context, @Parameter(value = "source", required = true) String source, Pageable pageable) { + boolean ascending = false; + if (pageable.getSort() != null && pageable.getSort().getOrderFor(ORDER_FIELD) != null) { + ascending = pageable.getSort().getOrderFor(ORDER_FIELD).getDirection() == Direction.ASC; + } List topics = qaEventService.findAllTopicsBySource(source, - pageable.getOffset(), pageable.getPageSize()); + pageable.getOffset(), pageable.getPageSize(), ORDER_FIELD, ascending); long count = qaEventService.countTopicsBySource(source); if (topics == null) { return null; From f64bbd6c3215576eb89548500ef6e98c75ea3720 Mon Sep 17 00:00:00 2001 From: frabacche Date: Thu, 14 Dec 2023 17:14:20 +0100 Subject: [PATCH 507/510] CST-5249 IT java fixes OpenaireEventsImportIT --- .../qaevent/script/OpenaireEventsImportIT.java | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/dspace-api/src/test/java/org/dspace/qaevent/script/OpenaireEventsImportIT.java b/dspace-api/src/test/java/org/dspace/qaevent/script/OpenaireEventsImportIT.java index 35d9231ea5bb..6bb979f48be8 100644 --- a/dspace-api/src/test/java/org/dspace/qaevent/script/OpenaireEventsImportIT.java +++ b/dspace-api/src/test/java/org/dspace/qaevent/script/OpenaireEventsImportIT.java @@ -63,6 +63,8 @@ public class OpenaireEventsImportIT extends AbstractIntegrationTestWithDatabase private static final String BASE_JSON_DIR_PATH = "org/dspace/app/openaire-events/"; + private static final String ORDER_FIELD = "topic"; + private QAEventService qaEventService = new DSpace().getSingletonService(QAEventService.class); private Collection collection; @@ -157,7 +159,7 @@ public void testManyEventsImportFromFile() throws Exception { assertThat(qaEventService.findAllSources(0, 20), contains(QASourceMatcher.with(OPENAIRE_SOURCE, 5L))); - assertThat(qaEventService.findAllTopics(0, 20), containsInAnyOrder( + assertThat(qaEventService.findAllTopics(0, 20, ORDER_FIELD, false), containsInAnyOrder( QATopicMatcher.with("ENRICH/MORE/PROJECT", 1L), QATopicMatcher.with("ENRICH/MORE/PID", 1L), QATopicMatcher.with("ENRICH/MISSING/PID", 1L), @@ -213,7 +215,7 @@ public void testManyEventsImportFromFileWithUnknownHandle() throws Exception { assertThat(qaEventService.findAllSources(0, 20), contains(QASourceMatcher.with(OPENAIRE_SOURCE, 3L))); - assertThat(qaEventService.findAllTopics(0, 20), containsInAnyOrder( + assertThat(qaEventService.findAllTopics(0, 20, ORDER_FIELD, false), containsInAnyOrder( QATopicMatcher.with("ENRICH/MISSING/ABSTRACT", 1L), QATopicMatcher.with("ENRICH/MISSING/PROJECT", 1L), QATopicMatcher.with("ENRICH/MORE/PID", 1L) @@ -253,7 +255,8 @@ public void testManyEventsImportFromFileWithUnknownTopic() throws Exception { assertThat(qaEventService.findAllSources(0, 20), contains(QASourceMatcher.with(OPENAIRE_SOURCE, 1L))); - assertThat(qaEventService.findAllTopics(0, 20), contains(QATopicMatcher.with("ENRICH/MISSING/ABSTRACT", 1L))); + assertThat(qaEventService.findAllTopics(0, 20, ORDER_FIELD, false), + contains(QATopicMatcher.with("ENRICH/MISSING/ABSTRACT", 1L))); String abstractMessage = "{\"abstracts[0]\":\"Missing Abstract\"}"; @@ -280,7 +283,7 @@ public void testImportFromFileWithoutEvents() throws Exception { assertThat(qaEventService.findAllSources(0, 20), contains(QASourceMatcher.with(OPENAIRE_SOURCE, 0L))); - assertThat(qaEventService.findAllTopics(0, 20), empty()); + assertThat(qaEventService.findAllTopics(0, 20, ORDER_FIELD, false), empty()); verifyNoInteractions(mockBrokerClient); } @@ -327,7 +330,7 @@ public void testImportFromOpenaireBroker() throws Exception { assertThat(qaEventService.findAllSources(0, 20), contains(QASourceMatcher.with(OPENAIRE_SOURCE, 6L))); - assertThat(qaEventService.findAllTopics(0, 20), containsInAnyOrder( + assertThat(qaEventService.findAllTopics(0, 20, ORDER_FIELD, false), containsInAnyOrder( QATopicMatcher.with("ENRICH/MORE/PROJECT", 1L), QATopicMatcher.with("ENRICH/MORE/PID", 1L), QATopicMatcher.with("ENRICH/MISSING/PID", 1L), @@ -381,7 +384,7 @@ public void testImportFromOpenaireBrokerWithErrorDuringListSubscription() throws assertThat(qaEventService.findAllSources(0, 20), contains(QASourceMatcher.with(OPENAIRE_SOURCE, 0L))); - assertThat(qaEventService.findAllTopics(0, 20), empty()); + assertThat(qaEventService.findAllTopics(0, 20, ORDER_FIELD, false), empty()); verify(mockBrokerClient).listSubscriptions(openaireURL, "user@test.com"); @@ -432,7 +435,7 @@ public void testImportFromOpenaireBrokerWithErrorDuringEventsDownload() throws E assertThat(qaEventService.findAllSources(0, 20), contains(QASourceMatcher.with(OPENAIRE_SOURCE, 6L))); - assertThat(qaEventService.findAllTopics(0, 20), containsInAnyOrder( + assertThat(qaEventService.findAllTopics(0, 20, ORDER_FIELD, false), containsInAnyOrder( QATopicMatcher.with("ENRICH/MORE/PROJECT", 1L), QATopicMatcher.with("ENRICH/MISSING/PID", 1L), QATopicMatcher.with("ENRICH/MORE/PID", 1L), From 5c845dbbaa34e950dd1f503776c09849719a1d5f Mon Sep 17 00:00:00 2001 From: frabacche Date: Fri, 15 Dec 2023 10:51:36 +0100 Subject: [PATCH 508/510] CST-5249 new qaevent.enabled config and used for QAAuthorizationFeature --- .../impl/QAAuthorizationFeature.java | 44 +++++++++++++++++++ dspace/config/modules/qaevents.cfg | 2 + 2 files changed, 46 insertions(+) create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/QAAuthorizationFeature.java diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/QAAuthorizationFeature.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/QAAuthorizationFeature.java new file mode 100644 index 000000000000..ce0644eb91c0 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/QAAuthorizationFeature.java @@ -0,0 +1,44 @@ +/** + * 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.authorization.impl; + +import java.sql.SQLException; + +import org.dspace.app.rest.authorization.AuthorizationFeature; +import org.dspace.app.rest.authorization.AuthorizationFeatureDocumentation; +import org.dspace.app.rest.model.BaseObjectRest; +import org.dspace.app.rest.model.QAEventRest; +import org.dspace.core.Context; +import org.dspace.services.ConfigurationService; +import org.springframework.stereotype.Component; + +/** + * The QA Event feature. It can be used to verify if Quality Assurance can be seen. + * + * Authorization is granted if the current user has READ permissions on the given bitstream. + */ +@Component +@AuthorizationFeatureDocumentation(name = QAAuthorizationFeature.NAME, + description = "It can be used to verify if the user can manage Quality Assurance events") +public class QAAuthorizationFeature implements AuthorizationFeature { + public final static String NAME = "canSeeQA"; + + private ConfigurationService configurationService; + + @Override + public boolean isAuthorized(Context context, BaseObjectRest object) throws SQLException { + return configurationService.getBooleanProperty("qaevent.enabled", false); + } + + @Override + public String[] getSupportedTypes() { + return new String[]{ + QAEventRest.CATEGORY + "." + QAEventRest.NAME, + }; + } +} diff --git a/dspace/config/modules/qaevents.cfg b/dspace/config/modules/qaevents.cfg index da5080d58974..6cbaa120840e 100644 --- a/dspace/config/modules/qaevents.cfg +++ b/dspace/config/modules/qaevents.cfg @@ -3,6 +3,8 @@ #---------------------------------------------------------------# # Configuration properties used by data correction service # #---------------------------------------------------------------# +# Quality Assurance enable property, false by default +qaevents.enabled = false qaevents.solr.server = ${solr.server}/${solr.multicorePrefix}qaevent # A POST to these url(s) will be done to notify oaire of decision taken for each qaevents # qaevents.openaire.acknowledge-url = https://beta.api-broker.openaire.eu/feedback/events From f931a52001f4a10e815a94ee870aa9f0ca44bbe6 Mon Sep 17 00:00:00 2001 From: frabacche Date: Fri, 15 Dec 2023 12:43:08 +0100 Subject: [PATCH 509/510] CST-5249 typo property qaevents.enabled --- .../app/rest/authorization/impl/QAAuthorizationFeature.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/QAAuthorizationFeature.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/QAAuthorizationFeature.java index ce0644eb91c0..8d45a8a978ae 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/QAAuthorizationFeature.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/QAAuthorizationFeature.java @@ -32,7 +32,7 @@ public class QAAuthorizationFeature implements AuthorizationFeature { @Override public boolean isAuthorized(Context context, BaseObjectRest object) throws SQLException { - return configurationService.getBooleanProperty("qaevent.enabled", false); + return configurationService.getBooleanProperty("qaevents.enabled", false); } @Override From 0a74a941b0371e9f772c95e354c31064a2153a2a Mon Sep 17 00:00:00 2001 From: frabacche Date: Fri, 15 Dec 2023 16:41:48 +0100 Subject: [PATCH 510/510] CST-5249 Restore OpenAIRE on user interfaces, QAAuthorizationFeature fix and IT java class --- .../main/java/org/dspace/content/QAEvent.java | 10 ++- .../external/OpenaireRestConnector.java | 2 +- .../org/dspace/builder/QAEventBuilder.java | 12 +++ .../impl/QAAuthorizationFeature.java | 6 +- .../QAAuthorizationFeatureIT.java | 84 +++++++++++++++++++ dspace/config/crosswalks/oai/xoai.xml | 2 +- 6 files changed, 111 insertions(+), 5 deletions(-) create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/QAAuthorizationFeatureIT.java diff --git a/dspace-api/src/main/java/org/dspace/content/QAEvent.java b/dspace-api/src/main/java/org/dspace/content/QAEvent.java index 489599ea760a..9e90f81be32c 100644 --- a/dspace-api/src/main/java/org/dspace/content/QAEvent.java +++ b/dspace-api/src/main/java/org/dspace/content/QAEvent.java @@ -34,9 +34,17 @@ public class QAEvent { private String source; private String eventId; - + /** + * contains the targeted dspace object, + * ie: oai:www.openstarts.units.it:123456789/1120 contains the handle + * of the DSpace pbject in its final part 123456789/1120 + * */ private String originalId; + /** + * evaluated with the targeted dspace object id + * + * */ private String target; private String related; 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 2ec08be0551e..c96fad1de01c 100644 --- a/dspace-api/src/main/java/org/dspace/external/OpenaireRestConnector.java +++ b/dspace-api/src/main/java/org/dspace/external/OpenaireRestConnector.java @@ -103,7 +103,7 @@ public OpenaireRestToken grabNewAccessToken() throws IOException { if (StringUtils.isBlank(tokenServiceUrl) || StringUtils.isBlank(clientId) || StringUtils.isBlank(clientSecret)) { - throw new IOException("Cannot grab Openaire token with nulls service url, client id or secret"); + throw new IOException("Cannot grab OpenAIRE token with nulls service url, client id or secret"); } String auth = clientId + ":" + clientSecret; diff --git a/dspace-api/src/test/java/org/dspace/builder/QAEventBuilder.java b/dspace-api/src/test/java/org/dspace/builder/QAEventBuilder.java index 154bf737d921..823080516df8 100644 --- a/dspace-api/src/test/java/org/dspace/builder/QAEventBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/QAEventBuilder.java @@ -25,9 +25,21 @@ public class QAEventBuilder extends AbstractBuilder { private Item item; private QAEvent target; private String source = QAEvent.OPENAIRE_SOURCE; + /** + * the title of the DSpace object + * */ private String title; + /** + * the name of the Quality Assurance Event Topic + * */ private String topic; + /** + * thr original QA Event imported + * */ private String message; + /** + * uuid of the targeted DSpace object + * */ private String relatedItem; private double trust = 0.5; private Date lastUpdate = new Date(); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/QAAuthorizationFeature.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/QAAuthorizationFeature.java index 8d45a8a978ae..332c7a5989f2 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/QAAuthorizationFeature.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/QAAuthorizationFeature.java @@ -12,9 +12,10 @@ import org.dspace.app.rest.authorization.AuthorizationFeature; import org.dspace.app.rest.authorization.AuthorizationFeatureDocumentation; import org.dspace.app.rest.model.BaseObjectRest; -import org.dspace.app.rest.model.QAEventRest; +import org.dspace.app.rest.model.SiteRest; import org.dspace.core.Context; import org.dspace.services.ConfigurationService; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; /** @@ -28,6 +29,7 @@ public class QAAuthorizationFeature implements AuthorizationFeature { public final static String NAME = "canSeeQA"; + @Autowired private ConfigurationService configurationService; @Override @@ -38,7 +40,7 @@ public boolean isAuthorized(Context context, BaseObjectRest object) throws SQLEx @Override public String[] getSupportedTypes() { return new String[]{ - QAEventRest.CATEGORY + "." + QAEventRest.NAME, + SiteRest.CATEGORY + "." + SiteRest.NAME }; } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/QAAuthorizationFeatureIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/QAAuthorizationFeatureIT.java new file mode 100644 index 000000000000..f3e508485a25 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/QAAuthorizationFeatureIT.java @@ -0,0 +1,84 @@ +/** + * 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.authorization; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import org.dspace.app.rest.authorization.impl.QAAuthorizationFeature; +import org.dspace.app.rest.converter.SiteConverter; +import org.dspace.app.rest.matcher.AuthorizationMatcher; +import org.dspace.app.rest.model.SiteRest; +import org.dspace.app.rest.projection.DefaultProjection; +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.content.Site; +import org.dspace.content.service.SiteService; +import org.dspace.services.ConfigurationService; +import org.hamcrest.Matchers; +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Test suite for the Quality Assurance Authorization feature + * + * @author Francesco Bacchelli (francesco.bacchelli at 4science.it) + * + */ +public class QAAuthorizationFeatureIT extends AbstractControllerIntegrationTest { + + @Autowired + private AuthorizationFeatureService authorizationFeatureService; + + @Autowired + private SiteService siteService; + + @Autowired + private SiteConverter siteConverter; + + @Autowired + private ConfigurationService configurationService; + + + private AuthorizationFeature qaAuthorizationFeature; + + @Override + @Before + public void setUp() throws Exception { + super.setUp(); + context.turnOffAuthorisationSystem(); + qaAuthorizationFeature = authorizationFeatureService.find(QAAuthorizationFeature.NAME); + context.restoreAuthSystemState(); + } + + @Test + public void testQAAuthorizationSuccess() throws Exception { + configurationService.setProperty("qaevents.enabled", true); + Site site = siteService.findSite(context); + SiteRest siteRest = siteConverter.convert(site, DefaultProjection.DEFAULT); + String tokenAdmin = getAuthToken(admin.getEmail(), password); + Authorization authAdminSite = new Authorization(admin, qaAuthorizationFeature, siteRest); + + getClient(tokenAdmin).perform(get("/api/authz/authorizations/" + authAdminSite.getID())) + .andExpect(jsonPath("$", Matchers.is( + AuthorizationMatcher.matchAuthorization(authAdminSite)))); + } + + @Test + public void testQAAuthorizationFail() throws Exception { + configurationService.setProperty("qaevents.enabled", false); + Site site = siteService.findSite(context); + SiteRest siteRest = siteConverter.convert(site, DefaultProjection.DEFAULT); + String tokenAdmin = getAuthToken(admin.getEmail(), password); + Authorization authAdminSite = new Authorization(admin, qaAuthorizationFeature, siteRest); + + getClient(tokenAdmin).perform(get("/api/authz/authorizations/" + authAdminSite.getID())) + .andExpect(status().isNotFound()); + } +} diff --git a/dspace/config/crosswalks/oai/xoai.xml b/dspace/config/crosswalks/oai/xoai.xml index a7b0d9050cad..9bcabdf0e955 100644 --- a/dspace/config/crosswalks/oai/xoai.xml +++ b/dspace/config/crosswalks/oai/xoai.xml @@ -526,7 +526,7 @@ openaire - Openaire + OpenAIRE