From 75d9d6747ed5853e71074397fa984f003791971c Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Tue, 2 Feb 2021 19:24:37 +0100 Subject: [PATCH 001/412] 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 0000000000..782fa53802 --- /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 0000000000..ad575b5281 --- /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 0000000000..0a4de9c7fb --- /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 0000000000..b34ac6cd25 --- /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 0000000000..d6671676a0 --- /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 0000000000..0eba13e90b --- /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 0000000000..fc0e7b9dae --- /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 0000000000..2e33b6bfad --- /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 0000000000..0826e5173d --- /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 0000000000..ca198abaac --- /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 0000000000..33eaebcfaa --- /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 0000000000..afa9990d3d --- /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 0000000000..edc744d586 --- /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 0000000000..f426ddf6ab --- /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 0000000000..49894441b2 --- /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 0000000000..146f0d1290 --- /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 0000000000..6b72c58ee4 --- /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 0000000000..8901079425 --- /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 0000000000..c920016917 --- /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 0000000000..62f8222e24 --- /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 0000000000..b64c52248b --- /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 0000000000..5cf9a0484f --- /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 0000000000..5cf9a0484f --- /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 3c19a68e9f..258d53ee2f 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 5f86c73598..ffffa7f44d 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 0000000000..12058fbf73 --- /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 06deacaca4..2734a11628 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 java.util.List; 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.eperson.service.RegistrationDataService; 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 abstract class AbstractBuilder { inProgressUserService = XmlWorkflowServiceFactory.getInstance().getInProgressUserService(); poolTaskService = XmlWorkflowServiceFactory.getInstance().getPoolTaskService(); workflowItemRoleService = XmlWorkflowServiceFactory.getInstance().getWorkflowItemRoleService(); + + nbEventService = new DSpace().getSingletonService(NBEventService.class); } @@ -183,7 +188,7 @@ public abstract class AbstractBuilder { 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 0000000000..9101d3bf5e --- /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 0000000000..2411f4743d --- /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 7c79a85701..3adc55c4f1 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 class RestResourceController implements InitializingBean { 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 class RestResourceController implements InitializingBean { 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 class RestResourceController implements InitializingBean { 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 class RestResourceController implements InitializingBean { 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 0000000000..3534e3c310 --- /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 0000000000..8a5a284fe1 --- /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 0000000000..7d0f24a21d --- /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 0000000000..de2cea32fa --- /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 0000000000..4bebce27e8 --- /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 0000000000..bd3c266f1e --- /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 0000000000..a2fed4ffc6 --- /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 0000000000..3ec4660c4a --- /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 0000000000..b00688a6ea --- /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 0000000000..5c73f4d244 --- /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 0000000000..94da3b8a50 --- /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 0000000000..afaf3c7346 --- /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 0000000000..bd690ee683 --- /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 8739f6b8d5..8db5a74eef 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 @@ public class 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 0000000000..ef9abfe978 --- /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 0000000000..2d0095bd8f --- /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 0000000000..afb364bb0e --- /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 0000000000..644feeeec4 --- /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 02c618abf4..369d15c9c1 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 39f5a11378..b686a5672b 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 0000000000..68baec3d1d --- /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 0000000000..34dca93ebb --- /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 e7c55549c7..61b06c2aa9 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 698a824184..164a4dd835 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 563c639a10..518d81009b 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 0000000000..aa739da862 --- /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 0000000000..7630ebe20f --- /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 0000000000..1dfc0abecb --- /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 0000000000..8ed9b4d5ae --- /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 0000000000..f58b262ae0 --- /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 0000000000..a4cfbed4a9 --- /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 0000000000..d7ede6f561 --- /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 0000000000..8433c832d2 --- /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 0000000000..b0e31cb7ec --- /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 0000000000..e69de29bb2 From 2340a44e96ebde2398877ae3a3df839a8c92cefa Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Wed, 16 Feb 2022 15:48:28 +0100 Subject: [PATCH 002/412] [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 ad575b5281..2e5622cf4b 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.Item; 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 class NBEntityMetadataAction implements NBAction { 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 e54f609389..7a12c1c545 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 @@ public class CollectionServiceImpl extends DSpaceObjectServiceImpl i 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 522bdac224..07d4d113b7 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 interface CollectionService 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/412] [CST-5246] Added support for multiple providers --- .../app/nbevent/NBEntityMetadataAction.java | 36 ++-- .../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, 711 insertions(+), 286 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 2e5622cf4b..5e6b96c0b4 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 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.authorize.AuthorizeException; import org.dspace.content.Collection; import org.dspace.content.EntityType; @@ -141,21 +142,28 @@ public class NBEntityMetadataAction implements NBAction { } 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(); + if (!(message instanceof OpenaireMessageDto)) { + return null; } + + OpenaireMessageDto openaireMessage = (OpenaireMessageDto) message; + + if (StringUtils.equals(key, "acronym")) { + return openaireMessage.getAcronym(); + } else if (StringUtils.equals(key, "code")) { + return openaireMessage.getCode(); + } else if (StringUtils.equals(key, "funder")) { + return openaireMessage.getFunder(); + } else if (StringUtils.equals(key, "fundingProgram")) { + return openaireMessage.getFundingProgram(); + } else if (StringUtils.equals(key, "jurisdiction")) { + return openaireMessage.getJurisdiction(); + } else if (StringUtils.equals(key, "openaireId")) { + return openaireMessage.getOpenaireId(); + } else if (StringUtils.equals(key, "title")) { + 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 b34ac6cd25..2d84e8f9ba 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.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; @@ -72,7 +73,7 @@ public class NBEventActionServiceImpl implements NBEventActionService { 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 class NBEventActionServiceImpl implements NBEventActionService { } } + 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 fc0e7b9dae..80f7f5dabb 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.cli.ParseException; 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 class NBEventsRunnable extends DSpaceRunnable 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 interface NBEventService { 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 6b72c58ee4..55c1722de8 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 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 interface MessageDto { - 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 0000000000..5ae6b29c3a --- /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 8901079425..6f745d0800 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 @@ package org.dspace.app.nbevent.service.impl; 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.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.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 class NBEventServiceImpl implements NBEventService { */ 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 class NBEventServiceImpl implements NBEventService { 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 class NBEventServiceImpl implements NBEventService { */ @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 class NBEventServiceImpl implements NBEventService { 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 class NBEventServiceImpl implements NBEventService { 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 class NBEventServiceImpl implements NBEventService { 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 @@ public class NBEventServiceImpl implements NBEventService { } } + @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 c920016917..950fc37d01 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.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; /** @@ -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 class NBEvent { 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 0000000000..705cc26058 --- /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 9101d3bf5e..57e5c2a2fe 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.app.nbevent.service.NBEventService; 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 class NBEventBuilder extends AbstractBuilder { @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 3534e3c310..82230e8eee 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.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.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 class NBEventConverter implements DSpaceConverter { 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 class NBEventConverter implements DSpaceConverter { 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 0000000000..7524cc7975 --- /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 7d0f24a21d..7c2e03ac34 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 de2cea32fa..60dbce6d9b 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 class NBEventRest extends BaseObjectRest { 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 0000000000..69e230f378 --- /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 0000000000..84021abb6e --- /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 0000000000..899b684199 --- /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 0000000000..95dfa6b61a --- /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 afaf3c7346..c738da7bd6 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 java.util.List; 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 class NBTopicRestRepository extends DSpaceRestRepository 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 afb364bb0e..f8dca7e466 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.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.app.nbevent.service.dto.OpenaireMessageDto; import org.dspace.content.NBEvent; import org.hamcrest.Matcher; import org.hamcrest.Matchers; @@ -49,7 +49,8 @@ public class NBEventMatcher { 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 class NBEventMatcher { } } - 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 8ed9b4d5ae..338fbdcdcd 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/412] [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 | 106 +++++++++++++++++- .../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, 498 insertions(+), 117 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 782fa53802..70e7624197 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 5e6b96c0b4..f2322fe6b7 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.sql.SQLException; 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.content.service.WorkspaceItemService; 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 class NBEntityMetadataAction implements NBAction { } @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 @@ public class NBEntityMetadataAction implements NBAction { 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 0a4de9c7fb..e6a2917384 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 @@ package org.dspace.app.nbevent; 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 2d84e8f9ba..970858218b 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.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.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.core.Context; 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 class NBEventActionServiceImpl implements NBEventActionService { 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 class NBEventActionServiceImpl implements NBEventActionService { } } - 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 7e9de849b3..3d7e2114ce 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 @@ package org.dspace.app.nbevent; 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 class NBMetadataMapAction implements NBAction { } @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 910f799ad8..0bff9e05ff 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 @@ package org.dspace.app.nbevent; 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 class NBSimpleMetadataAction implements NBAction { } @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 d6671676a0..5263bc559b 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 @@ package org.dspace.app.nbevent; 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 80f7f5dabb..d56858402b 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.cli.ParseException; 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 @@ import org.slf4j.LoggerFactory; * @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 NBEventsScriptConfiguration extends Scr /** * 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 edc744d586..475cc44a7d 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.JsonDeserializer; 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 f426ddf6ab..db93eb95c5 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.content.NBEventProcessed; 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 49894441b2..db3977c109 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.AbstractHibernateDAO; 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 bb3b5bbc49..e2c4570129 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 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; +/** + * 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 55c1722de8..4c59ab1c85 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 5ae6b29c3a..188139afef 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 @@ package org.dspace.app.nbevent.service.dto; 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 6f745d0800..84853fb5e2 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 java.util.stream.Collectors; 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.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; @@ -45,6 +45,12 @@ 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. + * + * @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 class NBEventServiceImpl implements NBEventService { } @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 class NBEventServiceImpl implements NBEventService { } @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 class NBEventServiceImpl implements NBEventService { 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 class NBEventServiceImpl implements NBEventService { 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 @@ public class NBEventServiceImpl implements NBEventService { } @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 class NBEventServiceImpl implements NBEventService { 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 class NBEventServiceImpl implements NBEventService { } 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 950fc37d01..e99fbaefa1 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 java.util.Date; 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 class NBEvent { 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 @@ public class NBEvent { } + 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 705cc26058..0000000000 --- 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 57e5c2a2fe..8bf1b206da 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.app.nbevent.service.NBEventService; 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 82230e8eee..21bbdfff29 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.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.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.app.rest.projection.Projection; 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 class NBEventConverter implements DSpaceConverter { 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 class NBEventConverter implements DSpaceConverter { 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 7524cc7975..a1b496df04 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.model.NBSourceRest; 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 8a5a284fe1..f9ab34da89 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.model.NBTopicRest; 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 7c2e03ac34..df6187651c 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 60dbce6d9b..0ccc1a55da 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 java.util.Date; 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 84021abb6e..ca6ee5d06d 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 @@ package org.dspace.app.rest.model; 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 bd3c266f1e..b052d3d4da 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.NBEventRest; 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 899b684199..55db5d6343 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.NBSourceRest; 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 a2fed4ffc6..78af04a764 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.NBTopicRest; 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 b00688a6ea..d1bc469036 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.data.domain.Sort.Direction; 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 95dfa6b61a..ceeef3671a 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 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; @@ -20,6 +19,12 @@ import org.springframework.data.domain.Pageable; 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 c738da7bd6..479a606e00 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.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; @@ -21,6 +20,12 @@ 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 { @@ -51,9 +56,9 @@ public class NBTopicRestRepository extends DSpaceRestRepository 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 bd690ee683..55bfe3d2f1 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.dspace.services.RequestService; 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 ef9abfe978..a9f5d29427 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.dspace.content.NBEvent; 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 2d0095bd8f..dea109219a 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. 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.dspace.content.NBEvent; 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 f8dca7e466..a09d115359 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.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.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 class NBEventMatcher { 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 class NBEventMatcher { } } - 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 0000000000..35031202f0 --- /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 644feeeec4..7ad6972b1e 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 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.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 61b06c2aa9..184950a137 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 518d81009b..9dee833089 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/412] [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 970858218b..ac9fdd5c3f 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 class NBEventActionServiceImpl implements NBEventActionService { } 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 class NBEventActionServiceImpl implements NBEventActionService { @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 0eba13e90b..8297599bc5 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 class NBEventsDeleteCascadeConsumer implements Consumer { 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 e2c4570129..599806f425 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 interface NBEventService { /** * 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 interface NBEventService { /** * 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 84853fb5e2..47c98e1467 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 @@ public class NBEventServiceImpl implements NBEventService { } @Override - public long countTopics(Context context) { + public long countTopics() { SolrQuery solrQuery = new SolrQuery(); solrQuery.setRows(0); solrQuery.setQuery("*:*"); @@ -118,7 +120,7 @@ public class NBEventServiceImpl implements NBEventService { } @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 class NBEventServiceImpl implements NBEventService { } @Override - public void deleteEventByEventId(Context context, String id) { + public void deleteEventByEventId(String id) { try { getSolr().deleteById(id); getSolr().commit(); @@ -147,7 +149,7 @@ public class NBEventServiceImpl implements NBEventService { } @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 class NBEventServiceImpl implements NBEventService { 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 class NBEventServiceImpl implements NBEventService { } @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 @@ public class NBEventServiceImpl implements NBEventService { } @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 class NBEventServiceImpl implements NBEventService { } @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 @@ public class NBEventServiceImpl implements NBEventService { @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 class NBEventServiceImpl implements NBEventService { 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 8bf1b206da..3ad22738c3 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 class NBEventBuilder extends AbstractBuilder { 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 class NBEventBuilder extends AbstractBuilder { @Override public void cleanup() throws Exception { - nbEventService.deleteEventByEventId(context, target.getEventId()); + nbEventService.deleteEventByEventId(target.getEventId()); } @Override @@ -118,7 +122,7 @@ public class NBEventBuilder extends AbstractBuilder { @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 2411f4743d..1245c3854e 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 class NBEventRestController { @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 class NBEventRestController { 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 3ec4660c4a..901d600c10 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 d1bc469036..f173ebebc9 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 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 479a606e00..280d7e26d1 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 class NBTopicRestRepository extends DSpaceRestRepository 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 class NBTopicRestRepository extends DSpaceRestRepository 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 0000000000..2af54b74da --- /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/412] [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 47c98e1467..667e446b5b 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 class NBEventServiceImpl implements NBEventService { 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 class NBEventServiceImpl implements NBEventService { 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 class NBEventServiceImpl implements NBEventService { 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 class NBEventServiceImpl implements NBEventService { @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 class NBEventServiceImpl implements NBEventService { 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 @@ public class NBEventServiceImpl implements NBEventService { @Override public NBSource findSource(String sourceName) { - if (!ArrayUtils.contains(getSupportedSources(), sourceName)) { + if (isNotSupportedSource(sourceName)) { return null; } @@ -382,7 +383,7 @@ public class NBEventServiceImpl implements NBEventService { 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 class NBEventServiceImpl implements NBEventService { 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 280d7e26d1..53b1a4be6c 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 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; @@ -55,7 +56,8 @@ public class NBTopicRestRepository extends DSpaceRestRepository 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 dea109219a..7fe9dbc8b2 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.CollectionBuilder; 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 @@ import org.junit.Test; */ public class NBTopicRestRepositoryIT extends AbstractControllerIntegrationTest { + @Autowired + private ConfigurationService configurationService; + @Test public void findAllTest() throws Exception { context.turnOffAuthorisationSystem(); @@ -39,16 +43,16 @@ public class NBTopicRestRepositoryIT extends AbstractControllerIntegrationTest { .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 class NBTopicRestRepositoryIT extends AbstractControllerIntegrationTest { .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 class NBTopicRestRepositoryIT extends AbstractControllerIntegrationTest { .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 class NBTopicRestRepositoryIT extends AbstractControllerIntegrationTest { .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 class NBTopicRestRepositoryIT extends AbstractControllerIntegrationTest { .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 class NBTopicRestRepositoryIT extends AbstractControllerIntegrationTest { .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/412] [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 f2322fe6b7..8051362cfa 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 class NBEntityMetadataAction implements NBAction { 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 d56858402b..4969267d94 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 class OpenaireEventsRunnable extends DSpaceRunnable>() { }); } 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 class OpenaireEventsRunnable extends DSpaceRunnable Date: Tue, 29 Mar 2022 16:16:41 +0200 Subject: [PATCH 008/412] [CST-5249] Added suggestions from openaire --- ...blicationLoaderCliScriptConfiguration.java | 29 + .../OAIREPublicationLoaderRunnable.java | 113 + .../OAIREPublicationLoaderRunnableCli.java | 36 + ...EPublicationLoaderScriptConfiguration.java | 62 + .../suggestion/SolrSuggestionProvider.java | 140 ++ .../SolrSuggestionStorageService.java | 191 ++ .../SolrSuggestionStorageServiceImpl.java | 347 +++ .../org/dspace/app/suggestion/Suggestion.java | 89 + .../app/suggestion/SuggestionEvidence.java | 56 + .../app/suggestion/SuggestionProvider.java | 34 + .../app/suggestion/SuggestionService.java | 48 + .../app/suggestion/SuggestionServiceImpl.java | 191 ++ .../app/suggestion/SuggestionSource.java | 46 + .../app/suggestion/SuggestionTarget.java | 71 + .../app/suggestion/SuggestionUtils.java | 111 + .../suggestion/oaire/AuthorNamesScorer.java | 147 ++ .../app/suggestion/oaire/DateScorer.java | 200 ++ .../app/suggestion/oaire/EvidenceScorer.java | 37 + .../oaire/OAIREPublicationLoader.java | 240 ++ .../service/impl/ExternalDataServiceImpl.java | 15 + ...pleXpathDateFormatMetadataContributor.java | 84 + .../SimpleXpathMetadatumContributor.java | 8 +- .../OpenAIREPublicationFieldMapping.java | 29 + ...enAireImportMetadataSourceServiceImpl.java | 337 +++ .../spring-dspace-addon-import-services.xml | 16 +- ...pring-dspace-addon-suggestion-services.xml | 23 + .../config/spring/api/external-services.xml | 2 + .../config/spring/api/solr-services.xml | 3 + .../config/spring/api/suggestions.xml | 33 + .../MockSolrSuggestionProvider.java | 20 + .../MockSolrSuggestionStorageService.java | 38 + .../MockSuggestionExternalDataSource.java | 67 + .../org/dspace/builder/AbstractBuilder.java | 4 + .../java/org/dspace/builder/ItemBuilder.java | 4 + .../builder/SuggestionTargetBuilder.java | 161 ++ .../app/rest/RestResourceController.java | 7 + .../rest/converter/SuggestionConverter.java | 52 + .../converter/SuggestionSourceConverter.java | 39 + .../converter/SuggestionTargetConverter.java | 41 + .../dspace/app/rest/model/SuggestionRest.java | 110 + .../app/rest/model/SuggestionSourceRest.java | 51 + .../app/rest/model/SuggestionTargetRest.java | 73 + .../model/hateoas/SuggestionResource.java | 25 + .../hateoas/SuggestionSourceResource.java | 25 + .../hateoas/SuggestionTargetResource.java | 25 + .../repository/SuggestionRestRepository.java | 88 + .../SuggestionSourceRestRepository.java | 64 + .../SuggestionTargetRestRepository.java | 92 + .../SuggestionTargetTargetLinkRepository.java | 70 + ...ggestionRestPermissionEvaluatorPlugin.java | 94 + ...onTargetRestPermissionEvaluatorPlugin.java | 95 + .../rest/ExternalSourcesRestControllerIT.java | 1 + .../app/rest/SuggestionRestRepositoryIT.java | 473 ++++ .../SuggestionSourceRestRepositoryIT.java | 168 ++ .../SuggestionTargetRestRepositoryIT.java | 597 +++++ .../app/rest/matcher/SuggestionMatcher.java | 57 + .../rest/matcher/SuggestionSourceMatcher.java | 28 + .../rest/matcher/SuggestionTargetMatcher.java | 29 + dspace/config/dspace.cfg | 2 + dspace/config/modules/authority.cfg | 13 + dspace/config/modules/suggestion.cfg | 7 + dspace/config/registries/dspace-types.xml | 8 + .../config/spring/api/external-services.xml | 12 + .../spring/api/openaire-integration.xml | 226 ++ dspace/config/spring/api/scripts.xml | 6 + dspace/config/spring/api/solr-services.xml | 3 + dspace/config/spring/api/suggestions.xml | 68 + dspace/solr/suggestion/conf/admin-extra.html | 31 + dspace/solr/suggestion/conf/elevate.xml | 36 + dspace/solr/suggestion/conf/protwords.txt | 21 + dspace/solr/suggestion/conf/schema.xml | 547 +++++ dspace/solr/suggestion/conf/scripts.conf | 24 + dspace/solr/suggestion/conf/solrconfig.xml | 1943 +++++++++++++++++ dspace/solr/suggestion/conf/spellings.txt | 2 + dspace/solr/suggestion/conf/stopwords.txt | 57 + dspace/solr/suggestion/conf/synonyms.txt | 31 + dspace/solr/suggestion/core.properties | 0 77 files changed, 8365 insertions(+), 8 deletions(-) create mode 100644 dspace-api/src/main/java/org/dspace/app/suggestion/OAIREPublicationLoaderCliScriptConfiguration.java create mode 100644 dspace-api/src/main/java/org/dspace/app/suggestion/OAIREPublicationLoaderRunnable.java create mode 100644 dspace-api/src/main/java/org/dspace/app/suggestion/OAIREPublicationLoaderRunnableCli.java create mode 100644 dspace-api/src/main/java/org/dspace/app/suggestion/OAIREPublicationLoaderScriptConfiguration.java create mode 100644 dspace-api/src/main/java/org/dspace/app/suggestion/SolrSuggestionProvider.java create mode 100644 dspace-api/src/main/java/org/dspace/app/suggestion/SolrSuggestionStorageService.java create mode 100644 dspace-api/src/main/java/org/dspace/app/suggestion/SolrSuggestionStorageServiceImpl.java create mode 100644 dspace-api/src/main/java/org/dspace/app/suggestion/Suggestion.java create mode 100644 dspace-api/src/main/java/org/dspace/app/suggestion/SuggestionEvidence.java create mode 100644 dspace-api/src/main/java/org/dspace/app/suggestion/SuggestionProvider.java create mode 100644 dspace-api/src/main/java/org/dspace/app/suggestion/SuggestionService.java create mode 100644 dspace-api/src/main/java/org/dspace/app/suggestion/SuggestionServiceImpl.java create mode 100644 dspace-api/src/main/java/org/dspace/app/suggestion/SuggestionSource.java create mode 100644 dspace-api/src/main/java/org/dspace/app/suggestion/SuggestionTarget.java create mode 100644 dspace-api/src/main/java/org/dspace/app/suggestion/SuggestionUtils.java create mode 100644 dspace-api/src/main/java/org/dspace/app/suggestion/oaire/AuthorNamesScorer.java create mode 100644 dspace-api/src/main/java/org/dspace/app/suggestion/oaire/DateScorer.java create mode 100644 dspace-api/src/main/java/org/dspace/app/suggestion/oaire/EvidenceScorer.java create mode 100644 dspace-api/src/main/java/org/dspace/app/suggestion/oaire/OAIREPublicationLoader.java create mode 100644 dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleXpathDateFormatMetadataContributor.java create mode 100644 dspace-api/src/main/java/org/dspace/importer/external/openaire/metadatamapping/OpenAIREPublicationFieldMapping.java create mode 100644 dspace-api/src/main/java/org/dspace/importer/external/openaire/service/OpenAireImportMetadataSourceServiceImpl.java create mode 100644 dspace-api/src/main/resources/spring/spring-dspace-addon-suggestion-services.xml create mode 100644 dspace-api/src/test/data/dspaceFolder/config/spring/api/suggestions.xml create mode 100644 dspace-api/src/test/java/org/dspace/app/suggestion/MockSolrSuggestionProvider.java create mode 100644 dspace-api/src/test/java/org/dspace/app/suggestion/MockSolrSuggestionStorageService.java create mode 100644 dspace-api/src/test/java/org/dspace/app/suggestion/MockSuggestionExternalDataSource.java create mode 100644 dspace-api/src/test/java/org/dspace/builder/SuggestionTargetBuilder.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SuggestionConverter.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SuggestionSourceConverter.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SuggestionTargetConverter.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SuggestionRest.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SuggestionSourceRest.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SuggestionTargetRest.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/SuggestionResource.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/SuggestionSourceResource.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/SuggestionTargetResource.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SuggestionRestRepository.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SuggestionSourceRestRepository.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SuggestionTargetRestRepository.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SuggestionTargetTargetLinkRepository.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/security/SuggestionRestPermissionEvaluatorPlugin.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/security/SuggestionTargetRestPermissionEvaluatorPlugin.java create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/SuggestionRestRepositoryIT.java create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/SuggestionSourceRestRepositoryIT.java create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/SuggestionTargetRestRepositoryIT.java create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/SuggestionMatcher.java create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/SuggestionSourceMatcher.java create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/SuggestionTargetMatcher.java create mode 100644 dspace/config/modules/authority.cfg create mode 100644 dspace/config/modules/suggestion.cfg create mode 100644 dspace/config/spring/api/openaire-integration.xml create mode 100644 dspace/config/spring/api/suggestions.xml create mode 100644 dspace/solr/suggestion/conf/admin-extra.html create mode 100644 dspace/solr/suggestion/conf/elevate.xml create mode 100644 dspace/solr/suggestion/conf/protwords.txt create mode 100644 dspace/solr/suggestion/conf/schema.xml create mode 100644 dspace/solr/suggestion/conf/scripts.conf create mode 100644 dspace/solr/suggestion/conf/solrconfig.xml create mode 100644 dspace/solr/suggestion/conf/spellings.txt create mode 100644 dspace/solr/suggestion/conf/stopwords.txt create mode 100644 dspace/solr/suggestion/conf/synonyms.txt create mode 100644 dspace/solr/suggestion/core.properties diff --git a/dspace-api/src/main/java/org/dspace/app/suggestion/OAIREPublicationLoaderCliScriptConfiguration.java b/dspace-api/src/main/java/org/dspace/app/suggestion/OAIREPublicationLoaderCliScriptConfiguration.java new file mode 100644 index 0000000000..aeb034da06 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/suggestion/OAIREPublicationLoaderCliScriptConfiguration.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.suggestion; + +import org.apache.commons.cli.Options; + +/** + * Extension of {@link OAIREPublicationLoaderScriptConfiguration} for CLI. + * + * @author Alessandro Martelli (alessandro.martelli at 4science.it) + */ +public class OAIREPublicationLoaderCliScriptConfiguration + extends OAIREPublicationLoaderScriptConfiguration { + + @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/suggestion/OAIREPublicationLoaderRunnable.java b/dspace-api/src/main/java/org/dspace/app/suggestion/OAIREPublicationLoaderRunnable.java new file mode 100644 index 0000000000..1349a1a40c --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/suggestion/OAIREPublicationLoaderRunnable.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.suggestion; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import org.apache.commons.cli.ParseException; +import org.dspace.app.suggestion.oaire.OAIREPublicationLoader; +import org.dspace.content.Item; +import org.dspace.core.Context; +import org.dspace.discovery.IndexableObject; +import org.dspace.discovery.SearchService; +import org.dspace.scripts.DSpaceRunnable; +import org.dspace.utils.DSpace; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Runner responsible to import metadata about authors from OpenAIRE to Solr. + * This runner works in two ways: + * If -s parameter with a valid UUID is received, then the specific researcher + * with this UUID will be used. + * Invocation without any parameter results in massive import, processing all + * authors registered in DSpace. + * + * @author Alessandro Martelli (alessandro.martelli at 4science.it) + */ +public class OAIREPublicationLoaderRunnable + extends DSpaceRunnable> { + + private static final Logger LOGGER = LoggerFactory.getLogger(OAIREPublicationLoaderRunnable.class); + + private OAIREPublicationLoader oairePublicationLoader = null; + + protected Context context; + + protected String profile; + + @Override + @SuppressWarnings({ "rawtypes", "unchecked" }) + public OAIREPublicationLoaderScriptConfiguration getScriptConfiguration() { + OAIREPublicationLoaderScriptConfiguration configuration = new DSpace().getServiceManager() + .getServiceByName("import-oaire-suggestions", OAIREPublicationLoaderScriptConfiguration.class); + return configuration; + } + + @Override + public void setup() throws ParseException { + + oairePublicationLoader = new DSpace().getServiceManager().getServiceByName( + "OAIREPublicationLoader", OAIREPublicationLoader.class); + + profile = commandLine.getOptionValue("s"); + if (profile == null) { + LOGGER.info("No argument for -s, process all profile"); + } else { + LOGGER.info("Process eperson item with UUID " + profile); + } + } + + @Override + public void internalRun() throws Exception { + + context = new Context(); + + List researchers = getResearchers(profile); + + for (Item researcher : researchers) { + + oairePublicationLoader.importAuthorRecords(context, researcher); + } + + } + + /** + * Get the Item(s) which map a researcher from Solr. If the uuid is specified, + * the researcher with this UUID will be chosen. If the uuid doesn't match any + * researcher, the method returns an empty array list. If uuid is null, all + * research will be return. + * + * @param profile uuid of the researcher. If null, all researcher will be + * returned. + * @return the researcher with specified UUID or all researchers + */ + @SuppressWarnings("rawtypes") + private List getResearchers(String profileUUID) { + final UUID uuid = profileUUID != null ? UUID.fromString(profileUUID) : null; + SearchService searchService = new DSpace().getSingletonService(SearchService.class); + List objects = null; + if (uuid != null) { + objects = searchService.search(context, "search.resourceid:" + uuid.toString(), + "lastModified", false, 0, 1000, "search.resourcetype:Item", "dspace.entity.type:Person"); + } else { + objects = searchService.search(context, "*:*", "lastModified", false, 0, 1000, "search.resourcetype:Item", + "dspace.entity.type:Person"); + } + List items = new ArrayList(); + if (objects != null) { + for (IndexableObject o : objects) { + items.add((Item) o.getIndexedObject()); + } + } + LOGGER.info("Found " + items.size() + " researcher(s)"); + return items; + } +} diff --git a/dspace-api/src/main/java/org/dspace/app/suggestion/OAIREPublicationLoaderRunnableCli.java b/dspace-api/src/main/java/org/dspace/app/suggestion/OAIREPublicationLoaderRunnableCli.java new file mode 100644 index 0000000000..b0f8505779 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/suggestion/OAIREPublicationLoaderRunnableCli.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.suggestion; + +import org.apache.commons.cli.HelpFormatter; +import org.apache.commons.cli.ParseException; +import org.dspace.utils.DSpace; + +public class OAIREPublicationLoaderRunnableCli extends OAIREPublicationLoaderRunnable { + + @Override + @SuppressWarnings({ "rawtypes", "unchecked" }) + public OAIREPublicationLoaderCliScriptConfiguration getScriptConfiguration() { + OAIREPublicationLoaderCliScriptConfiguration configuration = new DSpace().getServiceManager() + .getServiceByName("import-oaire-suggestions", OAIREPublicationLoaderCliScriptConfiguration.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 Readearchers Suggestions", getScriptConfiguration().getOptions()); + System.exit(0); + } + } + +} diff --git a/dspace-api/src/main/java/org/dspace/app/suggestion/OAIREPublicationLoaderScriptConfiguration.java b/dspace-api/src/main/java/org/dspace/app/suggestion/OAIREPublicationLoaderScriptConfiguration.java new file mode 100644 index 0000000000..594ab4ce31 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/suggestion/OAIREPublicationLoaderScriptConfiguration.java @@ -0,0 +1,62 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.suggestion; + +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 OAIREPublicationLoaderScriptConfiguration + 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 OAIREPublicationLoaderScriptConfiguration + */ + @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("s", "single-researcher", true, "Single researcher UUID"); + options.getOption("s").setType(String.class); + + super.options = options; + } + return options; + } + +} diff --git a/dspace-api/src/main/java/org/dspace/app/suggestion/SolrSuggestionProvider.java b/dspace-api/src/main/java/org/dspace/app/suggestion/SolrSuggestionProvider.java new file mode 100644 index 0000000000..e4573ebcd3 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/suggestion/SolrSuggestionProvider.java @@ -0,0 +1,140 @@ +/** + * The contents of this file are subject to the license and 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.suggestion; + +import java.io.IOException; +import java.util.List; +import java.util.UUID; + +import org.apache.logging.log4j.Logger; +import org.apache.solr.client.solrj.SolrServerException; +import org.dspace.content.service.ItemService; +import org.dspace.core.Context; +import org.dspace.external.model.ExternalDataObject; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Suggestion provider that read the suggestion from the local suggestion solr + * core + * + * @author Andrea Bollini (andrea.bollini at 4science dot it) + * + */ +public abstract class SolrSuggestionProvider implements SuggestionProvider { + private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(SolrSuggestionProvider.class); + + @Autowired + protected ItemService itemService; + + @Autowired + protected SolrSuggestionStorageService solrSuggestionStorageService; + + private String sourceName; + + public String getSourceName() { + return sourceName; + } + + public void setSourceName(String sourceName) { + this.sourceName = sourceName; + } + + public void setItemService(ItemService itemService) { + this.itemService = itemService; + } + + @Override + public long countAllTargets(Context context) { + try { + return this.solrSuggestionStorageService.countAllTargets(context, sourceName); + } catch (SolrServerException | IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public long countUnprocessedSuggestionByTarget(Context context, UUID target) { + try { + return this.solrSuggestionStorageService.countUnprocessedSuggestionByTarget(context, sourceName, target); + } catch (SolrServerException | IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public List findAllUnprocessedSuggestions(Context context, UUID target, int pageSize, long offset, + boolean ascending) { + + try { + return this.solrSuggestionStorageService.findAllUnprocessedSuggestions(context, sourceName, + target, pageSize, offset, ascending); + } catch (SolrServerException | IOException e) { + throw new RuntimeException(e); + } + + } + + @Override + public List findAllTargets(Context context, int pageSize, long offset) { + try { + return this.solrSuggestionStorageService.findAllTargets(context, sourceName, pageSize, offset); + } catch (SolrServerException | IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public Suggestion findUnprocessedSuggestion(Context context, UUID target, String id) { + try { + return this.solrSuggestionStorageService.findUnprocessedSuggestion(context, sourceName, target, id); + } catch (SolrServerException | IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public SuggestionTarget findTarget(Context context, UUID target) { + try { + return this.solrSuggestionStorageService.findTarget(context, sourceName, target); + } catch (SolrServerException | IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public void rejectSuggestion(Context context, UUID target, String idPart) { + Suggestion suggestion = findUnprocessedSuggestion(context, target, idPart); + try { + solrSuggestionStorageService.flagSuggestionAsProcessed(suggestion); + } catch (SolrServerException | IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public void flagRelatedSuggestionsAsProcessed(Context context, ExternalDataObject externalDataObject) { + if (!isExternalDataObjectPotentiallySuggested(context, externalDataObject)) { + return; + } + try { + solrSuggestionStorageService.flagAllSuggestionAsProcessed(sourceName, externalDataObject.getId()); + } catch (SolrServerException | IOException e) { + log.error(e.getMessage(), e); + } + } + + /** + * + * @param context + * @param externalDataObject + * @return true if the externalDataObject could be suggested by this provider + * (i.e. it comes from a DataProvider used by this suggestor) + */ + protected abstract boolean isExternalDataObjectPotentiallySuggested(Context context, + ExternalDataObject externalDataObject); +} diff --git a/dspace-api/src/main/java/org/dspace/app/suggestion/SolrSuggestionStorageService.java b/dspace-api/src/main/java/org/dspace/app/suggestion/SolrSuggestionStorageService.java new file mode 100644 index 0000000000..b7de6146f2 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/suggestion/SolrSuggestionStorageService.java @@ -0,0 +1,191 @@ +/** + * The contents of this file are subject to the license and 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.suggestion; + +import java.io.IOException; +import java.util.List; +import java.util.UUID; + +import org.apache.solr.client.solrj.SolrServerException; +import org.dspace.core.Context; + +/** + * Service to deal with the local suggestion solr core used by the + * SolrSuggestionProvider(s) + * + * @author Andrea Bollini (andrea.bollini at 4science dot it) + * @author Luca Giamminonni (luca.giamminonni at 4science dot it) + * + */ +public interface SolrSuggestionStorageService { + public static final String SOURCE = "source"; + /** This is the URI Part of the suggestion source:target:id */ + public static final String SUGGESTION_FULLID = "suggestion_fullid"; + public static final String SUGGESTION_ID = "suggestion_id"; + public static final String TARGET_ID = "target_id"; + public static final String TITLE = "title"; + public static final String DATE = "date"; + public static final String DISPLAY = "display"; + public static final String CONTRIBUTORS = "contributors"; + public static final String ABSTRACT = "abstract"; + public static final String CATEGORY = "category"; + public static final String EXTERNAL_URI = "external-uri"; + public static final String PROCESSED = "processed"; + public static final String SCORE = "trust"; + public static final String EVIDENCES = "evidences"; + + /** + * Add a new suggestion to SOLR + * + * @param suggestion + * @param force true if the suggestion must be reindexed + * @param commit + * @throws IOException + * @throws SolrServerException + */ + public void addSuggestion(Suggestion suggestion, boolean force, boolean commit) + throws SolrServerException, IOException; + + /** + * Return true if the suggestion is already in SOLR and flagged as processed + * + * @param suggestion + * @return true if the suggestion is already in SOLR and flagged as processed + * @throws IOException + * @throws SolrServerException + */ + public boolean exist(Suggestion suggestion) throws SolrServerException, IOException; + + /** + * Delete a suggestion from SOLR if any + * + * @param suggestion + * @throws IOException + * @throws SolrServerException + */ + public void deleteSuggestion(Suggestion suggestion) throws SolrServerException, IOException; + + /** + * Flag a suggestion as processed in SOLR if any + * + * @param suggestion + * @throws IOException + * @throws SolrServerException + */ + public void flagSuggestionAsProcessed(Suggestion suggestion) throws SolrServerException, IOException; + + /** + * Delete all the suggestions from SOLR if any related to a specific target + * + * @param target + * @throws IOException + * @throws SolrServerException + */ + public void deleteTarget(SuggestionTarget target) throws SolrServerException, IOException; + + /** + * Performs an explicit commit, causing pending documents to be committed for + * indexing. + * + * @throws SolrServerException + * @throws IOException + */ + void commit() throws SolrServerException, IOException; + + /** + * Flag all the suggestion related to the given source and id as processed. + * + * @param source the source name + * @param idPart the id's last part + * @throws SolrServerException + * @throws IOException + */ + void flagAllSuggestionAsProcessed(String source, String idPart) throws SolrServerException, IOException; + + /** + * Count all the targets related to the given source. + * + * @param source the source name + * @return the target's count + * @throws IOException + * @throws SolrServerException + */ + long countAllTargets(Context context, String source) throws SolrServerException, IOException; + + /** + * Count all the unprocessed suggestions related to the given source and target. + * + * @param context the DSpace Context + * @param source the source name + * @param target the target id + * @return the suggestion count + * @throws SolrServerException + * @throws IOException + */ + long countUnprocessedSuggestionByTarget(Context context, String source, UUID target) + throws SolrServerException, IOException; + + /** + * Find all the unprocessed suggestions related to the given source and target. + * + * @param context the DSpace Context + * @param source the source name + * @param target the target id + * @param pageSize the page size + * @param offset the page offset + * @param ascending true to retrieve the suggestions ordered by score + * ascending + * @return the found suggestions + * @throws SolrServerException + * @throws IOException + */ + List findAllUnprocessedSuggestions(Context context, String source, UUID target, + int pageSize, long offset, boolean ascending) throws SolrServerException, IOException; + + /** + * + * Find all the suggestion targets related to the given source. + * + * @param context the DSpace Context + * @param source the source name + * @param pageSize the page size + * @param offset the page offset + * @return the found suggestion targets + * @throws SolrServerException + * @throws IOException + */ + List findAllTargets(Context context, String source, int pageSize, long offset) + throws SolrServerException, IOException; + + /** + * Find an unprocessed suggestion by the given source, target id and suggestion + * id. + * + * @param context the DSpace Context + * @param source the source name + * @param target the target id + * @param id the suggestion id + * @return the suggestion, if any + * @throws SolrServerException + * @throws IOException + */ + Suggestion findUnprocessedSuggestion(Context context, String source, UUID target, String id) + throws SolrServerException, IOException; + + /** + * Find a suggestion target by the given source and target. + * + * @param context the DSpace Context + * @param source the source name + * @param target the target id + * @return the suggestion target, if any + * @throws SolrServerException + * @throws IOException + */ + SuggestionTarget findTarget(Context context, String source, UUID target) throws SolrServerException, IOException; +} diff --git a/dspace-api/src/main/java/org/dspace/app/suggestion/SolrSuggestionStorageServiceImpl.java b/dspace-api/src/main/java/org/dspace/app/suggestion/SolrSuggestionStorageServiceImpl.java new file mode 100644 index 0000000000..9d77fc2886 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/suggestion/SolrSuggestionStorageServiceImpl.java @@ -0,0 +1,347 @@ +/** + * The contents of this file are subject to the license and 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.suggestion; + +import static org.apache.commons.collections.CollectionUtils.isEmpty; + +import java.io.IOException; +import java.lang.reflect.Type; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.stream.Collectors; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import org.apache.commons.lang3.StringUtils; +import org.apache.solr.client.solrj.SolrClient; +import org.apache.solr.client.solrj.SolrQuery; +import org.apache.solr.client.solrj.SolrQuery.SortClause; +import org.apache.solr.client.solrj.SolrServerException; +import org.apache.solr.client.solrj.impl.HttpSolrClient; +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.content.Item; +import org.dspace.content.dto.MetadataValueDTO; +import org.dspace.content.service.ItemService; +import org.dspace.core.Context; +import org.dspace.services.factory.DSpaceServicesFactory; +import org.dspace.util.UUIDUtils; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Service to deal with the local suggestion solr core used by the + * SolrSuggestionProvider(s) + * + * @author Andrea Bollini (andrea.bollini at 4science dot it) + * + */ +public class SolrSuggestionStorageServiceImpl implements SolrSuggestionStorageService { + + protected SolrClient solrSuggestionClient; + + @Autowired + private ItemService itemService; + + /** + * Get solr client which use suggestion core + * + * @return solr client + */ + protected SolrClient getSolr() { + if (solrSuggestionClient == null) { + String solrService = DSpaceServicesFactory.getInstance().getConfigurationService() + .getProperty("suggestion.solr.server", "http://localhost:8983/solr/suggestion"); + solrSuggestionClient = new HttpSolrClient.Builder(solrService).build(); + } + return solrSuggestionClient; + } + + @Override + public void addSuggestion(Suggestion suggestion, boolean force, boolean commit) + throws SolrServerException, IOException { + if (force || !exist(suggestion)) { + Gson gson = new Gson(); + SolrInputDocument document = new SolrInputDocument(); + document.addField(SOURCE, suggestion.getSource()); + String suggestionFullID = suggestion.getID(); + document.addField(SUGGESTION_FULLID, suggestionFullID); + document.addField(SUGGESTION_ID, suggestionFullID.split(":", 3)[2]); + document.addField(TARGET_ID, suggestion.getTarget().getID().toString()); + document.addField(DISPLAY, suggestion.getDisplay()); + document.addField(TITLE, getFirstValue(suggestion, "dc", "title", null)); + document.addField(DATE, getFirstValue(suggestion, "dc", "date", "issued")); + document.addField(CONTRIBUTORS, getAllValues(suggestion, "dc", "contributor", "author")); + document.addField(ABSTRACT, getFirstValue(suggestion, "dc", "description", "abstract")); + document.addField(CATEGORY, getAllValues(suggestion, "dc", "source", null)); + document.addField(EXTERNAL_URI, suggestion.getExternalSourceUri()); + document.addField(SCORE, suggestion.getScore()); + document.addField(PROCESSED, false); + document.addField(EVIDENCES, gson.toJson(suggestion.getEvidences())); + getSolr().add(document); + if (commit) { + getSolr().commit(); + } + } + } + + @Override + public void commit() throws SolrServerException, IOException { + getSolr().commit(); + } + + private List getAllValues(Suggestion suggestion, String schema, String element, String qualifier) { + return suggestion.getMetadata().stream() + .filter(st -> StringUtils.isNotBlank(st.getValue()) && StringUtils.equals(st.getSchema(), schema) + && StringUtils.equals(st.getElement(), element) + && StringUtils.equals(st.getQualifier(), qualifier)) + .map(st -> st.getValue()).collect(Collectors.toList()); + } + + private String getFirstValue(Suggestion suggestion, String schema, String element, String qualifier) { + return suggestion.getMetadata().stream() + .filter(st -> StringUtils.isNotBlank(st.getValue()) + && StringUtils.equals(st.getSchema(), schema) + && StringUtils.equals(st.getElement(), element) + && StringUtils.equals(st.getQualifier(), qualifier)) + .map(st -> st.getValue()).findFirst().orElse(null); + } + + @Override + public boolean exist(Suggestion suggestion) throws SolrServerException, IOException { + SolrQuery query = new SolrQuery( + SUGGESTION_FULLID + ":\"" + suggestion.getID() + "\" AND " + PROCESSED + ":true"); + return getSolr().query(query).getResults().getNumFound() == 1; + } + + @Override + public void deleteSuggestion(Suggestion suggestion) throws SolrServerException, IOException { + getSolr().deleteById(suggestion.getID()); + getSolr().commit(); + } + + @Override + public void flagSuggestionAsProcessed(Suggestion suggestion) throws SolrServerException, IOException { + SolrInputDocument sdoc = new SolrInputDocument(); + sdoc.addField(SUGGESTION_FULLID, suggestion.getID()); + Map fieldModifier = new HashMap<>(1); + fieldModifier.put("set", true); + sdoc.addField(PROCESSED, fieldModifier); // add the map as the field value + getSolr().add(sdoc); + getSolr().commit(); + } + + @Override + public void flagAllSuggestionAsProcessed(String source, String idPart) throws SolrServerException, IOException { + SolrQuery query = new SolrQuery(SOURCE + ":" + source + " AND " + SUGGESTION_ID + ":\"" + idPart + "\""); + query.setRows(Integer.MAX_VALUE); + query.setFields(SUGGESTION_FULLID); + SolrDocumentList results = getSolr().query(query).getResults(); + if (results.getNumFound() > 0) { + for (SolrDocument rDoc : results) { + SolrInputDocument sdoc = new SolrInputDocument(); + sdoc.addField(SUGGESTION_FULLID, rDoc.getFieldValue(SUGGESTION_FULLID)); + Map fieldModifier = new HashMap<>(1); + fieldModifier.put("set", true); + sdoc.addField(PROCESSED, fieldModifier); // add the map as the field value + getSolr().add(sdoc); + } + } + getSolr().commit(); + } + + @Override + public void deleteTarget(SuggestionTarget target) throws SolrServerException, IOException { + getSolr().deleteByQuery( + SOURCE + ":" + target.getSource() + " AND " + TARGET_ID + ":" + target.getTarget().getID().toString()); + getSolr().commit(); + } + + @Override + public long countAllTargets(Context context, String source) throws SolrServerException, IOException { + SolrQuery solrQuery = new SolrQuery(); + solrQuery.setRows(0); + solrQuery.setQuery(SOURCE + ":" + source); + solrQuery.addFilterQuery(PROCESSED + ":false"); + solrQuery.setFacet(true); + solrQuery.setFacetMinCount(1); + solrQuery.addFacetField(TARGET_ID); + solrQuery.setFacetLimit(Integer.MAX_VALUE); + QueryResponse response = getSolr().query(solrQuery); + return response.getFacetField(TARGET_ID).getValueCount(); + } + + @Override + public long countUnprocessedSuggestionByTarget(Context context, String source, UUID target) + throws SolrServerException, IOException { + SolrQuery solrQuery = new SolrQuery(); + solrQuery.setRows(0); + solrQuery.setQuery("*:*"); + solrQuery.addFilterQuery( + SOURCE + ":" + source, + TARGET_ID + ":" + target.toString(), + PROCESSED + ":false"); + + QueryResponse response = getSolr().query(solrQuery); + return response.getResults().getNumFound(); + } + + @Override + public List findAllUnprocessedSuggestions(Context context, String source, UUID target, + int pageSize, long offset, boolean ascending) throws SolrServerException, IOException { + + SolrQuery solrQuery = new SolrQuery(); + solrQuery.setRows(pageSize); + solrQuery.setStart((int) offset); + solrQuery.setQuery("*:*"); + solrQuery.addFilterQuery( + SOURCE + ":" + source, + TARGET_ID + ":" + target.toString(), + PROCESSED + ":false"); + + if (ascending) { + solrQuery.addSort(SortClause.asc("trust")); + } else { + solrQuery.addSort(SortClause.desc("trust")); + } + + solrQuery.addSort(SortClause.desc("date")); + solrQuery.addSort(SortClause.asc("title")); + + QueryResponse response = getSolr().query(solrQuery); + List suggestions = new ArrayList(); + for (SolrDocument solrDoc : response.getResults()) { + suggestions.add(convertSolrDoc(context, solrDoc, source)); + } + return suggestions; + + } + + @Override + public List findAllTargets(Context context, String source, int pageSize, long offset) + throws SolrServerException, IOException { + + SolrQuery solrQuery = new SolrQuery(); + solrQuery.setRows(0); + solrQuery.setQuery(SOURCE + ":" + source); + solrQuery.addFilterQuery(PROCESSED + ":false"); + solrQuery.setFacet(true); + solrQuery.setFacetMinCount(1); + solrQuery.addFacetField(TARGET_ID); + solrQuery.setFacetLimit((int) (pageSize + offset)); + QueryResponse response = getSolr().query(solrQuery); + FacetField facetField = response.getFacetField(TARGET_ID); + List suggestionTargets = new ArrayList(); + int idx = 0; + for (Count c : facetField.getValues()) { + if (idx < offset) { + idx++; + continue; + } + SuggestionTarget target = new SuggestionTarget(); + target.setSource(source); + target.setTotal((int) c.getCount()); + target.setTarget(findItem(context, c.getName())); + suggestionTargets.add(target); + idx++; + } + return suggestionTargets; + + } + + @Override + public Suggestion findUnprocessedSuggestion(Context context, String source, UUID target, String id) + throws SolrServerException, IOException { + + SolrQuery solrQuery = new SolrQuery(); + solrQuery.setRows(1); + solrQuery.setQuery("*:*"); + solrQuery.addFilterQuery( + SOURCE + ":" + source, + TARGET_ID + ":" + target.toString(), + SUGGESTION_ID + ":\"" + id + "\"", + PROCESSED + ":false"); + + SolrDocumentList results = getSolr().query(solrQuery).getResults(); + return isEmpty(results) ? null : convertSolrDoc(context, results.get(0), source); + } + + @Override + public SuggestionTarget findTarget(Context context, String source, UUID target) + throws SolrServerException, IOException { + SolrQuery solrQuery = new SolrQuery(); + solrQuery.setRows(0); + solrQuery.setQuery(SOURCE + ":" + source); + solrQuery.addFilterQuery( + TARGET_ID + ":" + target.toString(), + PROCESSED + ":false"); + QueryResponse response = getSolr().query(solrQuery); + SuggestionTarget sTarget = new SuggestionTarget(); + sTarget.setSource(source); + sTarget.setTotal((int) response.getResults().getNumFound()); + Item itemTarget = findItem(context, target); + if (itemTarget != null) { + sTarget.setTarget(itemTarget); + } else { + return null; + } + return sTarget; + } + + private Suggestion convertSolrDoc(Context context, SolrDocument solrDoc, String sourceName) { + Item target = findItem(context, (String) solrDoc.getFieldValue(TARGET_ID)); + + Suggestion suggestion = new Suggestion(sourceName, target, (String) solrDoc.getFieldValue(SUGGESTION_ID)); + suggestion.setDisplay((String) solrDoc.getFieldValue(DISPLAY)); + suggestion.getMetadata() + .add(new MetadataValueDTO("dc", "title", null, null, (String) solrDoc.getFieldValue(TITLE))); + suggestion.getMetadata() + .add(new MetadataValueDTO("dc", "date", "issued", null, (String) solrDoc.getFieldValue(DATE))); + suggestion.getMetadata().add( + new MetadataValueDTO("dc", "description", "abstract", null, (String) solrDoc.getFieldValue(ABSTRACT))); + + suggestion.setExternalSourceUri((String) solrDoc.getFieldValue(EXTERNAL_URI)); + if (solrDoc.containsKey(CATEGORY)) { + for (Object o : solrDoc.getFieldValues(CATEGORY)) { + suggestion.getMetadata().add( + new MetadataValueDTO("dc", "source", null, null, (String) o)); + } + } + if (solrDoc.containsKey(CONTRIBUTORS)) { + for (Object o : solrDoc.getFieldValues(CONTRIBUTORS)) { + suggestion.getMetadata().add( + new MetadataValueDTO("dc", "contributor", "author", null, (String) o)); + } + } + String evidencesJson = (String) solrDoc.getFieldValue(EVIDENCES); + Type listType = new TypeToken>() { + }.getType(); + List evidences = new Gson().fromJson(evidencesJson, listType); + suggestion.getEvidences().addAll(evidences); + return suggestion; + } + + private Item findItem(Context context, UUID itemId) { + try { + return itemService.find(context, itemId); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + + private Item findItem(Context context, String itemId) { + return findItem(context, UUIDUtils.fromString(itemId)); + } +} diff --git a/dspace-api/src/main/java/org/dspace/app/suggestion/Suggestion.java b/dspace-api/src/main/java/org/dspace/app/suggestion/Suggestion.java new file mode 100644 index 0000000000..3629b508ab --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/suggestion/Suggestion.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.suggestion; + +import java.util.LinkedList; +import java.util.List; + +import org.dspace.content.Item; +import org.dspace.content.dto.MetadataValueDTO; + +/** + * + * + * @author Andrea Bollini (andrea.bollini at 4science.it) + */ +public class Suggestion { + + private String id; + + private String display; + + private String source; + + private String externalSourceUri; + + private Item target; + + private List evidences = new LinkedList(); + + private List metadata = new LinkedList(); + + public Suggestion(String source, Item target, String idPart) { + this.source = source; + this.target = target; + this.id = source + ":" + target.getID().toString() + ":" + idPart; + } + + public String getDisplay() { + return display; + } + + public void setDisplay(String display) { + this.display = display; + } + + public String getSource() { + return source; + } + + public String getExternalSourceUri() { + return externalSourceUri; + } + + public void setExternalSourceUri(String externalSourceUri) { + this.externalSourceUri = externalSourceUri; + } + + public List getEvidences() { + return evidences; + } + + public List getMetadata() { + return metadata; + } + + public Item getTarget() { + return target; + } + + public String getID() { + return id; + } + + public Double getScore() { + if (evidences != null && evidences.size() > 0) { + double score = 0; + for (SuggestionEvidence evidence : evidences) { + score += evidence.getScore(); + } + return score; + } + return null; + } +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/app/suggestion/SuggestionEvidence.java b/dspace-api/src/main/java/org/dspace/app/suggestion/SuggestionEvidence.java new file mode 100644 index 0000000000..d1129837bc --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/suggestion/SuggestionEvidence.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.suggestion; + +/** + * + * + * @author Andrea Bollini (andrea.bollini at 4science.it) + */ +public class SuggestionEvidence { + + private String name; + + private double score; + + private String notes; + + public SuggestionEvidence() { + } + + public SuggestionEvidence(String name, double score, String notes) { + this.name = name; + this.score = score; + this.notes = notes; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public double getScore() { + return score; + } + + public void setScore(double score) { + this.score = score; + } + + public String getNotes() { + return notes; + } + + public void setNotes(String notes) { + this.notes = notes; + } + +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/app/suggestion/SuggestionProvider.java b/dspace-api/src/main/java/org/dspace/app/suggestion/SuggestionProvider.java new file mode 100644 index 0000000000..c7ae8e8025 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/suggestion/SuggestionProvider.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.suggestion; + +import java.util.List; +import java.util.UUID; + +import org.dspace.core.Context; +import org.dspace.external.model.ExternalDataObject; + +public interface SuggestionProvider { + public List findAllTargets(Context context, int pageSize, long offset); + + public long countAllTargets(Context context); + + public SuggestionTarget findTarget(Context context, UUID target); + + public List findAllUnprocessedSuggestions(Context context, UUID target, int pageSize, long offset, + boolean ascending); + + public long countUnprocessedSuggestionByTarget(Context context, UUID target); + + public Suggestion findUnprocessedSuggestion(Context context, UUID target, String id); + + public void rejectSuggestion(Context context, UUID target, String idPart); + + public void flagRelatedSuggestionsAsProcessed(Context context, ExternalDataObject externalDataObject); + +} diff --git a/dspace-api/src/main/java/org/dspace/app/suggestion/SuggestionService.java b/dspace-api/src/main/java/org/dspace/app/suggestion/SuggestionService.java new file mode 100644 index 0000000000..c52a9bb41c --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/suggestion/SuggestionService.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.suggestion; + +import java.util.List; +import java.util.UUID; + +import org.dspace.core.Context; + +/** + * + * + * @author Andrea Bollini (andrea.bollini at 4science.it) + */ +public interface SuggestionService { + + public SuggestionTarget find(Context context, String source, UUID id); + + public long countAll(Context context, String source); + + public List findAllTargets(Context context, String source, int pageSize, long offset); + + public long countAllByTarget(Context context, UUID target); + + public List findByTarget(Context context, UUID target, int pageSize, long offset); + + public SuggestionSource findSource(Context context, String source); + + public long countSources(Context context); + + public List findAllSources(Context context, int pageSize, long offset); + + public Suggestion findUnprocessedSuggestion(Context context, String id); + + public void rejectSuggestion(Context context, String id); + + public List findByTargetAndSource(Context context, UUID target, String source, int pageSize, + long offset, boolean ascending); + + public long countAllByTargetAndSource(Context context, String source, UUID target); + + public List getSuggestionProviders(); +} diff --git a/dspace-api/src/main/java/org/dspace/app/suggestion/SuggestionServiceImpl.java b/dspace-api/src/main/java/org/dspace/app/suggestion/SuggestionServiceImpl.java new file mode 100644 index 0000000000..4ac804ddaf --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/suggestion/SuggestionServiceImpl.java @@ -0,0 +1,191 @@ +/** + * The contents of this file are subject to the license and 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.suggestion; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.stream.Collectors; +import javax.annotation.Resource; + +import org.apache.logging.log4j.Logger; +import org.dspace.core.Context; +import org.springframework.stereotype.Service; + +@Service +public class SuggestionServiceImpl implements SuggestionService { + private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(SuggestionServiceImpl.class); + + @Resource(name = "suggestionProviders") + private Map providersMap; + + @Override + public List getSuggestionProviders() { + if (providersMap != null) { + return providersMap.values().stream().collect(Collectors.toList()); + } + return null; + } + + @Override + public SuggestionTarget find(Context context, String source, UUID id) { + if (providersMap.containsKey(source)) { + return providersMap.get(source).findTarget(context, id); + } else { + return null; + } + } + + @Override + public long countAll(Context context, String source) { + if (providersMap.containsKey(source)) { + return providersMap.get(source).countAllTargets(context); + } else { + return 0; + } + } + + @Override + public List findAllTargets(Context context, String source, int pageSize, long offset) { + if (providersMap.containsKey(source)) { + return providersMap.get(source).findAllTargets(context, pageSize, offset); + } else { + return null; + } + } + + @Override + public long countAllByTarget(Context context, UUID target) { + int count = 0; + for (String provider : providersMap.keySet()) { + if (providersMap.get(provider).countUnprocessedSuggestionByTarget(context, target) > 0) { + count++; + } + } + return count; + } + + @Override + public List findByTarget(Context context, UUID target, int pageSize, long offset) { + List fullSourceTargets = new ArrayList(); + for (String source : providersMap.keySet()) { + SuggestionTarget sTarget = providersMap.get(source).findTarget(context, target); + if (sTarget != null && sTarget.getTotal() > 0) { + fullSourceTargets.add(sTarget); + } + } + fullSourceTargets.sort(new Comparator() { + @Override + public int compare(SuggestionTarget arg0, SuggestionTarget arg1) { + return -(arg0.getTotal() - arg1.getTotal()); + } + } + ); + return fullSourceTargets.stream().skip(offset).limit(pageSize).collect(Collectors.toList()); + } + + @Override + public long countSources(Context context) { + return providersMap.size(); + } + + @Override + public SuggestionSource findSource(Context context, String source) { + if (providersMap.containsKey(source)) { + SuggestionSource ssource = new SuggestionSource(source); + ssource.setTotal((int) providersMap.get(source).countAllTargets(context)); + return ssource; + } else { + return null; + } + } + + @Override + public List findAllSources(Context context, int pageSize, long offset) { + List fullSources = getSources(context).stream().skip(offset).limit(pageSize) + .collect(Collectors.toList()); + return fullSources; + } + + private List getSources(Context context) { + List results = new ArrayList(); + for (String source : providersMap.keySet()) { + SuggestionSource ssource = new SuggestionSource(source); + ssource.setTotal((int) providersMap.get(source).countAllTargets(context)); + results.add(ssource); + } + return results; + } + + @Override + public long countAllByTargetAndSource(Context context, String source, UUID target) { + if (providersMap.containsKey(source)) { + return providersMap.get(source).countUnprocessedSuggestionByTarget(context, target); + } + return 0; + } + + @Override + public List findByTargetAndSource(Context context, UUID target, String source, int pageSize, + long offset, boolean ascending) { + if (providersMap.containsKey(source)) { + return providersMap.get(source).findAllUnprocessedSuggestions(context, target, pageSize, offset, ascending); + } + return null; + } + + @Override + public Suggestion findUnprocessedSuggestion(Context context, String id) { + String source = null; + UUID target = null; + String idPart = null; + String[] split; + try { + split = id.split(":", 3); + source = split[0]; + target = UUID.fromString(split[1]); + idPart = split[2]; + } catch (Exception e) { + log.warn("findSuggestion got an invalid id " + id + ", return null"); + return null; + } + if (split.length != 3) { + return null; + } + if (providersMap.containsKey(source)) { + return providersMap.get(source).findUnprocessedSuggestion(context, target, idPart); + } + return null; + } + + @Override + public void rejectSuggestion(Context context, String id) { + String source = null; + UUID target = null; + String idPart = null; + String[] split; + try { + split = id.split(":", 3); + source = split[0]; + target = UUID.fromString(split[1]); + idPart = split[2]; + } catch (Exception e) { + log.warn("rejectSuggestion got an invalid id " + id + ", doing nothing"); + return; + } + if (split.length != 3) { + return; + } + if (providersMap.containsKey(source)) { + providersMap.get(source).rejectSuggestion(context, target, idPart); + } + + } +} diff --git a/dspace-api/src/main/java/org/dspace/app/suggestion/SuggestionSource.java b/dspace-api/src/main/java/org/dspace/app/suggestion/SuggestionSource.java new file mode 100644 index 0000000000..b9df687dec --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/suggestion/SuggestionSource.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.suggestion; + +/** + * + * + * @author Andrea Bollini (andrea.bollini at 4science.it) + */ +public class SuggestionSource { + + private String name; + + private int total; + + public SuggestionSource() { + } + + /** + * Summarize the available suggestions from a source. + * + * @param the name must be not null + */ + public SuggestionSource(String name) { + super(); + this.name = name; + } + + public String getID() { + return name; + } + + public int getTotal() { + return total; + } + + public void setTotal(int total) { + this.total = total; + } + +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/app/suggestion/SuggestionTarget.java b/dspace-api/src/main/java/org/dspace/app/suggestion/SuggestionTarget.java new file mode 100644 index 0000000000..985d398d71 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/suggestion/SuggestionTarget.java @@ -0,0 +1,71 @@ +/** + * The contents of this file are subject to the license and 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.suggestion; + +import org.dspace.content.Item; + +/** + * + * + * @author Andrea Bollini (andrea.bollini at 4science.it) + */ +public class SuggestionTarget { + + private Item target; + + private String source; + + private int total; + + public SuggestionTarget() { + } + + /** + * Wrap a target person into a suggestion target. + * + * @param item must be not null + */ + public SuggestionTarget(Item item) { + super(); + this.target = item; + } + + /** + * The suggestion target uses the concatenation of the source and target uuid separated by colon as id + * + * @return the source:uuid of the wrapped item + */ + public String getID() { + return source + ":" + target.getID(); + } + + public Item getTarget() { + return target; + } + + public void setTarget(Item target) { + this.target = target; + } + + public String getSource() { + return source; + } + + public void setSource(String source) { + this.source = source; + } + + public int getTotal() { + return total; + } + + public void setTotal(int total) { + this.total = total; + } + +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/app/suggestion/SuggestionUtils.java b/dspace-api/src/main/java/org/dspace/app/suggestion/SuggestionUtils.java new file mode 100644 index 0000000000..30ced75fc9 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/suggestion/SuggestionUtils.java @@ -0,0 +1,111 @@ +/** + * The contents of this file are subject to the license and 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.suggestion; + +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import org.apache.commons.lang3.StringUtils; +import org.dspace.external.model.ExternalDataObject; + +/** + * This utility class provides convenient methods to deal with the + * {@link ExternalDataObject} for the purpose of the Suggestion framework + * + * @author Andrea Bollini (andrea.bollini at 4science.it) + */ +public class SuggestionUtils { + private SuggestionUtils() { + } + /** + * This method receive an ExternalDataObject and a metadatum key. + * It return only the values of the Metadata associated with the key. + * + * @param record the ExternalDataObject to extract metadata from + * @param schema schema of the searching record + * @param element element of the searching record + * @param qualifier qualifier of the searching record + * @return value of the first matching record + */ + public static List getAllEntriesByMetadatum(ExternalDataObject record, String schema, String element, + String qualifier) { + return record.getMetadata().stream() + .filter(x -> + StringUtils.equals(x.getSchema(), schema) + && StringUtils.equals(x.getElement(), element) + && StringUtils.equals(x.getQualifier(), qualifier)) + .map(x -> x.getValue()).collect(Collectors.toList()); + } + + /** + * This method receive an ExternalDataObject and a metadatum key. + * It return only the values of the Metadata associated with the key. + * + * @param record the ExternalDataObject to extract metadata from + * @param metadataFieldKey the metadata field key (i.e. dc.title or dc.contributor.author), + * the jolly char is not supported + * @return value of the first matching record + */ + public static List getAllEntriesByMetadatum(ExternalDataObject record, String metadataFieldKey) { + if (metadataFieldKey == null) { + return Collections.EMPTY_LIST; + } + String[] fields = metadataFieldKey.split("\\."); + String schema = fields[0]; + String element = fields[1]; + String qualifier = null; + if (fields.length == 3) { + qualifier = fields[2]; + } + return getAllEntriesByMetadatum(record, schema, element, qualifier); + } + + /** + * This method receive and ExternalDataObject and a metadatum key. + * It return only the value of the first Metadatum from the list associated with the key. + * + * @param record the ExternalDataObject to extract metadata from + * @param schema schema of the searching record + * @param element element of the searching record + * @param qualifier qualifier of the searching record + * @return value of the first matching record + */ + public static String getFirstEntryByMetadatum(ExternalDataObject record, String schema, String element, + String qualifier) { + return record.getMetadata().stream() + .filter(x -> + StringUtils.equals(x.getSchema(), schema) + && StringUtils.equals(x.getElement(), element) + && StringUtils.equals(x.getQualifier(), qualifier)) + .map(x -> x.getValue()).findFirst().orElse(null); + } + + /** + * This method receive and ExternalDataObject and a metadatum key. + * It return only the value of the first Metadatum from the list associated with the key. + * + * @param record the ExternalDataObject to extract metadata from + * @param metadataFieldKey the metadata field key (i.e. dc.title or dc.contributor.author), + * the jolly char is not supported + * @return value of the first matching record + */ + public static String getFirstEntryByMetadatum(ExternalDataObject record, String metadataFieldKey) { + if (metadataFieldKey == null) { + return null; + } + String[] fields = metadataFieldKey.split("\\."); + String schema = fields[0]; + String element = fields[1]; + String qualifier = null; + if (fields.length == 3) { + qualifier = fields[2]; + } + return getFirstEntryByMetadatum(record, schema, element, qualifier); + } +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/app/suggestion/oaire/AuthorNamesScorer.java b/dspace-api/src/main/java/org/dspace/app/suggestion/oaire/AuthorNamesScorer.java new file mode 100644 index 0000000000..f429ae017c --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/suggestion/oaire/AuthorNamesScorer.java @@ -0,0 +1,147 @@ +/** + * The contents of this file are subject to the license and 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.suggestion.oaire; + +import static org.dspace.app.suggestion.SuggestionUtils.getAllEntriesByMetadatum; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; +import java.util.Optional; +import java.util.stream.Collectors; + +import com.ibm.icu.text.CharsetDetector; +import com.ibm.icu.text.CharsetMatch; +import com.ibm.icu.text.Normalizer; +import org.apache.commons.lang3.StringUtils; +import org.dspace.app.suggestion.SuggestionEvidence; +import org.dspace.content.Item; +import org.dspace.content.MetadataValue; +import org.dspace.content.service.ItemService; +import org.dspace.external.model.ExternalDataObject; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Implementation of {@see org.dspace.app.suggestion.oaire.EvidenceScorer} which evaluate ImportRecords + * based on Author's name. + * + * @author Andrea Bollini (andrea.bollini at 4science dot it) + * @author Pasquale Cavallo (pasquale.cavallo at 4science dot it) + * + */ +public class AuthorNamesScorer implements EvidenceScorer { + + private List contributorMetadata; + + private List names; + + @Autowired + private ItemService itemService; + + /** + * returns the metadata key of the Item which to base the filter on + * @return metadata key + */ + public List getContributorMetadata() { + return contributorMetadata; + } + + /** + * set the metadata key of the Item which to base the filter on + * @return metadata key + */ + public void setContributorMetadata(List contributorMetadata) { + this.contributorMetadata = contributorMetadata; + } + + /** + * return the metadata key of ImportRecord which to base the filter on + * @return + */ + public List getNames() { + return names; + } + + /** + * set the metadata key of ImportRecord which to base the filter on + */ + public void setNames(List names) { + this.names = names; + } + + /** + * Method which is responsible to evaluate ImportRecord based on authors name. + * This method extract the researcher name from Item using contributorMetadata fields + * and try to match them with values extract from ImportRecord using metadata keys defined + * in names. + * ImportRecords which don't match will be discarded. + * + * @param importRecord the import record to check + * @param researcher DSpace item + * @return the generated evidence or null if the record must be discarded + */ + @Override + public SuggestionEvidence computeEvidence(Item researcher, ExternalDataObject importRecord) { + List names = searchMetadataValues(researcher); + int maxNameLenght = names.stream().mapToInt(n -> n[0].length()).max().orElse(1); + List metadataAuthors = new ArrayList<>(); + for (String contributorMetadatum : contributorMetadata) { + metadataAuthors.addAll(getAllEntriesByMetadatum(importRecord, contributorMetadatum)); + } + List normalizedMetadataAuthors = metadataAuthors.stream().map(x -> normalize(x)) + .collect(Collectors.toList()); + int idx = 0; + for (String nMetadataAuthor : normalizedMetadataAuthors) { + Optional found = names.stream() + .filter(a -> StringUtils.equalsIgnoreCase(a[0], nMetadataAuthor)).findFirst(); + if (found.isPresent()) { + return new SuggestionEvidence(this.getClass().getSimpleName(), + 100 * ((double) nMetadataAuthor.length() / (double) maxNameLenght), + "The author " + metadataAuthors.get(idx) + " at position " + (idx + 1) + + " in the authors list matches the name " + found.get()[1] + + " in the researcher profile"); + } + idx++; + } + return null; + } + + /** + * Return list of Item metadata values starting from metadata keys defined in class level variable names. + * + * @param researcher DSpace item + * @return list of metadata values + */ + private List searchMetadataValues(Item researcher) { + List authors = new ArrayList(); + for (String name : names) { + List values = itemService.getMetadataByMetadataString(researcher, name); + if (values != null) { + for (MetadataValue v : values) { + authors.add(new String[] {normalize(v.getValue()), v.getValue()}); + } + } + } + return authors; + } + + private String normalize(String value) { + String norm = Normalizer.normalize(value, Normalizer.NFD); + CharsetDetector cd = new CharsetDetector(); + cd.setText(value.getBytes()); + CharsetMatch detect = cd.detect(); + if (detect != null && detect.getLanguage() != null) { + norm = norm.replaceAll("[^\\p{L}]", " ").toLowerCase(new Locale(detect.getLanguage())); + } else { + norm = norm.replaceAll("[^\\p{L}]", " ").toLowerCase(); + } + return Arrays.asList(norm.split("\\s+")).stream().sorted().collect(Collectors.joining()); + } + +} diff --git a/dspace-api/src/main/java/org/dspace/app/suggestion/oaire/DateScorer.java b/dspace-api/src/main/java/org/dspace/app/suggestion/oaire/DateScorer.java new file mode 100644 index 0000000000..2a5f37e12a --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/suggestion/oaire/DateScorer.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.suggestion.oaire; + +import java.util.Calendar; +import java.util.Collections; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.List; + +import org.dspace.app.suggestion.SuggestionEvidence; +import org.dspace.app.suggestion.SuggestionUtils; +import org.dspace.content.Item; +import org.dspace.content.MetadataValue; +import org.dspace.content.service.ItemService; +import org.dspace.external.model.ExternalDataObject; +import org.dspace.util.MultiFormatDateParser; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Implementation of {@see org.dspace.app.suggestion.oaire.EvidenceScorer} which evaluate ImportRecords + * based on the distance from a date extracted from the ResearcherProfile (birthday / graduation date) + * + * @author Andrea Bollini (andrea.bollini at 4science dot it) + * + */ +public class DateScorer implements EvidenceScorer { + + private String birthDateMetadata; + + private String educationDateMetadata; + + private String minDateMetadata; + + private String maxDateMetadata; + + private int birthDateDelta = 20; + private int birthDateRange = 50; + + private int educationDateDelta = -3; + private int educationDateRange = 50; + + @Autowired + private ItemService itemService; + + private String publicationDateMetadata; + + public void setItemService(ItemService itemService) { + this.itemService = itemService; + } + + public void setBirthDateMetadata(String birthDate) { + this.birthDateMetadata = birthDate; + } + + public String getBirthDateMetadata() { + return birthDateMetadata; + } + + public void setEducationDateMetadata(String educationDate) { + this.educationDateMetadata = educationDate; + } + + public String getEducationDateMetadata() { + return educationDateMetadata; + } + + public void setBirthDateDelta(int birthDateDelta) { + this.birthDateDelta = birthDateDelta; + } + + public void setBirthDateRange(int birthDateRange) { + this.birthDateRange = birthDateRange; + } + + public void setEducationDateDelta(int educationDateDelta) { + this.educationDateDelta = educationDateDelta; + } + + public void setEducationDateRange(int educationDateRange) { + this.educationDateRange = educationDateRange; + } + + public void setMaxDateMetadata(String maxDateMetadata) { + this.maxDateMetadata = maxDateMetadata; + } + + public void setMinDateMetadata(String minDateMetadata) { + this.minDateMetadata = minDateMetadata; + } + + public void setPublicationDateMetadata(String publicationDateMetadata) { + this.publicationDateMetadata = publicationDateMetadata; + } + + /** + * Method which is responsible to evaluate ImportRecord based on the publication date. + * ImportRecords which have a date outside the defined or calculated expected range will be discarded. + * + * @param importRecord the ExternalDataObject to check + * @param researcher DSpace item + * @return the generated evidence or null if the record must be discarded + */ + @Override + public SuggestionEvidence computeEvidence(Item researcher, ExternalDataObject importRecord) { + Integer[] range = calculateRange(researcher); + if (range == null) { + return new SuggestionEvidence(this.getClass().getSimpleName(), + 0, + "No assumption was possible about the publication year range. " + + "Please consider to set a min/max date in the profile, specify the birthday " + + "or education achievements"); + } else { + String optDate = SuggestionUtils.getFirstEntryByMetadatum(importRecord, publicationDateMetadata); + int year = getYear(optDate); + if (year > 0) { + if ((range[0] == null || year >= range[0]) && + (range[1] == null || year <= range[1])) { + return new SuggestionEvidence(this.getClass().getSimpleName(), + 10, + "The publication date is within the expected range [" + range[0] + ", " + + range[1] + "]"); + } else { + // outside the range, discard the suggestion + return null; + } + } else { + return new SuggestionEvidence(this.getClass().getSimpleName(), + 0, + "No assumption was possible as the publication date is " + (optDate != null + ? "unprocessable [" + optDate + "]" + : "unknown")); + } + } + } + + private Integer[] calculateRange(Item researcher) { + String minDateStr = getSingleValue(researcher, minDateMetadata); + int minYear = getYear(minDateStr); + String maxDateStr = getSingleValue(researcher, maxDateMetadata); + int maxYear = getYear(maxDateStr); + if (minYear > 0 && maxYear > 0) { + return new Integer[] { minYear, maxYear }; + } else { + String birthDateStr = getSingleValue(researcher, birthDateMetadata); + int birthDateYear = getYear(birthDateStr); + int educationDateYear = getListMetadataValues(researcher, educationDateMetadata) + .stream() + .mapToInt(x -> getYear(x.getValue())) + .filter(d -> d > 0) + .min().orElse(-1); + if (educationDateYear > 0) { + return new Integer[] { + minYear > 0 ? minYear : educationDateYear + educationDateDelta, + maxYear > 0 ? maxYear : educationDateYear + educationDateDelta + educationDateRange + }; + } else if (birthDateYear > 0) { + return new Integer[] { + minYear > 0 ? minYear : birthDateYear + birthDateDelta, + maxYear > 0 ? maxYear : birthDateYear + birthDateDelta + birthDateRange + }; + } else { + return null; + } + } + } + + private List getListMetadataValues(Item researcher, String metadataKey) { + if (metadataKey != null) { + return itemService.getMetadataByMetadataString(researcher, metadataKey); + } else { + return Collections.EMPTY_LIST; + } + } + + private String getSingleValue(Item researcher, String metadataKey) { + if (metadataKey != null) { + return itemService.getMetadata(researcher, metadataKey); + } + return null; + } + + private int getYear(String birthDateStr) { + int birthDateYear = -1; + if (birthDateStr != null) { + Date birthDate = MultiFormatDateParser.parse(birthDateStr); + if (birthDate != null) { + Calendar calendar = new GregorianCalendar(); + calendar.setTime(birthDate); + birthDateYear = calendar.get(Calendar.YEAR); + } + } + return birthDateYear; + } +} diff --git a/dspace-api/src/main/java/org/dspace/app/suggestion/oaire/EvidenceScorer.java b/dspace-api/src/main/java/org/dspace/app/suggestion/oaire/EvidenceScorer.java new file mode 100644 index 0000000000..9df7621b46 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/suggestion/oaire/EvidenceScorer.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.suggestion.oaire; + +import org.dspace.app.suggestion.SuggestionEvidence; +import org.dspace.content.Item; +import org.dspace.external.model.ExternalDataObject; + +/** + * Interface used in {@see org.dspace.app.suggestion.oaire.OAIREPublicationApproverServiceImpl} + * to construct filtering pipeline. + * + * For each EvidenceScorer, the service call computeEvidence method. + * + * @author Andrea Bollini (andrea.bollini at 4science dot it) + * @author Pasquale Cavallo (pasquale.cavallo at 4science dot it) + * + */ +public interface EvidenceScorer { + + /** + * Method to compute the suggestion evidence of an ImportRecord, a null evidence + * would lead the record to be discarded. + * + * @param researcher DSpace item + * @param importRecord the record to evaluate + * @return the generated suggestion evidence or null if the record should be + * discarded + */ + public SuggestionEvidence computeEvidence(Item researcher, ExternalDataObject importRecords); + +} diff --git a/dspace-api/src/main/java/org/dspace/app/suggestion/oaire/OAIREPublicationLoader.java b/dspace-api/src/main/java/org/dspace/app/suggestion/oaire/OAIREPublicationLoader.java new file mode 100644 index 0000000000..d8a20ed3a8 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/suggestion/oaire/OAIREPublicationLoader.java @@ -0,0 +1,240 @@ +/** + * The contents of this file are subject to the license and 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.suggestion.oaire; + +import static org.dspace.app.suggestion.SuggestionUtils.getAllEntriesByMetadatum; +import static org.dspace.app.suggestion.SuggestionUtils.getFirstEntryByMetadatum; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.lang3.StringUtils; +import org.apache.solr.client.solrj.SolrServerException; +import org.dspace.app.suggestion.SolrSuggestionProvider; +import org.dspace.app.suggestion.Suggestion; +import org.dspace.app.suggestion.SuggestionEvidence; +import org.dspace.content.Item; +import org.dspace.content.dto.MetadataValueDTO; +import org.dspace.core.Context; +import org.dspace.external.model.ExternalDataObject; +import org.dspace.external.provider.ExternalDataProvider; +import org.dspace.services.ConfigurationService; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Class responsible to load and manage ImportRecords from OpenAIRE + * + * @author Pasquale Cavallo (pasquale.cavallo at 4science dot it) + * + */ +public class OAIREPublicationLoader extends SolrSuggestionProvider { + + private List names; + + private ExternalDataProvider primaryProvider; + + private List otherProviders; + + @Autowired + private ConfigurationService configurationService; + + private List pipeline; + + public void setPrimaryProvider(ExternalDataProvider primaryProvider) { + this.primaryProvider = primaryProvider; + } + + public void setOtherProviders(List otherProviders) { + this.otherProviders = otherProviders; + } + + /** + * Set the pipeline of Approver + * @param pipeline list Approver + */ + public void setPipeline(List pipeline) { + this.pipeline = pipeline; + } + + /** + * This method filter a list of ImportRecords using a pipeline of AuthorNamesApprover + * and return a filtered list of ImportRecords. + * + * @see org.dspace.app.suggestion.oaire.AuthorNamesScorer + * @param researcher the researcher Item + * @param importRecords List of import record + * @return a list of filtered import records + */ + public List reduceAndTransform(Item researcher, List importRecords) { + List results = new ArrayList<>(); + for (ExternalDataObject r : importRecords) { + boolean skip = false; + List evidences = new ArrayList(); + for (EvidenceScorer authorNameApprover : pipeline) { + SuggestionEvidence evidence = authorNameApprover.computeEvidence(researcher, r); + if (evidence != null) { + evidences.add(evidence); + } else { + skip = true; + break; + } + } + if (!skip) { + Suggestion suggestion = translateImportRecordToSuggestion(researcher, r); + suggestion.getEvidences().addAll(evidences); + results.add(suggestion); + } + } + return results; + } + + /** + * Save a List of ImportRecord into Solr. + * ImportRecord will be translate into a SolrDocument by the method translateImportRecordToSolrDocument. + * + * @param context the DSpace Context + * @param researcher a DSpace Item + * @throws SolrServerException + * @throws IOException + */ + public void importAuthorRecords(Context context, Item researcher) + throws SolrServerException, IOException { + List metadata = getImportRecords(researcher); + List records = reduceAndTransform(researcher, metadata); + for (Suggestion record : records) { + solrSuggestionStorageService.addSuggestion(record, false, false); + } + solrSuggestionStorageService.commit(); + } + + /** + * Translate an ImportRecord into a Suggestion + * @param item DSpace item + * @param record ImportRecord + * @return Suggestion + */ + private Suggestion translateImportRecordToSuggestion(Item item, ExternalDataObject record) { + String openAireId = record.getId(); + Suggestion suggestion = new Suggestion(getSourceName(), item, openAireId); + suggestion.setDisplay(getFirstEntryByMetadatum(record, "dc", "title", null)); + suggestion.getMetadata().add( + new MetadataValueDTO("dc", "title", null, null, getFirstEntryByMetadatum(record, "dc", "title", null))); + suggestion.getMetadata().add(new MetadataValueDTO("dc", "date", "issued", null, + getFirstEntryByMetadatum(record, "dc", "date", "issued"))); + suggestion.getMetadata().add(new MetadataValueDTO("dc", "description", "abstract", null, + getFirstEntryByMetadatum(record, "dc", "description", "abstract"))); + suggestion.setExternalSourceUri(configurationService.getProperty("dspace.server.url") + + "/api/integration/externalsources/" + primaryProvider.getSourceIdentifier() + "/entryValues/" + + openAireId); + for (String o : getAllEntriesByMetadatum(record, "dc", "source", null)) { + suggestion.getMetadata().add(new MetadataValueDTO("dc", "source", null, null, o)); + } + for (String o : getAllEntriesByMetadatum(record, "dc", "contributor", "author")) { + suggestion.getMetadata().add(new MetadataValueDTO("dc", "contributor", "author", null, o)); + } + return suggestion; + } + + public List getNames() { + return names; + } + + public void setNames(List names) { + this.names = names; + } + + /** + * Load metadata from OpenAIRE using the import service. The service use the value + * get from metadata key defined in class level variable names as author to query OpenAIRE. + * + * @see org.dspace.importer.external.openaire.service.OpenAireImportMetadataSourceServiceImpl + * @param researcher item to extract metadata from + * @return list of ImportRecord + */ + private List getImportRecords(Item researcher) { + List searchValues = searchMetadataValues(researcher); + List matchingRecords = new ArrayList<>(); + for (String searchValue : searchValues) { + matchingRecords.addAll(primaryProvider.searchExternalDataObjects(searchValue, 0, 9999)); + } + List toReturn = removeDuplicates(matchingRecords); + return toReturn; + } + + /** + * This method remove duplicates from importRecords list. + * An element is a duplicate if in the list exist another element + * with the same value of the metadatum 'dc.identifier.other' + * + * @param importRecords list of ImportRecord + * @return list of ImportRecords without duplicates + */ + private List removeDuplicates(List importRecords) { + List filteredRecords = new ArrayList<>(); + for (ExternalDataObject currentRecord : importRecords) { + if (!isDuplicate(currentRecord, filteredRecords)) { + filteredRecords.add(currentRecord); + } + } + return filteredRecords; + } + + + /** + * Check if the ImportRecord is already present in the list. + * The comparison is made on the value of metadatum with key 'dc.identifier.other' + * + * @param dto An importRecord instance + * @param importRecords a list of importRecord + * @return true if dto is already present in importRecords, false otherwise + */ + private boolean isDuplicate(ExternalDataObject dto, List importRecords) { + String currentItemId = dto.getId(); + if (currentItemId == null) { + return true; + } + for (ExternalDataObject importRecord : importRecords) { + if (currentItemId.equals(importRecord.getId())) { + return true; + } + } + return false; + } + + + /** + * Return list of Item metadata values starting from metadata keys defined in class level variable names. + * + * @param researcher DSpace item + * @return list of metadata values + */ + private List searchMetadataValues(Item researcher) { + List authors = new ArrayList(); + for (String name : names) { + String value = itemService.getMetadata(researcher, name); + if (value != null) { + authors.add(value); + } + } + return authors; + } + + @Override + protected boolean isExternalDataObjectPotentiallySuggested(Context context, ExternalDataObject externalDataObject) { + if (StringUtils.equals(externalDataObject.getSource(), primaryProvider.getSourceIdentifier())) { + return true; + } else if (otherProviders != null) { + return otherProviders.stream() + .anyMatch(x -> StringUtils.equals(externalDataObject.getSource(), x.getSourceIdentifier())); + } else { + return false; + } + } + +} diff --git a/dspace-api/src/main/java/org/dspace/external/service/impl/ExternalDataServiceImpl.java b/dspace-api/src/main/java/org/dspace/external/service/impl/ExternalDataServiceImpl.java index f91ea00cac..59cbe4f9d0 100644 --- a/dspace-api/src/main/java/org/dspace/external/service/impl/ExternalDataServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/external/service/impl/ExternalDataServiceImpl.java @@ -13,6 +13,8 @@ import java.util.Optional; import java.util.stream.Collectors; import org.apache.logging.log4j.Logger; +import org.dspace.app.suggestion.SuggestionProvider; +import org.dspace.app.suggestion.SuggestionService; import org.dspace.authorize.AuthorizeException; import org.dspace.content.Collection; import org.dspace.content.Item; @@ -44,6 +46,9 @@ public class ExternalDataServiceImpl implements ExternalDataService { @Autowired private WorkspaceItemService workspaceItemService; + @Autowired + private SuggestionService suggestionService; + @Override public Optional getExternalDataObject(String source, String id) { ExternalDataProvider provider = getExternalDataProvider(source); @@ -105,6 +110,16 @@ public class ExternalDataServiceImpl implements ExternalDataService { log.info(LogHelper.getHeader(context, "create_item_from_externalDataObject", "Created item" + "with id: " + item.getID() + " from source: " + externalDataObject.getSource() + " with identifier: " + externalDataObject.getId())); + try { + List providers = suggestionService.getSuggestionProviders(); + if (providers != null) { + for (SuggestionProvider p : providers) { + p.flagRelatedSuggestionsAsProcessed(context, externalDataObject); + } + } + } catch (Exception e) { + log.error("Got problems with the solr suggestion storage service: " + e.getMessage(), e); + } return workspaceItem; } diff --git a/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleXpathDateFormatMetadataContributor.java b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleXpathDateFormatMetadataContributor.java new file mode 100644 index 0000000000..bbb4e7311e --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleXpathDateFormatMetadataContributor.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.importer.external.metadatamapping.contributor; + +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; + +import org.apache.axiom.om.OMAttribute; +import org.apache.axiom.om.OMElement; +import org.apache.axiom.om.OMText; +import org.apache.axiom.om.xpath.AXIOMXPath; +import org.dspace.importer.external.metadatamapping.MetadataFieldConfig; +import org.dspace.importer.external.metadatamapping.MetadatumDTO; +import org.jaxen.JaxenException; + + +public class SimpleXpathDateFormatMetadataContributor extends SimpleXpathMetadatumContributor { + + + private DateFormat dateFormatFrom; + private DateFormat dateFormatTo; + + public void setDateFormatFrom(String dateFormatFrom) { + this.dateFormatFrom = new SimpleDateFormat(dateFormatFrom); + } + + public void setDateFormatTo(String dateFormatTo) { + this.dateFormatTo = new SimpleDateFormat(dateFormatTo); + } + + @Override + public Collection contributeMetadata(OMElement t) { + List values = new LinkedList<>(); + try { + AXIOMXPath xpath = new AXIOMXPath(query); + for (String ns : prefixToNamespaceMapping.keySet()) { + xpath.addNamespace(prefixToNamespaceMapping.get(ns), ns); + } + List nodes = xpath.selectNodes(t); + for (Object el : nodes) { + if (el instanceof OMElement) { + values.add(getMetadatum(field, ((OMElement) el).getText())); + } else if (el instanceof OMAttribute) { + values.add(getMetadatum(field, ((OMAttribute) el).getAttributeValue())); + } else if (el instanceof String) { + values.add(getMetadatum(field, (String) el)); + } else if (el instanceof OMText) { + values.add(metadataFieldMapping.toDCValue(field, ((OMText) el).getText())); + } else { + System.err.println("node of type: " + el.getClass()); + } + } + return values; + } catch (JaxenException e) { + System.err.println(query); + throw new RuntimeException(e); + } + } + + private MetadatumDTO getMetadatum(MetadataFieldConfig field, String value) { + MetadatumDTO dcValue = new MetadatumDTO(); + if (field == null) { + return null; + } + try { + dcValue.setValue(dateFormatTo.format(dateFormatFrom.parse(value))); + } catch (ParseException e) { + dcValue.setValue(value); + } + dcValue.setElement(field.getElement()); + dcValue.setQualifier(field.getQualifier()); + dcValue.setSchema(field.getSchema()); + return dcValue; + } +} diff --git a/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleXpathMetadatumContributor.java b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleXpathMetadatumContributor.java index 87cdbfa6ed..8b4c959543 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleXpathMetadatumContributor.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleXpathMetadatumContributor.java @@ -31,7 +31,7 @@ import org.springframework.beans.factory.annotation.Autowired; * @author Roeland Dillen (roeland at atmire dot com) */ public class SimpleXpathMetadatumContributor implements MetadataContributor { - private MetadataFieldConfig field; + protected MetadataFieldConfig field; private static final Logger log = LoggerFactory.getLogger(SimpleXpathMetadatumContributor.class); @@ -44,7 +44,7 @@ public class SimpleXpathMetadatumContributor implements MetadataContributor> metadataFieldMapping; + protected MetadataFieldMapping> metadataFieldMapping; /** * Return metadataFieldMapping @@ -76,7 +76,7 @@ public class SimpleXpathMetadatumContributor implements MetadataContributor prefixToNamespaceMapping; + protected Map prefixToNamespaceMapping; /** * Initialize SimpleXpathMetadatumContributor with a query, prefixToNamespaceMapping and MetadataFieldConfig @@ -100,7 +100,7 @@ public class SimpleXpathMetadatumContributor implements MetadataContributor + implements QuerySource { + + private String baseAddress; + + private WebTarget webTarget; + + private String queryParam; + + @Override + public String getImportSource() { + return "openaire"; + } + + /** + * The string that identifies this import implementation. Preferable a URI + * + * @return the identifying uri + */ + @Override + public ImportRecord getRecord(String id) throws MetadataSourceException { + return retry(new SearchByIdCallable(id)); + } + + /** + * The string that identifies this import implementation. Preferable a URI + * + * @return the identifying uri + */ + @Override + public ImportRecord getRecord(Query query) throws MetadataSourceException { + return retry(new SearchByIdCallable(query)); + } + + + /** + * Find the number of records matching a query; + * + * @param query a query string to base the search on. + * @return the sum of the matching records over this import source + * @throws MetadataSourceException if the underlying methods throw any exception. + */ + @Override + public int getRecordsCount(String query) throws MetadataSourceException { + return retry(new CountByQueryCallable(query)); + } + + /** + * Find the number of records matching a query; + * + * @param query a query object to base the search on. + * @return the sum of the matching records over this import source + * @throws MetadataSourceException if the underlying methods throw any exception. + */ + @Override + public int getRecordsCount(Query query) throws MetadataSourceException { + return retry(new CountByQueryCallable(query)); + } + + /** + * Find the number of records matching a string query. Supports pagination + * + * @param query a query string to base the search on. + * @param start offset to start at + * @param count number of records to retrieve. + * @return a set of records. Fully transformed. + * @throws MetadataSourceException if the underlying methods throw any exception. + */ + @Override + public Collection getRecords(String query, int start, int count) throws MetadataSourceException { + return retry(new SearchByQueryCallable(query, start, count)); + } + + + /** + * Find records based on a object query. + * + * @param query a query object to base the search on. + * @return a set of records. Fully transformed. + * @throws MetadataSourceException if the underlying methods throw any exception. + */ + @Override + public Collection getRecords(Query query) throws MetadataSourceException { + return retry(new SearchByQueryCallable(query)); + } + + @Override + public Collection findMatchingRecords(Query query) throws MetadataSourceException { + throw new MethodNotFoundException("This method is not implemented for OpenAIRE"); + } + + @Override + public Collection findMatchingRecords(Item item) throws MetadataSourceException { + throw new MethodNotFoundException("This method is not implemented for OpenAIRE"); + } + + /** + * Set the baseAddress to this object + * + * @param baseAddress The String object that represents the baseAddress of this object + */ + public void setBaseAddress(String baseAddress) { + this.baseAddress = baseAddress; + } + + /** + * Return the baseAddress set to this object + * + * @return The String object that represents the baseAddress of this object + */ + public String getBaseAddress() { + return baseAddress; + } + + /** + * Set the name of the query param, this correspond to the index used (title, author) + * + * @param queryParam on which index make the query + */ + public void setQueryParam(String queryParam) { + this.queryParam = queryParam; + } + + /** + * Get the name of the query param for the rest call + * + * @return the name of the query param, i.e. the index (title, author) to use + */ + public String getQueryParam() { + return queryParam; + } + /** + * Initialize the class + * + * @throws Exception on generic exception + */ + @Override + public void init() throws Exception { + Client client = ClientBuilder.newClient(); + if (baseAddress == null) { + baseAddress = "http://api.openaire.eu/search/publications"; + } + if (queryParam == null) { + queryParam = "title"; + } + webTarget = client.target(baseAddress); + } + + public class SearchByIdCallable implements Callable { + + String id = null; + + public SearchByIdCallable(String id) { + this.id = id; + } + + public SearchByIdCallable(Query query) { + this.id = query.getParameterAsClass("id", String.class); + } + + @Override + public ImportRecord call() throws Exception { + List results = new ArrayList(); + WebTarget localTarget = webTarget.queryParam("openairePublicationID", id); + Invocation.Builder invocationBuilder = localTarget.request(); + Response response = invocationBuilder.get(); + if (response.getStatus() == 200) { + String responseString = response.readEntity(String.class); + List omElements = splitToRecords(responseString); + if (omElements != null) { + for (OMElement record : omElements) { + results.add(filterMultipleTitles(transformSourceRecords(record))); + } + } + return results != null ? results.get(0) : null; + } else { + return null; + } + } + } + + public class CountByQueryCallable implements Callable { + + String q; + + public CountByQueryCallable(String query) { + q = query; + } + + public CountByQueryCallable(Query query) { + q = query.getParameterAsClass("query", String.class); + } + + @Override + public Integer call() throws Exception { + WebTarget localTarget = webTarget.queryParam(queryParam, q); + Invocation.Builder invocationBuilder = localTarget.request(); + Response response = invocationBuilder.get(); + if (response.getStatus() == 200) { + String responseString = response.readEntity(String.class); + OMXMLParserWrapper records = OMXMLBuilderFactory.createOMBuilder(new StringReader(responseString)); + OMElement element = records.getDocumentElement(); + AXIOMXPath xpath = null; + try { + xpath = new AXIOMXPath("/response/header/total"); + OMElement totalItem = (OMElement) xpath.selectSingleNode(element); + return totalItem != null ? Integer.parseInt(totalItem.getText()) : null; + } catch (JaxenException e) { + return 0; + } + } else { + return 0; + } + } + } + + public class SearchByQueryCallable implements Callable> { + + String q; + int page; + int count; + + public SearchByQueryCallable(String query, int start, int count) { + this.q = query; + this.page = start / count; + this.count = count; + } + + public SearchByQueryCallable(Query query) { + this.q = query.getParameterAsClass("query", String.class); + this.page = query.getParameterAsClass("start", Integer.class) / + query.getParameterAsClass("count", Integer.class); + this.count = query.getParameterAsClass("count", Integer.class); + } + + @Override + public List call() throws Exception { + WebTarget localTarget = webTarget.queryParam(queryParam, q); + localTarget = localTarget.queryParam("page", page + 1); + localTarget = localTarget.queryParam("size", count); + List results = new ArrayList(); + Invocation.Builder invocationBuilder = localTarget.request(); + Response response = invocationBuilder.get(); + if (response.getStatus() == 200) { + String responseString = response.readEntity(String.class); + List omElements = splitToRecords(responseString); + if (omElements != null) { + for (OMElement record : omElements) { + results.add(filterMultipleTitles(transformSourceRecords(record))); + } + } + } + return results; + } + } + + /** + * This method remove multiple titles occurrences + * + * @param transformSourceRecords + * @return ImportRecord with one or zero title + */ + private ImportRecord filterMultipleTitles(ImportRecord transformSourceRecords) { + List metadata = (List)transformSourceRecords.getValueList(); + ArrayList nextSourceRecord = new ArrayList<>(); + boolean found = false; + for (MetadatumDTO dto : metadata) { + if ("dc".equals(dto.getSchema()) && "title".equals(dto.getElement()) && dto.getQualifier() == null) { + if (!found) { + nextSourceRecord.add(dto); + found = true; + } + } else { + nextSourceRecord.add(dto); + } + } + return new ImportRecord(nextSourceRecord); + } + + private List splitToRecords(String recordsSrc) { + OMXMLParserWrapper records = OMXMLBuilderFactory.createOMBuilder(new StringReader(recordsSrc)); + OMElement element = records.getDocumentElement(); + AXIOMXPath xpath = null; + try { + xpath = new AXIOMXPath("/response/results/result"); + xpath.addNamespace("dri", "http://www.driver-repository.eu/namespace/dri"); + xpath.addNamespace("oaf", "http://namespace.openaire.eu/oaf"); + xpath.addNamespace("xsi", "http://www.w3.org/2001/XMLSchema-instance"); + List recordsList = xpath.selectNodes(element); + return recordsList; + } catch (JaxenException e) { + return null; + } + } + + + +} diff --git a/dspace-api/src/main/resources/spring/spring-dspace-addon-import-services.xml b/dspace-api/src/main/resources/spring/spring-dspace-addon-import-services.xml index 5e69ee9c42..00900f4bda 100644 --- a/dspace-api/src/main/resources/spring/spring-dspace-addon-import-services.xml +++ b/dspace-api/src/main/resources/spring/spring-dspace-addon-import-services.xml @@ -115,10 +115,18 @@ - - - - + + + + + + + + + diff --git a/dspace-api/src/main/resources/spring/spring-dspace-addon-suggestion-services.xml b/dspace-api/src/main/resources/spring/spring-dspace-addon-suggestion-services.xml new file mode 100644 index 0000000000..fb720137c4 --- /dev/null +++ b/dspace-api/src/main/resources/spring/spring-dspace-addon-suggestion-services.xml @@ -0,0 +1,23 @@ + + + + + + + + diff --git a/dspace-api/src/test/data/dspaceFolder/config/spring/api/external-services.xml b/dspace-api/src/test/data/dspaceFolder/config/spring/api/external-services.xml index ac163d3581..8a5f585335 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/spring/api/external-services.xml +++ b/dspace-api/src/test/data/dspaceFolder/config/spring/api/external-services.xml @@ -90,6 +90,8 @@ + + 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 5f86c73598..611a32ad79 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,8 @@ + + + diff --git a/dspace-api/src/test/data/dspaceFolder/config/spring/api/suggestions.xml b/dspace-api/src/test/data/dspaceFolder/config/spring/api/suggestions.xml new file mode 100644 index 0000000000..a3ae1cb875 --- /dev/null +++ b/dspace-api/src/test/data/dspaceFolder/config/spring/api/suggestions.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/dspace-api/src/test/java/org/dspace/app/suggestion/MockSolrSuggestionProvider.java b/dspace-api/src/test/java/org/dspace/app/suggestion/MockSolrSuggestionProvider.java new file mode 100644 index 0000000000..af890da455 --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/app/suggestion/MockSolrSuggestionProvider.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/ + */ +package org.dspace.app.suggestion; + +import org.apache.commons.lang3.StringUtils; +import org.dspace.core.Context; +import org.dspace.external.model.ExternalDataObject; + +public class MockSolrSuggestionProvider extends SolrSuggestionProvider { + + @Override + protected boolean isExternalDataObjectPotentiallySuggested(Context context, ExternalDataObject externalDataObject) { + return StringUtils.equals(MockSuggestionExternalDataSource.NAME, externalDataObject.getSource()); + } +} diff --git a/dspace-api/src/test/java/org/dspace/app/suggestion/MockSolrSuggestionStorageService.java b/dspace-api/src/test/java/org/dspace/app/suggestion/MockSolrSuggestionStorageService.java new file mode 100644 index 0000000000..1c843026d4 --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/app/suggestion/MockSolrSuggestionStorageService.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.suggestion; + +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 suggestion Core. + */ +@Service +public class MockSolrSuggestionStorageService extends SolrSuggestionStorageServiceImpl + implements InitializingBean, DisposableBean { + private MockSolrServer mockSolrServer; + + @Override + public void afterPropertiesSet() throws Exception { + mockSolrServer = new MockSolrServer("suggestion"); + solrSuggestionClient = 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/app/suggestion/MockSuggestionExternalDataSource.java b/dspace-api/src/test/java/org/dspace/app/suggestion/MockSuggestionExternalDataSource.java new file mode 100644 index 0000000000..cf0303debd --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/app/suggestion/MockSuggestionExternalDataSource.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.suggestion; + +import java.util.List; +import java.util.Optional; + +import org.apache.commons.codec.binary.StringUtils; +import org.dspace.core.Context; +import org.dspace.external.model.ExternalDataObject; +import org.dspace.external.provider.AbstractExternalDataProvider; +import org.dspace.services.RequestService; +import org.dspace.services.model.Request; +import org.dspace.utils.DSpace; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +public class MockSuggestionExternalDataSource extends AbstractExternalDataProvider { + public static final String NAME = "suggestion"; + + @Autowired + private SuggestionService suggestionService; + + @Override + public String getSourceIdentifier() { + return NAME; + } + + @Override + public Optional getExternalDataObject(String id) { + RequestService requestService = new DSpace().getRequestService(); + Request currentRequest = requestService.getCurrentRequest(); + Context context = (Context) currentRequest.getAttribute("dspace.context"); + Suggestion suggestion = suggestionService.findUnprocessedSuggestion(context, id); + if (suggestion != null) { + ExternalDataObject extDataObj = new ExternalDataObject(NAME); + extDataObj.setDisplayValue(suggestion.getDisplay()); + extDataObj.setId(suggestion.getExternalSourceUri() + .substring(suggestion.getExternalSourceUri().lastIndexOf("/") + 1)); + extDataObj.setMetadata(suggestion.getMetadata()); + return Optional.of(extDataObj); + } + return null; + } + + @Override + public List searchExternalDataObjects(String query, int start, int limit) { + return null; + } + + @Override + public boolean supports(String source) { + return StringUtils.equals(NAME, source); + } + + @Override + public int getNumberOfResults(String query) { + return 0; + } + +} 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 06deacaca4..56a0356df7 100644 --- a/dspace-api/src/test/java/org/dspace/builder/AbstractBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/AbstractBuilder.java @@ -15,6 +15,7 @@ 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.app.suggestion.SolrSuggestionStorageService; import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.factory.AuthorizeServiceFactory; import org.dspace.authorize.service.AuthorizeService; @@ -45,6 +46,7 @@ import org.dspace.eperson.service.RegistrationDataService; 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 SolrSuggestionStorageService solrSuggestionService; protected Context context; @@ -151,6 +154,7 @@ public abstract class AbstractBuilder { inProgressUserService = XmlWorkflowServiceFactory.getInstance().getInProgressUserService(); poolTaskService = XmlWorkflowServiceFactory.getInstance().getPoolTaskService(); workflowItemRoleService = XmlWorkflowServiceFactory.getInstance().getWorkflowItemRoleService(); + solrSuggestionService = new DSpace().getSingletonService(SolrSuggestionStorageService.class); } 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 aad0e86b1e..519321c5d5 100644 --- a/dspace-api/src/test/java/org/dspace/builder/ItemBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/ItemBuilder.java @@ -139,6 +139,10 @@ public class ItemBuilder extends AbstractDSpaceObjectBuilder { return addMetadataValue(item, "iiif", "image", "height", String.valueOf(i)); } + public ItemBuilder withDSpaceObjectOwner(String name, String authority) { + return addMetadataValue(item, "dspace", "object", "owner", null, name, authority, 600); + } + public ItemBuilder withMetadata(final String schema, final String element, final String qualifier, final String value) { return addMetadataValue(item, schema, element, qualifier, value); diff --git a/dspace-api/src/test/java/org/dspace/builder/SuggestionTargetBuilder.java b/dspace-api/src/test/java/org/dspace/builder/SuggestionTargetBuilder.java new file mode 100644 index 0000000000..f9671bba60 --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/builder/SuggestionTargetBuilder.java @@ -0,0 +1,161 @@ +/** + * The contents of this file are subject to the license and 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.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import org.apache.solr.client.solrj.SolrServerException; +import org.dspace.app.suggestion.MockSuggestionExternalDataSource; +import org.dspace.app.suggestion.SolrSuggestionStorageService; +import org.dspace.app.suggestion.Suggestion; +import org.dspace.app.suggestion.SuggestionEvidence; +import org.dspace.app.suggestion.SuggestionTarget; +import org.dspace.content.Collection; +import org.dspace.content.Item; +import org.dspace.content.dto.MetadataValueDTO; +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; + +/** + * Builder to construct Item objects + * + * @author Andrea Bollini (andrea.bollini at 4science.it) + */ +public class SuggestionTargetBuilder extends AbstractBuilder { + public final static String EVIDENCE_MOCK_NAME = "MockEvidence"; + public final static String EVIDENCE_MOCK_NOTE = "Generated for testing purpose..."; + private Item item; + private SuggestionTarget target; + private List suggestions; + private String source; + private int total; + + protected SuggestionTargetBuilder(Context context) { + super(context); + } + + public static SuggestionTargetBuilder createTarget(final Context context, final Collection col, final String name) { + return createTarget(context, col, name, null); + } + + public static SuggestionTargetBuilder createTarget(final Context context, final Collection col, final String name, + final EPerson eperson) { + SuggestionTargetBuilder builder = new SuggestionTargetBuilder(context); + return builder.create(context, col, name, eperson); + } + + public static SuggestionTargetBuilder createTarget(final Context context, final Item item) { + SuggestionTargetBuilder builder = new SuggestionTargetBuilder(context); + return builder.create(context, item); + } + + private SuggestionTargetBuilder create(final Context context, final Collection col, final String name) { + return create(context, col, name, null); + } + + private SuggestionTargetBuilder create(final Context context, final Collection col, final String name, + final EPerson eperson) { + this.context = context; + + try { + ItemBuilder itemBuilder = ItemBuilder.createItem(context, col).withTitle(name); + if (eperson != null) { + itemBuilder = itemBuilder.withDSpaceObjectOwner(eperson.getFullName(), eperson.getID().toString()); + } + item = itemBuilder.build(); + context.dispatchEvents(); + indexingService.commit(); + } catch (Exception e) { + return handleException(e); + } + return this; + } + + private SuggestionTargetBuilder create(final Context context, final Item item) { + this.context = context; + this.item = item; + return this; + } + + public SuggestionTargetBuilder withSuggestionCount(final String source, final int total) { + this.source = source; + this.total = total; + return this; + } + + @Override + public SuggestionTarget build() { + target = new SuggestionTarget(item); + target.setTotal(total); + target.setSource(source); + suggestions = generateAllSuggestion(); + try { + for (Suggestion s : suggestions) { + solrSuggestionService.addSuggestion(s, false, false); + } + solrSuggestionService.commit(); + } catch (SolrServerException | IOException e) { + e.printStackTrace(); + throw new RuntimeException(e); + } + return target; + } + + @Override + public void cleanup() throws Exception { + solrSuggestionService.deleteTarget(target); + } + + @Override + protected SolrSuggestionStorageService getService() { + return solrSuggestionService; + } + + @Override + public void delete(Context c, SuggestionTarget dso) throws Exception { + solrSuggestionService.deleteTarget(dso); + } + + private List generateAllSuggestion() { + List allSuggestions = new ArrayList(); + for (int idx = 0; idx < target.getTotal(); idx++) { + String idPartStr = String.valueOf(idx + 1); + Suggestion sug = new Suggestion(source, item, idPartStr); + sug.setDisplay("Suggestion " + source + " " + idPartStr); + MetadataValueDTO mTitle = new MetadataValueDTO(); + mTitle.setSchema("dc"); + mTitle.setElement("title"); + mTitle.setValue("Title Suggestion " + idPartStr); + + MetadataValueDTO mSource1 = new MetadataValueDTO(); + mSource1.setSchema("dc"); + mSource1.setElement("source"); + mSource1.setValue("Source 1"); + + MetadataValueDTO mSource2 = new MetadataValueDTO(); + mSource2.setSchema("dc"); + mSource2.setElement("source"); + mSource2.setValue("Source 2"); + + sug.getMetadata().add(mTitle); + sug.getMetadata().add(mSource1); + sug.getMetadata().add(mSource2); + + sug.setExternalSourceUri( + "http://localhost/api/integration/externalsources/" + MockSuggestionExternalDataSource.NAME + + "/entryValues/" + idPartStr); + sug.getEvidences().add(new SuggestionEvidence(EVIDENCE_MOCK_NAME, + idx % 2 == 0 ? 100 - idx : (double) idx / 2, EVIDENCE_MOCK_NOTE)); + allSuggestions.add(sug); + } + return allSuggestions; + } + +} 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 7c79a85701..57c6052c27 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 @@ -1067,6 +1067,13 @@ public class RestResourceController implements InitializingBean { return ControllerUtils.toEmptyResponse(HttpStatus.NO_CONTENT); } + @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); + } + /** * 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/converter/SuggestionConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SuggestionConverter.java new file mode 100644 index 0000000000..8eed5fb78a --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SuggestionConverter.java @@ -0,0 +1,52 @@ +/** + * The contents of this file are subject to the license and 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.rest.model.SuggestionRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.app.suggestion.Suggestion; +import org.dspace.app.suggestion.SuggestionEvidence; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * This class provides the method to convert a Suggestion to its REST representation, the + * SuggestionRest + * + * @author Andrea Bollini (andrea.bollini at 4science.it) + */ +@Component +public class SuggestionConverter + implements DSpaceConverter { + + @Autowired + private MetadataValueDTOListConverter metadataConverter; + + @Override + public SuggestionRest convert(Suggestion target, Projection projection) { + SuggestionRest targetRest = new SuggestionRest(); + targetRest.setProjection(projection); + targetRest.setId(target.getID()); + targetRest.setDisplay(target.getDisplay()); + targetRest.setExternalSourceUri(target.getExternalSourceUri()); + targetRest.setSource(target.getSource()); + targetRest.setScore(String.format("%.2f", target.getScore())); + for (SuggestionEvidence se : target.getEvidences()) { + targetRest.getEvidences().put(se.getName(), + new SuggestionRest.EvidenceRest(String.format("%.2f", se.getScore()), se.getNotes())); + } + targetRest.setMetadata(metadataConverter.convert(target.getMetadata())); + return targetRest; + } + + @Override + public Class getModelClass() { + return Suggestion.class; + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SuggestionSourceConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SuggestionSourceConverter.java new file mode 100644 index 0000000000..3506133b6f --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SuggestionSourceConverter.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.rest.converter; + +import org.dspace.app.rest.model.SuggestionSourceRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.app.suggestion.SuggestionSource; +import org.springframework.stereotype.Component; + +/** + * This class provides the method to convert a SuggestionSource to its REST representation, the + * SuggestionSourceRest + * + * @author Andrea Bollini (andrea.bollini at 4science.it) + */ +@Component +public class SuggestionSourceConverter + implements DSpaceConverter { + + @Override + public SuggestionSourceRest convert(SuggestionSource target, Projection projection) { + SuggestionSourceRest targetRest = new SuggestionSourceRest(); + targetRest.setProjection(projection); + targetRest.setId(target.getID()); + targetRest.setTotal(target.getTotal()); + return targetRest; + } + + @Override + public Class getModelClass() { + return SuggestionSource.class; + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SuggestionTargetConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SuggestionTargetConverter.java new file mode 100644 index 0000000000..4bf4be7226 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SuggestionTargetConverter.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.app.rest.converter; + +import org.dspace.app.rest.model.SuggestionTargetRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.app.suggestion.SuggestionTarget; +import org.springframework.stereotype.Component; + +/** + * This class provides the method to convert a SuggestionTarget to its REST representation, the + * SuggestionTargetRest + * + * @author Andrea Bollini (andrea.bollini at 4science.it) + */ +@Component +public class SuggestionTargetConverter + implements DSpaceConverter { + + @Override + public SuggestionTargetRest convert(SuggestionTarget target, Projection projection) { + SuggestionTargetRest targetRest = new SuggestionTargetRest(); + targetRest.setProjection(projection); + targetRest.setId(target.getID()); + targetRest.setDisplay(target.getTarget().getName()); + targetRest.setTotal(target.getTotal()); + targetRest.setSource(target.getSource()); + return targetRest; + } + + @Override + public Class getModelClass() { + return SuggestionTarget.class; + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SuggestionRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SuggestionRest.java new file mode 100644 index 0000000000..461aff70e8 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SuggestionRest.java @@ -0,0 +1,110 @@ +/** + * The contents of this file are subject to the license and 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.HashMap; +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonProperty.Access; +import org.dspace.app.rest.RestResourceController; + +/** + * The Suggestion REST Resource. A suggestion is an object, usually a + * publication, proposed by a source related to a specific Person (target) to be + * imported in the system. + * + * @author Andrea Bollini (andrea.bollini at 4science.it) + */ +@LinksRest(links = { @LinkRest(name = SuggestionRest.TARGET, method = "getTarget") }) +public class SuggestionRest extends BaseObjectRest { + private static final long serialVersionUID = 1L; + public static final String NAME = "suggestion"; + public static final String TARGET = "target"; + public static final String CATEGORY = RestAddressableModel.INTEGRATION; + + private String display; + private String source; + private String externalSourceUri; + private String score; + private Map evidences = new HashMap(); + private MetadataRest metadata = new MetadataRest(); + + @Override + @JsonProperty(access = Access.READ_ONLY) + public String getType() { + return NAME; + } + + @Override + public String getCategory() { + return CATEGORY; + } + + @Override + public Class getController() { + return RestResourceController.class; + } + + public String getDisplay() { + return display; + } + + public void setDisplay(String display) { + this.display = display; + } + + public String getSource() { + return source; + } + + public void setSource(String source) { + this.source = source; + } + + public String getExternalSourceUri() { + return externalSourceUri; + } + + public void setExternalSourceUri(String externalSourceUri) { + this.externalSourceUri = externalSourceUri; + } + + public void setScore(String score) { + this.score = score; + } + + public String getScore() { + return score; + } + + public Map getEvidences() { + return evidences; + } + + public void setEvidences(Map evidences) { + this.evidences = evidences; + } + + public MetadataRest getMetadata() { + return metadata; + } + + public void setMetadata(MetadataRest metadata) { + this.metadata = metadata; + } + + public static class EvidenceRest { + public String score; + public String notes; + public EvidenceRest(String score, String notes) { + this.score = score; + this.notes = notes; + } + } +} \ No newline at end of file diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SuggestionSourceRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SuggestionSourceRest.java new file mode 100644 index 0000000000..9c2aa80e82 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SuggestionSourceRest.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.model; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonProperty.Access; +import org.dspace.app.rest.RestResourceController; + +/** + * The Suggestion Source REST Resource. A suggestion source is a connector to an + * external system that provides suggestion for a target object of related + * objects to be imported in the system. + * + * @author Andrea Bollini (andrea.bollini at 4science.it) + */ +public class SuggestionSourceRest extends BaseObjectRest { + private static final long serialVersionUID = 1L; + public static final String CATEGORY = RestAddressableModel.INTEGRATION; + public static final String NAME = "suggestionsource"; + + private int total; + + @Override + @JsonProperty(access = Access.READ_ONLY) + public String getType() { + return NAME; + } + + @Override + public String getCategory() { + return CATEGORY; + } + + @Override + public Class getController() { + return RestResourceController.class; + } + + public int getTotal() { + return total; + } + + public void setTotal(int total) { + this.total = total; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SuggestionTargetRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SuggestionTargetRest.java new file mode 100644 index 0000000000..ba93ab4e52 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SuggestionTargetRest.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.model; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonProperty.Access; +import org.dspace.app.rest.RestResourceController; + +/** + * The Suggestion Target REST Resource. A suggestion target is a Person to whom + * one or more suggester sources have found related objects to be importe in the + * system. + * + * @author Andrea Bollini (andrea.bollini at 4science.it) + */ +@LinksRest(links = { + @LinkRest(name = SuggestionTargetRest.TARGET, method = "getTarget") +}) +public class SuggestionTargetRest extends BaseObjectRest { + private static final long serialVersionUID = 1L; + public static final String NAME = "suggestiontarget"; + public static final String TARGET = "target"; + public static final String CATEGORY = RestAddressableModel.INTEGRATION; + + private String display; + private String source; + private int total; + + @Override + @JsonProperty(access = Access.READ_ONLY) + public String getType() { + return NAME; + } + + @Override + public String getCategory() { + return CATEGORY; + } + + @Override + public Class getController() { + return RestResourceController.class; + } + + public String getDisplay() { + return display; + } + + public void setDisplay(String display) { + this.display = display; + } + + public String getSource() { + return source; + } + + public void setSource(String source) { + this.source = source; + } + + public int getTotal() { + return total; + } + + public void setTotal(int total) { + this.total = total; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/SuggestionResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/SuggestionResource.java new file mode 100644 index 0000000000..66165f8698 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/SuggestionResource.java @@ -0,0 +1,25 @@ +/** + * The contents of this file are subject to the license and 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.SuggestionRest; +import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource; +import org.dspace.app.rest.utils.Utils; + +/** + * Suggestion Rest HAL Resource. The HAL Resource wraps the REST Resource + * adding support for the links and embedded resources + * + * @author Andrea Bollini (andrea.bollini at 4science.it) + */ +@RelNameDSpaceResource(SuggestionRest.NAME) +public class SuggestionResource extends DSpaceResource { + public SuggestionResource(SuggestionRest target, Utils utils) { + super(target, utils); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/SuggestionSourceResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/SuggestionSourceResource.java new file mode 100644 index 0000000000..1f01f27d86 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/SuggestionSourceResource.java @@ -0,0 +1,25 @@ +/** + * The contents of this file are subject to the license and 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.SuggestionSourceRest; +import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource; +import org.dspace.app.rest.utils.Utils; + +/** + * Suggestion Source Rest HAL Resource. The HAL Resource wraps the REST Resource + * adding support for the links and embedded resources + * + * @author Andrea Bollini (andrea.bollini at 4science.it) + */ +@RelNameDSpaceResource(SuggestionSourceRest.NAME) +public class SuggestionSourceResource extends DSpaceResource { + public SuggestionSourceResource(SuggestionSourceRest target, Utils utils) { + super(target, utils); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/SuggestionTargetResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/SuggestionTargetResource.java new file mode 100644 index 0000000000..26cd7c3c34 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/SuggestionTargetResource.java @@ -0,0 +1,25 @@ +/** + * The contents of this file are subject to the license and 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.SuggestionTargetRest; +import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource; +import org.dspace.app.rest.utils.Utils; + +/** + * Suggestion Target Rest HAL Resource. The HAL Resource wraps the REST Resource + * adding support for the links and embedded resources + * + * @author Andrea Bollini (andrea.bollini at 4science.it) + */ +@RelNameDSpaceResource(SuggestionTargetRest.NAME) +public class SuggestionTargetResource extends DSpaceResource { + public SuggestionTargetResource(SuggestionTargetRest target, Utils utils) { + super(target, utils); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SuggestionRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SuggestionRestRepository.java new file mode 100644 index 0000000000..e2e1c3ce7c --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SuggestionRestRepository.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.repository; + +import java.util.List; +import java.util.UUID; + +import org.apache.logging.log4j.Logger; +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.SuggestionRest; +import org.dspace.app.rest.model.SuggestionTargetRest; +import org.dspace.app.suggestion.Suggestion; +import org.dspace.app.suggestion.SuggestionService; +import org.dspace.authorize.AuthorizeException; +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.data.domain.Sort.Direction; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Component; + +/** + * This is the repository responsible to manage Suggestion Target Rest object + * + * @author Andrea Bollini (andrea.bollini at 4science.it) + */ + +@Component(SuggestionRest.CATEGORY + "." + SuggestionRest.NAME) +public class SuggestionRestRepository extends DSpaceRestRepository { + private final static String ORDER_FIELD = "trust"; + + private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(SuggestionRestRepository.class); + + @Autowired + private SuggestionService suggestionService; + + @Override + @PreAuthorize("hasPermission(#id, 'SUGGESTION', 'READ')") + public SuggestionRest findOne(Context context, String id) { + Suggestion suggestion = suggestionService.findUnprocessedSuggestion(context, id); + if (suggestion == null) { + return null; + } + return converter.toRest(suggestion, utils.obtainProjection()); + } + + @Override + @PreAuthorize("permitAll()") + public Page findAll(Context context, Pageable pageable) { + throw new RepositoryMethodNotImplementedException(SuggestionTargetRest.NAME, "findAll"); + } + + @PreAuthorize("hasPermission(#target, 'SUGGESTION.TARGET', 'READ')") + @SearchRestMethod(name = "findByTargetAndSource") + public Page findByTargetAndSource( + @Parameter(required = true, value = "source") String source, + @Parameter(required = true, value = "target") UUID target, Pageable pageable) { + Context context = obtainContext(); + boolean ascending = false; + if (pageable.getSort() != null && pageable.getSort().getOrderFor(ORDER_FIELD) != null) { + ascending = pageable.getSort().getOrderFor(ORDER_FIELD).getDirection() == Direction.ASC; + } + List suggestions = suggestionService.findByTargetAndSource(context, target, source, + pageable.getPageSize(), pageable.getOffset(), ascending); + long tot = suggestionService.countAllByTargetAndSource(context, source, target); + return converter.toRestPage(suggestions, pageable, tot, utils.obtainProjection()); + } + + @Override + @PreAuthorize("hasPermission(#id, 'SUGGESTION', 'DELETE')") + protected void delete(Context context, String id) + throws AuthorizeException, RepositoryMethodNotImplementedException { + suggestionService.rejectSuggestion(context, id); + } + + @Override + public Class getDomainClass() { + return SuggestionRest.class; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SuggestionSourceRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SuggestionSourceRestRepository.java new file mode 100644 index 0000000000..6bc251749b --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SuggestionSourceRestRepository.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.rest.repository; + +import java.util.List; + +import org.apache.logging.log4j.Logger; +import org.dspace.app.rest.model.SuggestionSourceRest; +import org.dspace.app.suggestion.SuggestionService; +import org.dspace.app.suggestion.SuggestionSource; +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; + +/** + * This is the repository responsible to manage Suggestion Target Rest object + * + * @author Andrea Bollini (andrea.bollini at 4science.it) + */ + +@Component(SuggestionSourceRest.CATEGORY + "." + SuggestionSourceRest.NAME) +public class SuggestionSourceRestRepository extends DSpaceRestRepository { + + private static final Logger log = org.apache.logging.log4j.LogManager + .getLogger(SuggestionSourceRestRepository.class); + + @Autowired + private SuggestionService suggestionService; + + @Override + @PreAuthorize("hasAuthority('ADMIN')") + public SuggestionSourceRest findOne(Context context, String source) { + SuggestionSource suggestionSource = suggestionService.findSource(context, source); + if (suggestionSource == null) { + return null; + } + return converter.toRest(suggestionSource, utils.obtainProjection()); + } + + @Override + @PreAuthorize("hasAuthority('ADMIN')") + public Page findAll(Context context, Pageable pageable) { + List suggestionSources = suggestionService.findAllSources(context, pageable.getPageSize(), + pageable.getOffset()); + long count = suggestionService.countSources(context); + if (suggestionSources == null) { + return null; + } + return converter.toRestPage(suggestionSources, pageable, count, utils.obtainProjection()); + } + + @Override + public Class getDomainClass() { + return SuggestionSourceRest.class; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SuggestionTargetRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SuggestionTargetRestRepository.java new file mode 100644 index 0000000000..aadeb4da94 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SuggestionTargetRestRepository.java @@ -0,0 +1,92 @@ +/** + * The contents of this file are subject to the license and 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 java.util.UUID; + +import org.apache.logging.log4j.Logger; +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.SuggestionTargetRest; +import org.dspace.app.suggestion.SuggestionService; +import org.dspace.app.suggestion.SuggestionTarget; +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; + +/** + * This is the repository responsible to manage Suggestion Target Rest object + * + * @author Andrea Bollini (andrea.bollini at 4science.it) + */ + +@Component(SuggestionTargetRest.CATEGORY + "." + SuggestionTargetRest.NAME) +public class SuggestionTargetRestRepository extends DSpaceRestRepository { + + private static final Logger log = org.apache.logging.log4j.LogManager + .getLogger(SuggestionTargetRestRepository.class); + + @Autowired + private SuggestionService suggestionService; + + @Override + @PreAuthorize("hasPermission(#id, 'SUGGESTIONTARGET', 'READ')") + public SuggestionTargetRest findOne(Context context, String id) { + String source = null; + UUID uuid = null; + try { + source = id.split(":")[0]; + uuid = UUID.fromString(id.split(":")[1]); + } catch (Exception e) { + return null; + } + SuggestionTarget suggestionTarget = suggestionService.find(context, source, uuid); + if (suggestionTarget == null) { + return null; + } + return converter.toRest(suggestionTarget, utils.obtainProjection()); + } + + @Override + @PreAuthorize("permitAll()") + public Page findAll(Context context, Pageable pageable) { + throw new RepositoryMethodNotImplementedException(SuggestionTargetRest.NAME, "findAll"); + } + + @PreAuthorize("hasAuthority('ADMIN')") + @SearchRestMethod(name = "findBySource") + public Page findBySource(@Parameter(required = true, value = "source") String source, + Pageable pageable) { + Context context = obtainContext(); + List suggestionTargets = suggestionService.findAllTargets(context, source, + pageable.getPageSize(), pageable.getOffset()); + long tot = suggestionService.countAll(context, source); + return converter.toRestPage(suggestionTargets, pageable, tot, utils.obtainProjection()); + } + + @PreAuthorize("hasPermission(#target, 'SUGGESTIONTARGET.TARGET', 'READ')") + @SearchRestMethod(name = "findByTarget") + public Page findByTarget(@Parameter(required = true, value = "target") UUID target, + Pageable pageable) { + Context context = obtainContext(); + List suggestionTargets = suggestionService.findByTarget(context, target, + pageable.getPageSize(), pageable.getOffset()); + long tot = suggestionService.countAllByTarget(context, target); + return converter.toRestPage(suggestionTargets, pageable, tot, utils.obtainProjection()); + } + + @Override + public Class getDomainClass() { + return SuggestionTargetRest.class; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SuggestionTargetTargetLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SuggestionTargetTargetLinkRepository.java new file mode 100644 index 0000000000..50c6e4d48e --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SuggestionTargetTargetLinkRepository.java @@ -0,0 +1,70 @@ +/** + * The contents of this file are subject to the license and 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.apache.commons.lang3.StringUtils; +import org.dspace.app.rest.model.ItemRest; +import org.dspace.app.rest.model.SuggestionTargetRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.content.Item; +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 an suggestion target. + * + * @author Andrea Bollini (andrea.bollini at 4science.it) + * + */ +@Component(SuggestionTargetRest.CATEGORY + "." + SuggestionTargetRest.NAME + "." + SuggestionTargetRest.TARGET) +public class SuggestionTargetTargetLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { + + @Autowired + private ItemService itemService; + + /** + * Returns the item related to the suggestion target with the given id. + * + * @param request the http servlet request + * @param id the suggestion target UUID + * @param pageable the optional pageable + * @param projection the projection object + * @return the target item rest representation + */ + @PreAuthorize("hasPermission(#id, 'SUGGESTIONTARGET', 'READ')") + public ItemRest getTarget(@Nullable HttpServletRequest request, String id, + @Nullable Pageable pageable, Projection projection) { + String source = id.split(":")[0]; + UUID uuid = UUID.fromString(id.split(":")[1]); + if (StringUtils.isBlank(source) || uuid == null) { + throw new ResourceNotFoundException("No such item related to a suggestion target with UUID: " + id); + } + try { + Context context = obtainContext(); + Item profile = itemService.find(context, uuid); + if (profile == null) { + throw new ResourceNotFoundException("No such item related to a suggestion target with UUID: " + id); + } + + return converter.toRest(profile, projection); + } catch (SQLException e) { + throw new RuntimeException(e); + } + + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/SuggestionRestPermissionEvaluatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/SuggestionRestPermissionEvaluatorPlugin.java new file mode 100644 index 0000000000..2f095a9abc --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/SuggestionRestPermissionEvaluatorPlugin.java @@ -0,0 +1,94 @@ +/** + * The contents of this file are subject to the license and 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 static org.dspace.app.rest.security.DSpaceRestPermission.DELETE; +import static org.dspace.app.rest.security.DSpaceRestPermission.READ; +import static org.dspace.app.rest.security.DSpaceRestPermission.WRITE; + +import java.io.Serializable; +import java.util.List; +import java.util.UUID; + +import org.apache.commons.lang3.StringUtils; +import org.dspace.app.rest.model.SuggestionRest; +import org.dspace.app.rest.utils.ContextUtil; +import org.dspace.content.Item; +import org.dspace.content.MetadataValue; +import org.dspace.content.service.ItemService; +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; +import org.dspace.util.UUIDUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.Authentication; +import org.springframework.stereotype.Component; + +/** + * + * An authenticated user is allowed to view a suggestion for the data that his + * own. This {@link RestPermissionEvaluatorPlugin} implements that requirement. + * + * @author Andrea Bollini (andrea.bollini at 4science.it) + * + */ +@Component +public class SuggestionRestPermissionEvaluatorPlugin extends RestObjectPermissionEvaluatorPlugin { + + @Autowired + private ItemService itemService; + + @Override + public boolean hasDSpacePermission(Authentication authentication, Serializable targetId, String targetType, + DSpaceRestPermission restPermission) { + + if (!READ.equals(restPermission) && !WRITE.equals(restPermission) && !DELETE.equals(restPermission)) { + return false; + } + + if (!StringUtils.equalsIgnoreCase(targetType, SuggestionRest.NAME) + && !StringUtils.startsWithIgnoreCase(targetType, SuggestionRest.NAME)) { + return false; + } + + Context context = ContextUtil.obtainCurrentRequestContext(); + + EPerson currentUser = context.getCurrentUser(); + if (currentUser == null) { + return false; + } + + try { + String id = targetId.toString(); + UUID uuid = null; + if (id.contains(":")) { + uuid = UUIDUtils.fromString(id.split(":", 3)[1]); + } else { + uuid = UUIDUtils.fromString(id); + } + if (uuid == null) { + return false; + } + Item item = itemService.find(context, uuid); + if (item != null) { + List mvalues = itemService.getMetadataByMetadataString(item, "dspace.object.owner"); + if (mvalues != null) { + for (MetadataValue mv : mvalues) { + if (StringUtils.equals(mv.getAuthority(), currentUser.getID().toString())) { + return true; + } + } + } + } + } catch (Exception ex) { + return false; + } + + return false; + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/SuggestionTargetRestPermissionEvaluatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/SuggestionTargetRestPermissionEvaluatorPlugin.java new file mode 100644 index 0000000000..98acee30c6 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/SuggestionTargetRestPermissionEvaluatorPlugin.java @@ -0,0 +1,95 @@ +/** + * The contents of this file are subject to the license and 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 static org.dspace.app.rest.security.DSpaceRestPermission.DELETE; +import static org.dspace.app.rest.security.DSpaceRestPermission.READ; +import static org.dspace.app.rest.security.DSpaceRestPermission.WRITE; + +import java.io.Serializable; +import java.util.List; +import java.util.UUID; + +import org.apache.commons.lang3.StringUtils; +import org.dspace.app.rest.model.SuggestionTargetRest; +import org.dspace.app.rest.utils.ContextUtil; +import org.dspace.content.Item; +import org.dspace.content.MetadataValue; +import org.dspace.content.service.ItemService; +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; +import org.dspace.util.UUIDUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.Authentication; +import org.springframework.stereotype.Component; + +/** + * + * An authenticated user is allowed to view the suggestions summary + * (SuggestionTarget) for the data that his own. This + * {@link RestPermissionEvaluatorPlugin} implements that requirement. + * + * @author Andrea Bollini (andrea.bollini at 4science.it) + * + */ +@Component +public class SuggestionTargetRestPermissionEvaluatorPlugin extends RestObjectPermissionEvaluatorPlugin { + + @Autowired + private ItemService itemService; + + @Override + public boolean hasDSpacePermission(Authentication authentication, Serializable targetId, String targetType, + DSpaceRestPermission restPermission) { + + if (!READ.equals(restPermission) && !WRITE.equals(restPermission) && !DELETE.equals(restPermission)) { + return false; + } + + if (!StringUtils.equalsIgnoreCase(targetType, SuggestionTargetRest.NAME) + && !StringUtils.startsWithIgnoreCase(targetType, SuggestionTargetRest.NAME)) { + return false; + } + + Context context = ContextUtil.obtainCurrentRequestContext(); + + EPerson currentUser = context.getCurrentUser(); + if (currentUser == null) { + return false; + } + + try { + String id = targetId.toString(); + UUID uuid = null; + if (id.contains(":")) { + uuid = UUIDUtils.fromString(id.split(":", 2)[1]); + } else { + uuid = UUIDUtils.fromString(id); + } + if (uuid == null) { + return false; + } + Item item = itemService.find(context, uuid); + if (item != null) { + List mvalues = itemService.getMetadataByMetadataString(item, "dspace.object.owner"); + if (mvalues != null) { + for (MetadataValue mv : mvalues) { + if (StringUtils.equals(mv.getAuthority(), currentUser.getID().toString())) { + return true; + } + } + } + } + } catch (Exception ex) { + return false; + } + + return false; + } + +} 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 9205c3b88e..4c610c74bc 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 @@ -41,6 +41,7 @@ public class ExternalSourcesRestControllerIT extends AbstractControllerIntegrati .andExpect(jsonPath("$._embedded.externalsources", Matchers.hasItems( ExternalSourceMatcher.matchExternalSource("mock", "mock", false), ExternalSourceMatcher.matchExternalSource("orcid", "orcid", false), + ExternalSourceMatcher.matchExternalSource("suggestion", "suggestion", false), ExternalSourceMatcher.matchExternalSource( "sherpaJournalIssn", "sherpaJournalIssn", false), ExternalSourceMatcher.matchExternalSource( diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SuggestionRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SuggestionRestRepositoryIT.java new file mode 100644 index 0000000000..16d4580594 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SuggestionRestRepositoryIT.java @@ -0,0 +1,473 @@ +/** + * The contents of this file are subject to the license and 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 org.dspace.app.rest.matcher.SuggestionMatcher.matchSuggestion; +import static org.dspace.builder.SuggestionTargetBuilder.EVIDENCE_MOCK_NAME; +import static org.dspace.builder.SuggestionTargetBuilder.EVIDENCE_MOCK_NOTE; +import static org.hamcrest.Matchers.is; +import static org.springframework.data.rest.webmvc.RestMediaTypes.TEXT_URI_LIST_VALUE; +import static org.springframework.http.MediaType.parseMediaType; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.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.Map; +import java.util.UUID; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.dspace.app.rest.matcher.MetadataMatcher; +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.app.suggestion.MockSuggestionExternalDataSource; +import org.dspace.app.suggestion.SuggestionTarget; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.ItemBuilder; +import org.dspace.builder.SuggestionTargetBuilder; +import org.dspace.builder.WorkspaceItemBuilder; +import org.dspace.content.Collection; +import org.dspace.content.Item; +import org.hamcrest.Matchers; +import org.junit.Before; +import org.junit.Test; +import org.springframework.test.web.servlet.MvcResult; + +/** + * Integration Tests against the /api/integration/suggestions endpoint + */ +public class SuggestionRestRepositoryIT extends AbstractControllerIntegrationTest { + private Collection colPeople; + + @Override + @Before + public void setUp() throws Exception { + super.setUp(); + // We turn off the authorization system in order to create the structure as + // defined below + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context).withName("Parent Community").build(); + colPeople = CollectionBuilder.createCollection(context, parentCommunity).withName("People") + .withEntityType("Person").build(); + context.restoreAuthSystemState(); + } + + @Test + public void findAllTest() throws Exception { + context.turnOffAuthorisationSystem(); + Item itemFirst = ItemBuilder.createItem(context, colPeople).withTitle("Bollini, Andrea").build(); + SuggestionTarget targetFirstScopus = SuggestionTargetBuilder.createTarget(context, itemFirst) + .withSuggestionCount("scopus", 3).build(); + context.restoreAuthSystemState(); + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(get("/api/integration/suggestions")) + .andExpect(status().isMethodNotAllowed()); + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform(get("/api/integration/suggestions")).andExpect(status().isMethodNotAllowed()); + getClient().perform(get("/api/integration/suggestions")).andExpect(status().isMethodNotAllowed()); + } + + @Test + public void findByTargetAndSourceTest() throws Exception { + context.turnOffAuthorisationSystem(); + Item itemFirst = ItemBuilder.createItem(context, colPeople).withTitle("Bollini, Andrea").build(); + SuggestionTarget targetFirstReciter = SuggestionTargetBuilder.createTarget(context, itemFirst) + .withSuggestionCount("reciter", 31).build(); + SuggestionTarget targetFirstScopus = SuggestionTargetBuilder.createTarget(context, itemFirst) + .withSuggestionCount("scopus", 3).build(); + SuggestionTarget targetSecond = SuggestionTargetBuilder + .createTarget(context, colPeople, "Digilio, Giuseppe", eperson).withSuggestionCount("reciter", 11) + .build(); + context.restoreAuthSystemState(); + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken) + .perform(get("/api/integration/suggestions/search/findByTargetAndSource").param("source", "scopus") + .param("target", itemFirst.getID().toString())) + .andExpect(status().isOk()).andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.suggestions", Matchers.contains( + matchSuggestion("scopus", itemFirst, "Suggestion scopus 1", "1", + 100.0, EVIDENCE_MOCK_NAME, 100.0, EVIDENCE_MOCK_NOTE), + matchSuggestion("scopus", itemFirst, "Suggestion scopus 3", "3", + 98.0, EVIDENCE_MOCK_NAME, 98.0, EVIDENCE_MOCK_NOTE), + matchSuggestion("scopus", itemFirst, "Suggestion scopus 2", "2", + 0.5, EVIDENCE_MOCK_NAME, 0.5, EVIDENCE_MOCK_NOTE)))) + .andExpect(jsonPath("$._links.self.href", Matchers.allOf( + Matchers.containsString( + "/api/integration/suggestions/search/findByTargetAndSource?"), + Matchers.containsString("source=scopus"), + Matchers.containsString("target=" + itemFirst.getID().toString())))) + .andExpect(jsonPath("$.page.size", is(20))).andExpect(jsonPath("$.page.totalElements", is(3))); + Item itemSecond = targetSecond.getTarget(); + getClient(adminToken) + .perform(get("/api/integration/suggestions/search/findByTargetAndSource").param("source", "reciter") + .param("target", itemSecond.getID().toString())) + .andExpect(status().isOk()).andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.suggestions", Matchers.containsInAnyOrder( + matchSuggestion("reciter", itemSecond, "Suggestion reciter 1", "1"), + matchSuggestion("reciter", itemSecond, "Suggestion reciter 2", "2"), + matchSuggestion("reciter", itemSecond, "Suggestion reciter 3", "3"), + matchSuggestion("reciter", itemSecond, "Suggestion reciter 4", "4"), + matchSuggestion("reciter", itemSecond, "Suggestion reciter 5", "5"), + matchSuggestion("reciter", itemSecond, "Suggestion reciter 6", "6"), + matchSuggestion("reciter", itemSecond, "Suggestion reciter 7", "7"), + matchSuggestion("reciter", itemSecond, "Suggestion reciter 8", "8"), + matchSuggestion("reciter", itemSecond, "Suggestion reciter 9", "9"), + matchSuggestion("reciter", itemSecond, "Suggestion reciter 10", "10"), + matchSuggestion("reciter", itemSecond, "Suggestion reciter 11", "11")))) + .andExpect(jsonPath("$._links.self.href", Matchers.allOf( + Matchers.containsString( + "/api/integration/suggestions/search/findByTargetAndSource?"), + Matchers.containsString("source=reciter"), + Matchers.containsString("target=" + itemSecond.getID().toString())))) + .andExpect(jsonPath("$.page.size", is(20))).andExpect(jsonPath("$.page.totalElements", is(11))); + String epersonToken = getAuthToken(eperson.getEmail(), password); + getClient(epersonToken) + .perform(get("/api/integration/suggestions/search/findByTargetAndSource").param("source", "reciter") + .param("target", itemSecond.getID().toString())) + .andExpect(status().isOk()).andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.suggestions", Matchers.containsInAnyOrder( + matchSuggestion("reciter", itemSecond, "Suggestion reciter 1", "1"), + matchSuggestion("reciter", itemSecond, "Suggestion reciter 2", "2"), + matchSuggestion("reciter", itemSecond, "Suggestion reciter 3", "3"), + matchSuggestion("reciter", itemSecond, "Suggestion reciter 4", "4"), + matchSuggestion("reciter", itemSecond, "Suggestion reciter 5", "5"), + matchSuggestion("reciter", itemSecond, "Suggestion reciter 6", "6"), + matchSuggestion("reciter", itemSecond, "Suggestion reciter 7", "7"), + matchSuggestion("reciter", itemSecond, "Suggestion reciter 8", "8"), + matchSuggestion("reciter", itemSecond, "Suggestion reciter 9", "9"), + matchSuggestion("reciter", itemSecond, "Suggestion reciter 10", "10"), + matchSuggestion("reciter", itemSecond, "Suggestion reciter 11", "11")))) + .andExpect(jsonPath("$._links.self.href", Matchers.allOf( + Matchers.containsString( + "/api/integration/suggestions/search/findByTargetAndSource?"), + Matchers.containsString("source=reciter"), + Matchers.containsString("target=" + itemSecond.getID().toString())))) + .andExpect(jsonPath("$.page.size", is(20))).andExpect(jsonPath("$.page.totalElements", is(11))); + } + + @Test + public void findByTargetAndSourcePaginationTest() throws Exception { + context.turnOffAuthorisationSystem(); + SuggestionTarget targetSecond = SuggestionTargetBuilder + .createTarget(context, colPeople, "Digilio, Giuseppe", eperson).withSuggestionCount("reciter", 11) + .build(); + context.restoreAuthSystemState(); + String adminToken = getAuthToken(admin.getEmail(), password); + Item itemSecond = targetSecond.getTarget(); + getClient(adminToken) + .perform(get("/api/integration/suggestions/search/findByTargetAndSource").param("source", "reciter") + .param("size", "5") + .param("target", itemSecond.getID().toString())) + .andExpect(status().isOk()).andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.suggestions", Matchers.contains( + matchSuggestion("reciter", itemSecond, "Suggestion reciter 1", "1"), + matchSuggestion("reciter", itemSecond, "Suggestion reciter 3", "3"), + matchSuggestion("reciter", itemSecond, "Suggestion reciter 5", "5"), + matchSuggestion("reciter", itemSecond, "Suggestion reciter 7", "7"), + matchSuggestion("reciter", itemSecond, "Suggestion reciter 9", "9")))) + .andExpect(jsonPath("$._links.self.href", Matchers.allOf( + Matchers.containsString( + "/api/integration/suggestions/search/findByTargetAndSource?"), + Matchers.containsString("source=reciter"), + Matchers.containsString("size=5"), + Matchers.containsString("target=" + itemSecond.getID().toString())))) + .andExpect(jsonPath("$._links.next.href", + Matchers.allOf( + Matchers.containsString( + "/api/integration/suggestions/search/findByTargetAndSource?"), + Matchers.containsString("source=reciter"), + Matchers.containsString("page=1"), + Matchers.containsString("size=5"), + Matchers.containsString("target=" + itemSecond.getID().toString())))) + .andExpect(jsonPath("$._links.last.href", + Matchers.allOf( + Matchers.containsString( + "/api/integration/suggestions/search/findByTargetAndSource?"), + Matchers.containsString("source=reciter"), + Matchers.containsString("page=2"), + Matchers.containsString("size=5"), + Matchers.containsString("target=" + itemSecond.getID().toString())))) + .andExpect(jsonPath("$._links.first.href", + Matchers.allOf( + Matchers.containsString( + "/api/integration/suggestions/search/findByTargetAndSource?"), + Matchers.containsString("source=reciter"), + Matchers.containsString("page=0"), + Matchers.containsString("size=5"), + Matchers.containsString("target=" + itemSecond.getID().toString())))) + .andExpect(jsonPath("$._links.prev.href").doesNotExist()) + .andExpect(jsonPath("$.page.size", is(5))).andExpect(jsonPath("$.page.totalElements", is(11))); + + getClient(adminToken) + .perform(get("/api/integration/suggestions/search/findByTargetAndSource").param("source", "reciter") + .param("size", "5") + .param("page", "1") + .param("target", itemSecond.getID().toString())) + .andExpect(status().isOk()).andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.suggestions", Matchers.contains( + matchSuggestion("reciter", itemSecond, "Suggestion reciter 11", "11"), + matchSuggestion("reciter", itemSecond, "Suggestion reciter 10", "10"), + matchSuggestion("reciter", itemSecond, "Suggestion reciter 8", "8"), + matchSuggestion("reciter", itemSecond, "Suggestion reciter 6", "6"), + matchSuggestion("reciter", itemSecond, "Suggestion reciter 4", "4")))) + .andExpect(jsonPath("$._links.self.href", Matchers.allOf( + Matchers.containsString( + "/api/integration/suggestions/search/findByTargetAndSource?"), + Matchers.containsString("source=reciter"), + Matchers.containsString("page=1"), + Matchers.containsString("size=5"), + Matchers.containsString("target=" + itemSecond.getID().toString())))) + .andExpect(jsonPath("$._links.next.href", + Matchers.allOf( + Matchers.containsString( + "/api/integration/suggestions/search/findByTargetAndSource?"), + Matchers.containsString("source=reciter"), + Matchers.containsString("page=2"), + Matchers.containsString("size=5"), + Matchers.containsString("target=" + itemSecond.getID().toString())))) + .andExpect(jsonPath("$._links.prev.href", + Matchers.allOf( + Matchers.containsString( + "/api/integration/suggestions/search/findByTargetAndSource?"), + Matchers.containsString("source=reciter"), + Matchers.containsString("page=0"), + Matchers.containsString("size=5"), + Matchers.containsString("target=" + itemSecond.getID().toString())))) + .andExpect(jsonPath("$._links.last.href", + Matchers.allOf( + Matchers.containsString( + "/api/integration/suggestions/search/findByTargetAndSource?"), + Matchers.containsString("source=reciter"), + Matchers.containsString("page=2"), + Matchers.containsString("size=5"), + Matchers.containsString("target=" + itemSecond.getID().toString())))) + .andExpect(jsonPath("$._links.first.href", + Matchers.allOf( + Matchers.containsString( + "/api/integration/suggestions/search/findByTargetAndSource?"), + Matchers.containsString("source=reciter"), + Matchers.containsString("page=0"), + Matchers.containsString("size=5"), + Matchers.containsString("target=" + itemSecond.getID().toString())))) + .andExpect(jsonPath("$.page.size", is(5))).andExpect(jsonPath("$.page.totalElements", is(11))); + + getClient(adminToken) + .perform(get("/api/integration/suggestions/search/findByTargetAndSource").param("source", "reciter") + .param("size", "5") + .param("page", "2") + .param("target", itemSecond.getID().toString())) + .andExpect(status().isOk()).andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.suggestions", Matchers.contains( + matchSuggestion("reciter", itemSecond, "Suggestion reciter 2", "2")))) + .andExpect(jsonPath("$._links.self.href", Matchers.allOf( + Matchers.containsString( + "/api/integration/suggestions/search/findByTargetAndSource?"), + Matchers.containsString("source=reciter"), + Matchers.containsString("page=2"), + Matchers.containsString("size=5"), + Matchers.containsString("target=" + itemSecond.getID().toString())))) + .andExpect(jsonPath("$._links.prev.href", + Matchers.allOf( + Matchers.containsString( + "/api/integration/suggestions/search/findByTargetAndSource?"), + Matchers.containsString("source=reciter"), + Matchers.containsString("page=1"), + Matchers.containsString("size=5"), + Matchers.containsString("target=" + itemSecond.getID().toString())))) + .andExpect(jsonPath("$._links.last.href", + Matchers.allOf( + Matchers.containsString( + "/api/integration/suggestions/search/findByTargetAndSource?"), + Matchers.containsString("source=reciter"), + Matchers.containsString("page=2"), + Matchers.containsString("size=5"), + Matchers.containsString("target=" + itemSecond.getID().toString())))) + .andExpect(jsonPath("$._links.first.href", + Matchers.allOf( + Matchers.containsString( + "/api/integration/suggestions/search/findByTargetAndSource?"), + Matchers.containsString("source=reciter"), + Matchers.containsString("page=0"), + Matchers.containsString("size=5"), + Matchers.containsString("target=" + itemSecond.getID().toString())))) + .andExpect(jsonPath("$._links.next.href").doesNotExist()) + .andExpect(jsonPath("$.page.size", is(5))).andExpect(jsonPath("$.page.totalElements", is(11))); + } + + @Test + public void findByTargetAndSourceNotAdminTest() throws Exception { + context.turnOffAuthorisationSystem(); + Item itemFirst = ItemBuilder.createItem(context, colPeople).withTitle("Bollini, Andrea").build(); + SuggestionTarget targetFirstReciter = SuggestionTargetBuilder.createTarget(context, itemFirst) + .withSuggestionCount("reciter", 31).build(); + SuggestionTarget targetFirstScopus = SuggestionTargetBuilder.createTarget(context, itemFirst) + .withSuggestionCount("scopus", 3).build(); + SuggestionTarget targetSecond = SuggestionTargetBuilder + .createTarget(context, colPeople, "Digilio, Giuseppe", eperson).withSuggestionCount("reciter", 11) + .build(); + context.restoreAuthSystemState(); + // anonymous cannot access the suggestions source endpoint + getClient() + .perform(get("/api/integration/suggestions/search/findByTargetAndSource") + .param("target", UUID.randomUUID().toString()).param("source", "reciter")) + .andExpect(status().isUnauthorized()); + // nor normal user + String tokenEperson = getAuthToken(eperson.getEmail(), password); + getClient(tokenEperson) + .perform(get("/api/integration/suggestions/search/findByTargetAndSource") + .param("target", UUID.randomUUID().toString()).param("source", "reciter")) + .andExpect(status().isForbidden()); + } + + @Test + public void findOneTest() throws Exception { + context.turnOffAuthorisationSystem(); + Item itemFirst = ItemBuilder.createItem(context, colPeople).withTitle("Bollini, Andrea").build(); + SuggestionTarget targetFirstReciter = SuggestionTargetBuilder.createTarget(context, itemFirst) + .withSuggestionCount("reciter", 31).build(); + SuggestionTarget targetFirstScopus = SuggestionTargetBuilder.createTarget(context, itemFirst) + .withSuggestionCount("scopus", 3).build(); + SuggestionTarget targetSecond = SuggestionTargetBuilder + .createTarget(context, colPeople, "Digilio, Giuseppe", eperson).withSuggestionCount("reciter", 11) + .build(); + context.restoreAuthSystemState(); + + String adminToken = getAuthToken(admin.getEmail(), password); + String suggestionId = "reciter:" + itemFirst.getID().toString() + ":6"; + getClient(adminToken).perform(get("/api/integration/suggestions/" + suggestionId)).andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$", matchSuggestion("reciter", itemFirst, "Suggestion reciter 6", "6"))) + .andExpect(jsonPath("$._links.self.href", + Matchers.endsWith("/api/integration/suggestions/" + suggestionId))); + Item itemSecond = targetSecond.getTarget(); + String epersonSuggestionId = "reciter:" + itemSecond.getID().toString() + ":2"; + String epersonToken = getAuthToken(eperson.getEmail(), password); + getClient(epersonToken).perform(get("/api/integration/suggestions/" + epersonSuggestionId)) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$", matchSuggestion("reciter", itemSecond, "Suggestion reciter 2", "2"))) + .andExpect(jsonPath("$._links.self.href", + Matchers.endsWith("/api/integration/suggestions/" + epersonSuggestionId))); + } + + @Test + public void findOneNotAdminTest() throws Exception { + context.turnOffAuthorisationSystem(); + Item itemFirst = ItemBuilder.createItem(context, colPeople).withTitle("Bollini, Andrea").build(); + SuggestionTarget targetFirstReciter = SuggestionTargetBuilder.createTarget(context, itemFirst) + .withSuggestionCount("reciter", 31).build(); + SuggestionTarget targetFirstScopus = SuggestionTargetBuilder.createTarget(context, itemFirst) + .withSuggestionCount("scopus", 3).build(); + SuggestionTarget targetSecond = SuggestionTargetBuilder + .createTarget(context, colPeople, "Digilio, Giuseppe", eperson).withSuggestionCount("reciter", 11) + .build(); + context.restoreAuthSystemState(); + + String epersonToken = getAuthToken(eperson.getEmail(), password); + String suggestionId = "reciter:" + itemFirst.getID().toString() + ":6"; + getClient(epersonToken).perform(get("/api/integration/suggestions/" + suggestionId)) + .andExpect(status().isForbidden()); + getClient(epersonToken).perform(get("/api/integration/suggestions/not-exist")) + .andExpect(status().isForbidden()); + getClient().perform(get("/api/integration/suggestions/" + suggestionId)).andExpect(status().isUnauthorized()); + getClient().perform(get("/api/integration/suggestions/not-exist")).andExpect(status().isUnauthorized()); + } + + @Test + public void acceptSuggestionTest() throws Exception { + context.turnOffAuthorisationSystem(); + Collection colPublications = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Publications").build(); + Item itemFirst = ItemBuilder.createItem(context, colPeople).withTitle("Bollini, Andrea").build(); + SuggestionTarget targetFirstReciter = SuggestionTargetBuilder.createTarget(context, itemFirst) + .withSuggestionCount("reciter", 2).build(); + context.restoreAuthSystemState(); + + String adminToken = getAuthToken(admin.getEmail(), password); + String suggestionId = "reciter:" + itemFirst.getID().toString() + ":1"; + // the suggestion is here + getClient(adminToken).perform(get("/api/integration/suggestions/" + suggestionId)).andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$", matchSuggestion("reciter", itemFirst, "Suggestion reciter 1", "1"))) + .andExpect(jsonPath("$._links.self.href", + Matchers.endsWith("/api/integration/suggestions/" + suggestionId))); + Integer workspaceItemId = null; + try { + ObjectMapper mapper = new ObjectMapper(); + MvcResult mvcResult = getClient(adminToken).perform( + post("/api/submission/workspaceitems?owningCollection=" + colPublications.getID().toString()) + .contentType(parseMediaType(TEXT_URI_LIST_VALUE)) + .content("http://localhost/api/integration/externalsources/" + + MockSuggestionExternalDataSource.NAME + "/entryValues/" + suggestionId)) + .andExpect(status().isCreated()).andReturn(); + String content = mvcResult.getResponse().getContentAsString(); + Map map = mapper.readValue(content, Map.class); + workspaceItemId = (Integer) map.get("id"); + String itemUuidString = String.valueOf(((Map) ((Map) map.get("_embedded")).get("item")).get("uuid")); + + getClient(adminToken).perform(get("/api/submission/workspaceitems/" + workspaceItemId)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.allOf( + hasJsonPath("$.id", is(workspaceItemId)), + hasJsonPath("$.type", is("workspaceitem")), + hasJsonPath("$._embedded.item", Matchers.allOf( + hasJsonPath("$.id", is(itemUuidString)), + hasJsonPath("$.uuid", is(itemUuidString)), + hasJsonPath("$.type", is("item")), + hasJsonPath("$.metadata", Matchers.allOf( + MetadataMatcher.matchMetadata("dc.title", "Title Suggestion 1") + ))))) + )); + + getClient(adminToken).perform(get("/api/integration/suggestions/" + suggestionId)) + .andExpect(status().isNotFound()); + // 1 suggestion is still pending + getClient(adminToken) + .perform(get("/api/integration/suggestions/search/findByTargetAndSource").param("source", "reciter") + .param("target", itemFirst.getID().toString())) + .andExpect(status().isOk()).andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.suggestions", Matchers.contains( + matchSuggestion("reciter", itemFirst, "Suggestion reciter 2", "2")))) + .andExpect(jsonPath("$.page.size", is(20))).andExpect(jsonPath("$.page.totalElements", is(1))); + } finally { + if (workspaceItemId != null) { + WorkspaceItemBuilder.deleteWorkspaceItem(workspaceItemId); + } + } + } + + @Test + public void rejectSuggestionTest() throws Exception { + context.turnOffAuthorisationSystem(); + Collection colPublications = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Publications").build(); + Item itemFirst = ItemBuilder.createItem(context, colPeople).withTitle("Bollini, Andrea").build(); + SuggestionTarget targetFirstReciter = SuggestionTargetBuilder.createTarget(context, itemFirst) + .withSuggestionCount("reciter", 2).build(); + context.restoreAuthSystemState(); + + String adminToken = getAuthToken(admin.getEmail(), password); + String suggestionId = "reciter:" + itemFirst.getID().toString() + ":1"; + // reject the suggestion + getClient(adminToken).perform(delete("/api/integration/suggestions/" + suggestionId)) + .andExpect(status().isNoContent()); + getClient(adminToken).perform(get("/api/integration/suggestions/" + suggestionId)) + .andExpect(status().isNotFound()); + // 1 suggestion is still pending + getClient(adminToken) + .perform(get("/api/integration/suggestions/search/findByTargetAndSource").param("source", "reciter") + .param("target", itemFirst.getID().toString())) + .andExpect(status().isOk()).andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.suggestions", Matchers.contains( + matchSuggestion("reciter", itemFirst, "Suggestion reciter 2", "2")))) + .andExpect(jsonPath("$.page.size", is(20))).andExpect(jsonPath("$.page.totalElements", is(1))); + } +} \ No newline at end of file diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SuggestionSourceRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SuggestionSourceRestRepositoryIT.java new file mode 100644 index 0000000000..30a1779fbd --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SuggestionSourceRestRepositoryIT.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.rest; + +import static org.dspace.app.rest.matcher.SuggestionSourceMatcher.matchSuggestionSource; +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.app.suggestion.SuggestionTarget; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.ItemBuilder; +import org.dspace.builder.SuggestionTargetBuilder; +import org.dspace.content.Collection; +import org.dspace.content.Item; +import org.hamcrest.Matchers; +import org.junit.Before; +import org.junit.Test; + +/** + * Integration Tests against the /api/integration/suggestionsources endpoint + */ +public class SuggestionSourceRestRepositoryIT extends AbstractControllerIntegrationTest { + private Collection colPeople; + + @Override + @Before + public void setUp() throws Exception { + super.setUp(); + // We turn off the authorization system in order to create the structure as + // defined below + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context).withName("Parent Community").build(); + colPeople = CollectionBuilder.createCollection(context, parentCommunity).withName("People") + .withEntityType("Person").build(); + context.restoreAuthSystemState(); + } + + /** + * Build a list of suggestion target, Bollini, Andrea has suggestion from both + * sources, Digilio, Giuseppe only from reciter Test 0, 3, 6 from both sources, + * Test 1, 2, 4, 5 only from ReCiter and finally Lombardi, Corrado only from + * scopus + */ + private void buildSuggestionTargetsList() { + // We turn off the authorization system in order to create the structure as + // defined below + context.turnOffAuthorisationSystem(); + Item itemFirst = ItemBuilder.createItem(context, colPeople).withTitle("Bollini, Andrea").build(); + SuggestionTarget targetFirstReciter = SuggestionTargetBuilder.createTarget(context, itemFirst) + .withSuggestionCount("reciter", 31).build(); + SuggestionTarget targetFirstScopus = SuggestionTargetBuilder.createTarget(context, itemFirst) + .withSuggestionCount("scopus", 3).build(); + SuggestionTarget targetSecond = SuggestionTargetBuilder + .createTarget(context, colPeople, "Digilio, Giuseppe", eperson).withSuggestionCount("reciter", 11) + .build(); + for (int idx = 0; idx < 8; idx++) { + Item item = ItemBuilder.createItem(context, colPeople).withTitle("Test " + idx).build(); + SuggestionTargetBuilder.createTarget(context, item).withSuggestionCount("reciter", idx + 3).build(); + if (idx % 3 == 0) { + SuggestionTargetBuilder.createTarget(context, item).withSuggestionCount("scopus", idx + 7).build(); + } + } + Item itemLast = ItemBuilder.createItem(context, colPeople).withTitle("Lombardi, Corrado").build(); + SuggestionTarget targetLast = SuggestionTargetBuilder.createTarget(context, itemLast) + .withSuggestionCount("scopus", 3).build(); + context.restoreAuthSystemState(); + } + + @Test + public void findAllTest() throws Exception { + buildSuggestionTargetsList(); + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(get("/api/integration/suggestionsources")).andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.suggestionsources", + Matchers.contains(matchSuggestionSource("reciter", 10), matchSuggestionSource("scopus", 5)))) + .andExpect(jsonPath("$.page.size", is(20))).andExpect(jsonPath("$.page.totalElements", is(2))); + } + + @Test + public void findAllPaginationTest() throws Exception { + buildSuggestionTargetsList(); + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(get("/api/integration/suggestionsources").param("size", "1")) + .andExpect(status().isOk()).andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.suggestionsources", + Matchers.contains(matchSuggestionSource("reciter", 10)))) + .andExpect(jsonPath("$._links.self.href", + Matchers.containsString("/api/integration/suggestionsources"))) + .andExpect(jsonPath("$._links.next.href", + Matchers.allOf(Matchers.containsString("/api/integration/suggestionsources?"), + Matchers.containsString("page=1"), Matchers.containsString("size=1")))) + .andExpect(jsonPath("$._links.last.href", + Matchers.allOf(Matchers.containsString("/api/integration/suggestionsources?"), + Matchers.containsString("page=1"), Matchers.containsString("size=1")))) + .andExpect(jsonPath("$._links.first.href", + Matchers.allOf(Matchers.containsString("/api/integration/suggestionsources?"), + Matchers.containsString("page=0"), Matchers.containsString("size=1")))) + .andExpect(jsonPath("$._links.prev.href").doesNotExist()).andExpect(jsonPath("$.page.size", is(1))) + .andExpect(jsonPath("$.page.totalElements", is(2))); + getClient(adminToken).perform(get("/api/integration/suggestionsources").param("size", "1").param("page", "1")) + .andExpect(status().isOk()).andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.suggestionsources", + Matchers.contains(matchSuggestionSource("scopus", 5)))) + .andExpect(jsonPath("$._links.self.href", + Matchers.containsString("/api/integration/suggestionsources"))) + .andExpect(jsonPath("$._links.next.href").doesNotExist()) + .andExpect(jsonPath("$._links.last.href", + Matchers.allOf(Matchers.containsString("/api/integration/suggestionsources?"), + Matchers.containsString("page=1"), Matchers.containsString("size=1")))) + .andExpect(jsonPath("$._links.first.href", + Matchers.allOf(Matchers.containsString("/api/integration/suggestionsources?"), + Matchers.containsString("page=0"), Matchers.containsString("size=1")))) + .andExpect(jsonPath("$._links.prev.href", + Matchers.allOf(Matchers.containsString("/api/integration/suggestionsources?"), + Matchers.containsString("page=0"), Matchers.containsString("size=1")))) + .andExpect(jsonPath("$.page.size", is(1))).andExpect(jsonPath("$.page.totalElements", is(2))); + } + + @Test + public void findAllNotAdminTest() throws Exception { + buildSuggestionTargetsList(); + // anonymous cannot access the suggestions source endpoint + getClient().perform(get("/api/integration/suggestionsources")).andExpect(status().isUnauthorized()); + // nor normal user + String tokenEperson = getAuthToken(eperson.getEmail(), password); + getClient(tokenEperson).perform(get("/api/integration/suggestionsources")).andExpect(status().isForbidden()); + + } + + @Test + public void findOneTest() throws Exception { + buildSuggestionTargetsList(); + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(get("/api/integration/suggestionsources/reciter")).andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$", matchSuggestionSource("reciter", 10))).andExpect(jsonPath("$._links.self.href", + Matchers.endsWith("/api/integration/suggestionsources/reciter"))); + getClient(adminToken).perform(get("/api/integration/suggestionsources/scopus")).andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$", matchSuggestionSource("scopus", 5))).andExpect( + jsonPath("$._links.self.href", Matchers.endsWith("/api/integration/suggestionsources/scopus"))); + + } + + @Test + public void findOneNotAdminTest() throws Exception { + buildSuggestionTargetsList(); + String epersonToken = getAuthToken(eperson.getEmail(), password); + getClient(epersonToken).perform(get("/api/integration/suggestionsources/reciter")) + .andExpect(status().isForbidden()); + getClient(epersonToken).perform(get("/api/integration/suggestionsources/not-exist")) + .andExpect(status().isForbidden()); + getClient().perform(get("/api/integration/suggestionsources/reciter")).andExpect(status().isUnauthorized()); + getClient().perform(get("/api/integration/suggestionsources/not-exist")).andExpect(status().isUnauthorized()); + } + +} \ No newline at end of file diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SuggestionTargetRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SuggestionTargetRestRepositoryIT.java new file mode 100644 index 0000000000..21108010f5 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SuggestionTargetRestRepositoryIT.java @@ -0,0 +1,597 @@ +/** + * The contents of this file are subject to the license and 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.SuggestionTargetMatcher.matchSuggestionTarget; +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 java.util.UUID; + +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.app.suggestion.SuggestionTarget; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.EPersonBuilder; +import org.dspace.builder.ItemBuilder; +import org.dspace.builder.SuggestionTargetBuilder; +import org.dspace.content.Collection; +import org.dspace.content.Item; +import org.dspace.eperson.EPerson; +import org.hamcrest.Matchers; +import org.junit.Before; +import org.junit.Test; + +/** + * Integration Tests against the /api/integration/suggestiontargets endpoint + */ +public class SuggestionTargetRestRepositoryIT extends AbstractControllerIntegrationTest { + private Collection colPeople; + + @Override + @Before + public void setUp() throws Exception { + super.setUp(); + // We turn off the authorization system in order to create the structure as + // defined below + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context).withName("Parent Community").build(); + colPeople = CollectionBuilder.createCollection(context, parentCommunity).withName("People") + .withEntityType("Person").build(); + context.restoreAuthSystemState(); + } + + /** + * Build a list of suggestion target, Bollini, Andrea has suggestion from both + * sources, Digilio, Giuseppe only from reciter Test 0, 3, 6 from both sources, + * Test 1, 2, 4, 5 only from ReCiter and finally Lombardi, Corrado only from + * scopus + */ + private void buildSuggestionTargetsList() { + // We turn off the authorization system in order to create the structure as + // defined below + context.turnOffAuthorisationSystem(); + Item itemFirst = ItemBuilder.createItem(context, colPeople).withTitle("Bollini, Andrea").build(); + SuggestionTarget targetFirstReciter = SuggestionTargetBuilder.createTarget(context, itemFirst) + .withSuggestionCount("reciter", 31).build(); + SuggestionTarget targetFirstScopus = SuggestionTargetBuilder.createTarget(context, itemFirst) + .withSuggestionCount("scopus", 3).build(); + SuggestionTarget targetSecond = SuggestionTargetBuilder + .createTarget(context, colPeople, "Digilio, Giuseppe", eperson).withSuggestionCount("reciter", 11) + .build(); + for (int idx = 0; idx < 8; idx++) { + Item item = ItemBuilder.createItem(context, colPeople).withTitle("Test " + idx).build(); + SuggestionTargetBuilder.createTarget(context, item).withSuggestionCount("reciter", idx + 3).build(); + if (idx % 3 == 0) { + SuggestionTargetBuilder.createTarget(context, item).withSuggestionCount("scopus", idx + 7).build(); + } + } + Item itemLast = ItemBuilder.createItem(context, colPeople).withTitle("Lombardi, Corrado").build(); + SuggestionTarget targetLast = SuggestionTargetBuilder.createTarget(context, itemLast) + .withSuggestionCount("scopus", 3).build(); + context.restoreAuthSystemState(); + } + + @Test + public void findAllTest() throws Exception { + buildSuggestionTargetsList(); + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(get("/api/integration/suggestiontargets")) + .andExpect(status().isMethodNotAllowed()); + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform(get("/api/integration/suggestiontargets")).andExpect(status().isMethodNotAllowed()); + getClient().perform(get("/api/integration/suggestiontargets")).andExpect(status().isMethodNotAllowed()); + } + + @Test + public void findBySourceTest() throws Exception { + buildSuggestionTargetsList(); + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken) + .perform(get("/api/integration/suggestiontargets/search/findBySource").param("source", "reciter")) + .andExpect(status().isOk()).andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.suggestiontargets", Matchers.contains( + matchSuggestionTarget("Bollini, Andrea", "reciter", 31), + matchSuggestionTarget("Digilio, Giuseppe", "reciter", 11), + matchSuggestionTarget("Test 7", "reciter", 10), matchSuggestionTarget("Test 6", "reciter", 9), + matchSuggestionTarget("Test 5", "reciter", 8), matchSuggestionTarget("Test 4", "reciter", 7), + matchSuggestionTarget("Test 3", "reciter", 6), matchSuggestionTarget("Test 2", "reciter", 5), + matchSuggestionTarget("Test 1", "reciter", 4), matchSuggestionTarget("Test 0", "reciter", 3) + ))) + .andExpect(jsonPath("$._links.self.href", + Matchers.containsString( + "/api/integration/suggestiontargets/search/findBySource?source=reciter"))) + .andExpect(jsonPath("$.page.size", is(20))).andExpect(jsonPath("$.page.totalElements", is(10))); + getClient(adminToken) + .perform(get("/api/integration/suggestiontargets/search/findBySource").param("source", "scopus")) + .andExpect(status().isOk()).andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.suggestiontargets", + Matchers.containsInAnyOrder( + matchSuggestionTarget("Test 6", "scopus", 13), + matchSuggestionTarget("Test 3", "scopus", 10), + matchSuggestionTarget("Test 0", "scopus", 7), + matchSuggestionTarget("Bollini, Andrea", "scopus", 3), + matchSuggestionTarget("Lombardi, Corrado", "scopus", 3)))) + .andExpect(jsonPath("$._links.self.href", + Matchers.containsString( + "/api/integration/suggestiontargets/search/findBySource?source=scopus"))) + .andExpect(jsonPath("$.page.size", is(20))).andExpect(jsonPath("$.page.totalElements", is(5))); + } + + @Test + public void findBySourcePaginationTest() throws Exception { + buildSuggestionTargetsList(); + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken) + .perform(get("/api/integration/suggestiontargets/search/findBySource") + .param("source", "reciter").param("size", "1")) + .andExpect(status().isOk()).andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.suggestiontargets", + Matchers.contains(matchSuggestionTarget("Bollini, Andrea", "reciter", 31)))) + .andExpect(jsonPath("$._links.self.href", + Matchers.allOf( + Matchers.containsString("/api/integration/suggestiontargets/search/findBySource?"), + Matchers.containsString("source=reciter"), Matchers.containsString("size=1")))) + .andExpect(jsonPath("$._links.next.href", + Matchers.allOf( + Matchers.containsString("/api/integration/suggestiontargets/search/findBySource?"), + Matchers.containsString("source=reciter"), Matchers.containsString("page=1"), + Matchers.containsString("size=1")))) + .andExpect(jsonPath("$._links.last.href", + Matchers.allOf( + Matchers.containsString("/api/integration/suggestiontargets/search/findBySource?"), + Matchers.containsString("source=reciter"), Matchers.containsString("page=9"), + Matchers.containsString("size=1")))) + .andExpect(jsonPath("$._links.first.href", + Matchers.allOf( + Matchers.containsString("/api/integration/suggestiontargets/search/findBySource?"), + Matchers.containsString("source=reciter"), Matchers.containsString("page=0"), + Matchers.containsString("size=1")))) + .andExpect(jsonPath("$._links.prev.href").doesNotExist()).andExpect(jsonPath("$.page.size", is(1))) + .andExpect(jsonPath("$.page.totalElements", is(10))); + getClient(adminToken) + .perform(get("/api/integration/suggestiontargets/search/findBySource").param("source", "reciter") + .param("size", "1").param("page", "1")) + .andExpect(status().isOk()).andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.suggestiontargets", + Matchers.contains(matchSuggestionTarget("Digilio, Giuseppe", "reciter", 11)))) + .andExpect(jsonPath("$._links.self.href", + Matchers.allOf( + Matchers.containsString("/api/integration/suggestiontargets/search/findBySource?"), + Matchers.containsString("source=reciter"), Matchers.containsString("page=1"), + Matchers.containsString("size=1")))) + .andExpect(jsonPath("$._links.next.href", + Matchers.allOf( + Matchers.containsString("/api/integration/suggestiontargets/search/findBySource?"), + Matchers.containsString("source=reciter"), Matchers.containsString("page=2"), + Matchers.containsString("size=1")))) + .andExpect(jsonPath("$._links.last.href", + Matchers.allOf( + Matchers.containsString("/api/integration/suggestiontargets/search/findBySource?"), + Matchers.containsString("source=reciter"), Matchers.containsString("page=9"), + Matchers.containsString("size=1")))) + .andExpect(jsonPath("$._links.first.href", + Matchers.allOf( + Matchers.containsString("/api/integration/suggestiontargets/search/findBySource?"), + Matchers.containsString("source=reciter"), Matchers.containsString("page=0"), + Matchers.containsString("size=1")))) + .andExpect(jsonPath("$._links.prev.href", + Matchers.allOf( + Matchers.containsString("/api/integration/suggestiontargets/search/findBySource?"), + Matchers.containsString("source=reciter"), Matchers.containsString("page=0"), + Matchers.containsString("size=1")))) + .andExpect(jsonPath("$.page.size", is(1))).andExpect(jsonPath("$.page.totalElements", is(10))); + getClient(adminToken) + .perform(get("/api/integration/suggestiontargets/search/findBySource").param("source", "reciter") + .param("size", "1").param("page", "9")) + .andExpect(status().isOk()).andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.suggestiontargets", + Matchers.contains(matchSuggestionTarget("Test 0", "reciter", 3)))) + .andExpect(jsonPath("$._links.self.href", + Matchers.allOf( + Matchers.containsString("/api/integration/suggestiontargets/search/findBySource?"), + Matchers.containsString("source=reciter"), Matchers.containsString("page=9"), + Matchers.containsString("size=1")))) + .andExpect(jsonPath("$._links.next.href").doesNotExist()) + .andExpect(jsonPath("$._links.last.href", + Matchers.allOf( + Matchers.containsString("/api/integration/suggestiontargets/search/findBySource?"), + Matchers.containsString("source=reciter"), Matchers.containsString("page=9"), + Matchers.containsString("size=1")))) + .andExpect(jsonPath("$._links.first.href", + Matchers.allOf( + Matchers.containsString("/api/integration/suggestiontargets/search/findBySource?"), + Matchers.containsString("source=reciter"), Matchers.containsString("page=0"), + Matchers.containsString("size=1")))) + .andExpect(jsonPath("$._links.prev.href", + Matchers.allOf( + Matchers.containsString("/api/integration/suggestiontargets/search/findBySource?"), + Matchers.containsString("source=reciter"), Matchers.containsString("page=8"), + Matchers.containsString("size=1")))) + .andExpect(jsonPath("$.page.size", is(1))).andExpect(jsonPath("$.page.totalElements", is(10))); + getClient(adminToken) + .perform(get("/api/integration/suggestiontargets/search/findBySource").param("source", "scopus") + .param("size", "3").param("page", "0")) + .andExpect(status().isOk()).andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.suggestiontargets", + Matchers.contains( + matchSuggestionTarget("Test 6", "scopus", 13), + matchSuggestionTarget("Test 3", "scopus", 10), + matchSuggestionTarget("Test 0", "scopus", 7)))) + .andExpect(jsonPath("$._links.self.href", + Matchers.allOf( + Matchers.containsString("/api/integration/suggestiontargets/search/findBySource?"), + Matchers.containsString("source=scopus"), Matchers.containsString("page=0"), + Matchers.containsString("size=3")))) + .andExpect(jsonPath("$._links.next.href", + Matchers.allOf( + Matchers.containsString("/api/integration/suggestiontargets/search/findBySource?"), + Matchers.containsString("source=scopus"), Matchers.containsString("page=1"), + Matchers.containsString("size=3")))) + .andExpect(jsonPath("$._links.last.href", + Matchers.allOf( + Matchers.containsString("/api/integration/suggestiontargets/search/findBySource?"), + Matchers.containsString("source=scopus"), Matchers.containsString("page=1"), + Matchers.containsString("size=3")))) + .andExpect(jsonPath("$._links.first.href", + Matchers.allOf( + Matchers.containsString("/api/integration/suggestiontargets/search/findBySource?"), + Matchers.containsString("source=scopus"), Matchers.containsString("page=0"), + Matchers.containsString("size=3")))) + .andExpect(jsonPath("$._links.prev.href").doesNotExist()).andExpect(jsonPath("$.page.size", is(3))) + .andExpect(jsonPath("$.page.totalElements", is(5))); + + getClient(adminToken) + .perform(get("/api/integration/suggestiontargets/search/findBySource").param("source", "scopus") + .param("size", "3").param("page", "1")) + .andExpect(status().isOk()).andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.suggestiontargets", Matchers.iterableWithSize(2))) + .andExpect(jsonPath("$._embedded.suggestiontargets", + Matchers.containsInAnyOrder(matchSuggestionTarget("Bollini, Andrea", "scopus", 3), + matchSuggestionTarget("Lombardi, Corrado", "scopus", 3)))) + .andExpect(jsonPath("$._links.self.href", + Matchers.allOf( + Matchers.containsString("/api/integration/suggestiontargets/search/findBySource?"), + Matchers.containsString("source=scopus"), Matchers.containsString("page=1"), + Matchers.containsString("size=3")))) + .andExpect(jsonPath("$._links.next.href").doesNotExist()) + .andExpect(jsonPath("$._links.last.href", + Matchers.allOf( + Matchers.containsString("/api/integration/suggestiontargets/search/findBySource?"), + Matchers.containsString("source=scopus"), Matchers.containsString("page=1"), + Matchers.containsString("size=3")))) + .andExpect(jsonPath("$._links.first.href", + Matchers.allOf( + Matchers.containsString("/api/integration/suggestiontargets/search/findBySource?"), + Matchers.containsString("source=scopus"), Matchers.containsString("page=0"), + Matchers.containsString("size=3")))) + .andExpect(jsonPath("$._links.prev.href", + Matchers.allOf( + Matchers.containsString("/api/integration/suggestiontargets/search/findBySource?"), + Matchers.containsString("source=scopus"), Matchers.containsString("page=0"), + Matchers.containsString("size=3")))) + .andExpect(jsonPath("$.page.size", is(3))).andExpect(jsonPath("$.page.totalElements", is(5))); + } + + @Test + public void findBySourceUnAuthenticatedTest() throws Exception { + buildSuggestionTargetsList(); + // anonymous cannot access the suggestions endpoint + getClient().perform(get("/api/integration/suggestiontargets/search/findBySource").param("source", "reciter")) + .andExpect(status().isUnauthorized()); + getClient().perform(get("/api/integration/suggestiontargets/search/findBySource").param("source", "not-exist")) + .andExpect(status().isUnauthorized()); + } + + @Test + public void findBySourceForbiddenTest() throws Exception { + buildSuggestionTargetsList(); + String tokenEperson = getAuthToken(eperson.getEmail(), password); + getClient(tokenEperson) + .perform(get("/api/integration/suggestiontargets/search/findBySource").param("source", "reciter")) + .andExpect(status().isForbidden()); + getClient(tokenEperson) + .perform(get("/api/integration/suggestiontargets/search/findBySource").param("source", "not-exist")) + .andExpect(status().isForbidden()); + } + + @Test + public void findBySourceBadRequestTest() throws Exception { + String tokenEperson = getAuthToken(admin.getEmail(), password); + getClient(tokenEperson).perform(get("/api/integration/suggestiontargets/search/findBySource")) + .andExpect(status().isBadRequest()); + } + + @Test + public void findOneTest() throws Exception { + context.turnOffAuthorisationSystem(); + SuggestionTarget target = SuggestionTargetBuilder.createTarget(context, colPeople, "Bollini, Andrea") + .withSuggestionCount("scopus", 3).build(); + SuggestionTarget targetEPerson = SuggestionTargetBuilder + .createTarget(context, colPeople, "Digilio, Giuseppe", eperson).withSuggestionCount("reciter", 11) + .build(); + context.restoreAuthSystemState(); + String uuidStr = target.getID().toString(); + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(get("/api/integration/suggestiontargets/" + uuidStr)).andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$", matchSuggestionTarget("Bollini, Andrea", "scopus", 3))).andExpect(jsonPath( + "$._links.self.href", Matchers.endsWith("/api/integration/suggestiontargets/" + uuidStr))); + // build a person profile linked to our eperson + String uuidStrEpersonProfile = targetEPerson.getID().toString(); + String tokenEperson = getAuthToken(eperson.getEmail(), password); + getClient(tokenEperson).perform(get("/api/integration/suggestiontargets/" + uuidStrEpersonProfile)) + .andExpect(status().isOk()).andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$", matchSuggestionTarget("Digilio, Giuseppe", "reciter", 11))) + .andExpect(jsonPath("$._links.self.href", + Matchers.endsWith("/api/integration/suggestiontargets/" + uuidStrEpersonProfile))); + } + + @Test + public void findOneFullProjectionTest() throws Exception { + context.turnOffAuthorisationSystem(); + SuggestionTarget target = SuggestionTargetBuilder.createTarget(context, colPeople, "Bollini, Andrea") + .withSuggestionCount("scopus", 3).build(); + SuggestionTarget targetEPerson = SuggestionTargetBuilder + .createTarget(context, colPeople, "Digilio, Giuseppe", eperson).withSuggestionCount("reciter", 11) + .build(); + context.restoreAuthSystemState(); + String uuidStrTarget = target.getID().toString(); + String uuidStrProfile = target.getTarget().getID().toString(); + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken) + .perform(get("/api/integration/suggestiontargets/" + uuidStrTarget).param("projection", "full")) + .andExpect(status().isOk()).andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$", matchSuggestionTarget("Bollini, Andrea", "scopus", 3))) + .andExpect(jsonPath("$._links.self.href", + Matchers.endsWith("/api/integration/suggestiontargets/" + uuidStrTarget))) + .andExpect(jsonPath("$._embedded.target.id", Matchers.is(uuidStrProfile))); + String uuidStrEpersonTarget = targetEPerson.getID().toString(); + String uuidStrEpersonProfile = targetEPerson.getTarget().getID().toString(); + String epersonToken = getAuthToken(eperson.getEmail(), password); + getClient(epersonToken) + .perform(get("/api/integration/suggestiontargets/" + uuidStrEpersonTarget).param("projection", "full")) + .andExpect(status().isOk()).andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$", matchSuggestionTarget("Digilio, Giuseppe", "reciter", 11))) + .andExpect(jsonPath("$._links.self.href", + Matchers.endsWith("/api/integration/suggestiontargets/" + uuidStrEpersonTarget))) + .andExpect(jsonPath("$._embedded.target.id", Matchers.is(uuidStrEpersonProfile))); + } + + @Test + public void findOneUnAuthenticatedTest() throws Exception { + context.turnOffAuthorisationSystem(); + SuggestionTarget target = SuggestionTargetBuilder.createTarget(context, colPeople, "Bollini, Andrea") + .withSuggestionCount("reciter", 31).build(); + context.restoreAuthSystemState(); + String uuidStr = target.getID().toString(); + getClient().perform(get("/api/integration/suggestiontargets/" + uuidStr)).andExpect(status().isUnauthorized()); + } + + @Test + public void findOneForbiddenTest() throws Exception { + // build a generic person profile + context.turnOffAuthorisationSystem(); + SuggestionTarget target = SuggestionTargetBuilder.createTarget(context, colPeople, "Bollini, Andrea") + .withSuggestionCount("reciter", 31).build(); + context.restoreAuthSystemState(); + String uuidStr = target.getID().toString(); + String tokenEperson = getAuthToken(eperson.getEmail(), password); + getClient(tokenEperson).perform(get("/api/integration/suggestiontargets/" + uuidStr)) + .andExpect(status().isForbidden()); + } + + @Test + public void findOneTestWrongID() throws Exception { + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(get("/api/integration/suggestiontargets/not-an-uuid")) + .andExpect(status().isNotFound()); + getClient(adminToken).perform(get("/api/integration/suggestiontargets/" + UUID.randomUUID())) + .andExpect(status().isNotFound()); + getClient(adminToken).perform(get("/api/integration/suggestiontargets/scopus:" + UUID.randomUUID())) + .andExpect(status().isNotFound()); + getClient(adminToken).perform(get("/api/integration/suggestiontargets/invalid:" + UUID.randomUUID())) + .andExpect(status().isNotFound()); + + String epersonToken = getAuthToken(eperson.getEmail(), password); + getClient(epersonToken).perform(get("/api/integration/suggestiontargets/not-an-uuid")) + .andExpect(status().isForbidden()); + getClient(epersonToken).perform(get("/api/integration/suggestiontargets/" + UUID.randomUUID())) + .andExpect(status().isForbidden()); + getClient(epersonToken).perform(get("/api/integration/suggestiontargets/scopus:" + UUID.randomUUID())) + .andExpect(status().isForbidden()); + getClient(epersonToken).perform(get("/api/integration/suggestiontargets/invalid:" + UUID.randomUUID())) + .andExpect(status().isForbidden()); + + getClient().perform(get("/api/integration/suggestiontargets/not-an-uuid")).andExpect(status().isUnauthorized()); + getClient().perform(get("/api/integration/suggestiontargets/" + UUID.randomUUID())) + .andExpect(status().isUnauthorized()); + getClient().perform(get("/api/integration/suggestiontargets/scopus:" + UUID.randomUUID())) + .andExpect(status().isUnauthorized()); + getClient().perform(get("/api/integration/suggestiontargets/invalid:" + UUID.randomUUID())) + .andExpect(status().isUnauthorized()); + } + + @Test + public void findByTargetTest() throws Exception { + context.turnOffAuthorisationSystem(); + Item itemFirst = ItemBuilder.createItem(context, colPeople).withTitle("Bollini, Andrea").build(); + SuggestionTarget targetFirstReciter = SuggestionTargetBuilder.createTarget(context, itemFirst) + .withSuggestionCount("reciter", 31).build(); + SuggestionTarget targetFirstScopus = SuggestionTargetBuilder.createTarget(context, itemFirst) + .withSuggestionCount("scopus", 3).build(); + Item itemLast = ItemBuilder.createItem(context, colPeople).withTitle("Lombardi, Corrado") + .withDSpaceObjectOwner(eperson.getFullName(), eperson.getID().toString()).build(); + SuggestionTarget targetLast = SuggestionTargetBuilder.createTarget(context, itemLast) + .withSuggestionCount("scopus", 2).build(); + context.restoreAuthSystemState(); + + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken) + .perform(get("/api/integration/suggestiontargets/search/findByTarget").param("target", + itemFirst.getID().toString())) + .andExpect(status().isOk()).andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.suggestiontargets", + Matchers.contains(matchSuggestionTarget("Bollini, Andrea", "reciter", 31), + matchSuggestionTarget("Bollini, Andrea", "scopus", 3)))) + .andExpect(jsonPath("$._links.self.href", + Matchers.containsString("/api/integration/suggestiontargets/search/findByTarget?target=" + + itemFirst.getID().toString()))) + .andExpect(jsonPath("$.page.size", is(20))).andExpect(jsonPath("$.page.totalElements", is(2))); + getClient(adminToken) + .perform(get("/api/integration/suggestiontargets/search/findByTarget").param("target", + itemLast.getID().toString())) + .andExpect(status().isOk()).andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.suggestiontargets", + Matchers.contains(matchSuggestionTarget("Lombardi, Corrado", "scopus", 2)))) + .andExpect(jsonPath("$._links.self.href", + Matchers.containsString("/api/integration/suggestiontargets/search/findByTarget?target=" + + itemLast.getID().toString()))) + .andExpect(jsonPath("$.page.size", is(20))).andExpect(jsonPath("$.page.totalElements", is(1))); + String epersonToken = getAuthToken(eperson.getEmail(), password); + getClient(epersonToken) + .perform(get("/api/integration/suggestiontargets/search/findByTarget").param("target", + itemLast.getID().toString())) + .andExpect(status().isOk()).andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.suggestiontargets", + Matchers.contains(matchSuggestionTarget("Lombardi, Corrado", "scopus", 2)))) + .andExpect(jsonPath("$._links.self.href", + Matchers.containsString("/api/integration/suggestiontargets/search/findByTarget?target=" + + itemLast.getID().toString()))) + .andExpect(jsonPath("$.page.size", is(20))).andExpect(jsonPath("$.page.totalElements", is(1))); + } + + @Test + public void findByTargetPaginationTest() throws Exception { + context.turnOffAuthorisationSystem(); + Item itemFirst = ItemBuilder.createItem(context, colPeople).withTitle("Bollini, Andrea").build(); + SuggestionTarget targetFirstReciter = SuggestionTargetBuilder.createTarget(context, itemFirst) + .withSuggestionCount("reciter", 31).build(); + SuggestionTarget targetFirstScopus = SuggestionTargetBuilder.createTarget(context, itemFirst) + .withSuggestionCount("scopus", 3).build(); + context.restoreAuthSystemState(); + + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken) + .perform(get("/api/integration/suggestiontargets/search/findByTarget").param("size", "1") + .param("target", itemFirst.getID().toString())) + .andExpect(status().isOk()).andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.suggestiontargets", + Matchers.contains(matchSuggestionTarget("Bollini, Andrea", "reciter", 31)))) + .andExpect(jsonPath("$._links.self.href", + Matchers.allOf( + Matchers.containsString("/api/integration/suggestiontargets/search/findByTarget?"), + Matchers.containsString("target=" + itemFirst.getID().toString()), + Matchers.containsString("size=1")))) + .andExpect(jsonPath("$._links.next.href", + Matchers.allOf( + Matchers.containsString("/api/integration/suggestiontargets/search/findByTarget?"), + Matchers.containsString("target=" + itemFirst.getID().toString()), + Matchers.containsString("page=1"), Matchers.containsString("size=1")))) + .andExpect(jsonPath("$._links.last.href", + Matchers.allOf( + Matchers.containsString("/api/integration/suggestiontargets/search/findByTarget?"), + Matchers.containsString("target=" + itemFirst.getID().toString()), + Matchers.containsString("page=1"), Matchers.containsString("size=1")))) + .andExpect(jsonPath("$._links.first.href", + Matchers.allOf( + Matchers.containsString("/api/integration/suggestiontargets/search/findByTarget?"), + Matchers.containsString("target=" + itemFirst.getID().toString()), + Matchers.containsString("page=0"), Matchers.containsString("size=1")))) + .andExpect(jsonPath("$._links.prev.href").doesNotExist()).andExpect(jsonPath("$.page.size", is(1))) + .andExpect(jsonPath("$.page.size", is(1))).andExpect(jsonPath("$.page.totalElements", is(2))); + getClient(adminToken) + .perform(get("/api/integration/suggestiontargets/search/findByTarget").param("size", "1") + .param("page", "1").param("target", itemFirst.getID().toString())) + .andExpect(status().isOk()).andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.suggestiontargets", + Matchers.contains(matchSuggestionTarget("Bollini, Andrea", "scopus", 3)))) + .andExpect(jsonPath("$._links.self.href", + Matchers.allOf( + Matchers.containsString("/api/integration/suggestiontargets/search/findByTarget?"), + Matchers.containsString("target=" + itemFirst.getID().toString()), + Matchers.containsString("size=1"), Matchers.containsString("page=1")))) + .andExpect(jsonPath("$._links.prev.href", + Matchers.allOf( + Matchers.containsString("/api/integration/suggestiontargets/search/findByTarget?"), + Matchers.containsString("target=" + itemFirst.getID().toString()), + Matchers.containsString("page=0"), Matchers.containsString("size=1")))) + .andExpect(jsonPath("$._links.last.href", + Matchers.allOf( + Matchers.containsString("/api/integration/suggestiontargets/search/findByTarget?"), + Matchers.containsString("target=" + itemFirst.getID().toString()), + Matchers.containsString("page=1"), Matchers.containsString("size=1")))) + .andExpect(jsonPath("$._links.first.href", + Matchers.allOf( + Matchers.containsString("/api/integration/suggestiontargets/search/findByTarget?"), + Matchers.containsString("target=" + itemFirst.getID().toString()), + Matchers.containsString("page=0"), Matchers.containsString("size=1")))) + .andExpect(jsonPath("$._links.next.href").doesNotExist()).andExpect(jsonPath("$.page.size", is(1))) + .andExpect(jsonPath("$.page.size", is(1))).andExpect(jsonPath("$.page.totalElements", is(2))); + } + + @Test + public void findByTargetUnAuthenticatedTest() throws Exception { + context.turnOffAuthorisationSystem(); + Item itemFirst = ItemBuilder.createItem(context, colPeople).withTitle("Bollini, Andrea").build(); + SuggestionTarget targetFirstReciter = SuggestionTargetBuilder.createTarget(context, itemFirst) + .withSuggestionCount("reciter", 31).build(); + SuggestionTarget targetFirstScopus = SuggestionTargetBuilder.createTarget(context, itemFirst) + .withSuggestionCount("scopus", 3).build(); + Item itemLast = ItemBuilder.createItem(context, colPeople).withTitle("Lombardi, Corrado") + .withDSpaceObjectOwner(eperson.getFullName(), eperson.getID().toString()).build(); + SuggestionTarget targetLast = SuggestionTargetBuilder.createTarget(context, itemLast) + .withSuggestionCount("scopus", 2).build(); + context.restoreAuthSystemState(); + + // anonymous cannot access the suggestions endpoint + getClient().perform(get("/api/integration/suggestiontargets/search/findByTarget").param("target", + itemFirst.getID().toString())).andExpect(status().isUnauthorized()); + getClient().perform(get("/api/integration/suggestiontargets/search/findByTarget").param("target", + itemLast.getID().toString())).andExpect(status().isUnauthorized()); + } + + @Test + public void findByTargetForbiddenTest() throws Exception { + context.turnOffAuthorisationSystem(); + Item itemFirst = ItemBuilder.createItem(context, colPeople).withTitle("Bollini, Andrea").build(); + SuggestionTarget targetFirstReciter = SuggestionTargetBuilder.createTarget(context, itemFirst) + .withSuggestionCount("reciter", 31).build(); + SuggestionTarget targetFirstScopus = SuggestionTargetBuilder.createTarget(context, itemFirst) + .withSuggestionCount("scopus", 3).build(); + Item itemLast = ItemBuilder.createItem(context, colPeople).withTitle("Lombardi, Corrado") + .withDSpaceObjectOwner(eperson.getFullName(), eperson.getID().toString()).build(); + SuggestionTarget targetLast = SuggestionTargetBuilder.createTarget(context, itemLast) + .withSuggestionCount("scopus", 2).build(); + EPerson anotherEPerson = EPersonBuilder.createEPerson(context).withEmail("another@example.com") + .withPassword(password).withNameInMetadata("Test", "Test").build(); + context.restoreAuthSystemState(); + + String tokenAnother = getAuthToken(anotherEPerson.getEmail(), password); + getClient(tokenAnother).perform(get("/api/integration/suggestiontargets/search/findByTarget").param("target", + itemFirst.getID().toString())).andExpect(status().isForbidden()); + getClient(tokenAnother).perform(get("/api/integration/suggestiontargets/search/findByTarget").param("target", + itemLast.getID().toString())).andExpect(status().isForbidden()); + } + + @Test + public void findByTargetBadRequestTest() throws Exception { + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken) + .perform(get("/api/integration/suggestiontargets/search/findByTarget").param("target", "not-exist")) + .andExpect(status().isBadRequest()); + getClient(adminToken).perform(get("/api/integration/suggestiontargets/search/findByTarget")) + .andExpect(status().isBadRequest()); + getClient().perform(get("/api/integration/suggestiontargets/search/findByTarget").param("target", "not-exist")) + .andExpect(status().isBadRequest()); + } + +} \ No newline at end of file diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/SuggestionMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/SuggestionMatcher.java new file mode 100644 index 0000000000..38be403cb2 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/SuggestionMatcher.java @@ -0,0 +1,57 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.matcher; + +import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; +import static org.hamcrest.Matchers.is; + +import org.dspace.content.Item; +import org.hamcrest.Matcher; +import org.hamcrest.Matchers; + +public class SuggestionMatcher { + + private SuggestionMatcher() { } + + // Matcher for a suggestion + public static Matcher matchSuggestion(String source, Item target, String display, + String suggestionId) { + return Matchers.allOf( + hasJsonPath("$.display", is(display)), + hasJsonPath("$.source", is(source)), + hasJsonPath("$.id", is(source + ":" + target.getID().toString() + ":" + suggestionId)), + hasJsonPath("$.metadata['dc.title'][0].value", is("Title Suggestion " + suggestionId )), + hasJsonPath("$.metadata['dc.source'][0].value", is("Source 1")), + hasJsonPath("$.metadata['dc.source'][1].value", is("Source 2")), + hasJsonPath("$.score"), + hasJsonPath("$.evidences"), + hasJsonPath("$.type", is("suggestion")) + ); + } + + public static Matcher matchSuggestion(String source, Item target, String display, + String suggestionId, double score, String evidenceName, double evidenceScore, String evidenceNote) { + return Matchers.allOf( + hasJsonPath("$.display", is(display)), + hasJsonPath("$.source", is(source)), + hasJsonPath("$.id", is(source + ":" + target.getID().toString() + ":" + suggestionId)), + hasJsonPath("$.metadata['dc.title'][0].value", is("Title Suggestion " + suggestionId )), + hasJsonPath("$.metadata['dc.source'][0].value", is("Source 1")), + hasJsonPath("$.metadata['dc.source'][1].value", is("Source 2")), + hasJsonPath("$.score", is(String.format("%.2f", score))), + hasJsonPath("$.evidences." + evidenceName, Matchers.is( + hasJsonPath("$", + Matchers.allOf( + hasJsonPath("$.score", is(String.format("%.2f", evidenceScore))), + hasJsonPath("$.notes", is(evidenceNote)))) + )), + hasJsonPath("$.type", is("suggestion")) + ); + } + +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/SuggestionSourceMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/SuggestionSourceMatcher.java new file mode 100644 index 0000000000..f9d70cef86 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/SuggestionSourceMatcher.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.matcher; + +import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; +import static org.hamcrest.Matchers.is; + +import org.hamcrest.Matcher; +import org.hamcrest.Matchers; + +public class SuggestionSourceMatcher { + + private SuggestionSourceMatcher() { } + + // Matcher for a suggestion target + public static Matcher matchSuggestionSource(String name, int total) { + return Matchers.allOf( + hasJsonPath("$.id", is(name)), + hasJsonPath("$.total", is(total)), + hasJsonPath("$.type", is("suggestionsource")) + ); + } +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/SuggestionTargetMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/SuggestionTargetMatcher.java new file mode 100644 index 0000000000..b88b51020e --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/SuggestionTargetMatcher.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.rest.matcher; + +import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; +import static org.hamcrest.Matchers.is; + +import org.hamcrest.Matcher; +import org.hamcrest.Matchers; + +public class SuggestionTargetMatcher { + + private SuggestionTargetMatcher() { } + + // Matcher for a suggestion target + public static Matcher matchSuggestionTarget(String name, String source, int total) { + return Matchers.allOf( + hasJsonPath("$.display", is(name)), + hasJsonPath("$.source", is(source)), + hasJsonPath("$.total", is(total)), + hasJsonPath("$.type", is("suggestiontarget")) + ); + } +} diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index d5c28da096..d9cce84541 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -1603,6 +1603,7 @@ include = ${module_dir}/authentication-oidc.cfg include = ${module_dir}/authentication-password.cfg include = ${module_dir}/authentication-shibboleth.cfg include = ${module_dir}/authentication-x509.cfg +include = ${module_dir}/authority.cfg include = ${module_dir}/bulkedit.cfg include = ${module_dir}/citation-page.cfg include = ${module_dir}/clamav.cfg @@ -1621,6 +1622,7 @@ include = ${module_dir}/solr-statistics.cfg include = ${module_dir}/solrauthority.cfg include = ${module_dir}/spring.cfg include = ${module_dir}/submission-curation.cfg +include = ${module_dir}/suggestion.cfg include = ${module_dir}/sword-client.cfg include = ${module_dir}/sword-server.cfg include = ${module_dir}/swordv2-server.cfg diff --git a/dspace/config/modules/authority.cfg b/dspace/config/modules/authority.cfg new file mode 100644 index 0000000000..44384f5c3f --- /dev/null +++ b/dspace/config/modules/authority.cfg @@ -0,0 +1,13 @@ +#---------------------------------------------------------------# +#----------------- AUTHORITY CONFIGURATIONS --------------------# +#---------------------------------------------------------------# +# These configs are used by the authority framework # +#---------------------------------------------------------------# + +##### Authority Control Settings ##### +plugin.named.org.dspace.content.authority.ChoiceAuthority = \ + org.dspace.content.authority.EPersonAuthority = EPersonAuthority + +choices.plugin.dspace.object.owner = EPersonAuthority +choices.presentation.dspace.object.owner = suggest +authority.controlled.dspace.object.owner = true diff --git a/dspace/config/modules/suggestion.cfg b/dspace/config/modules/suggestion.cfg new file mode 100644 index 0000000000..0792c46ebf --- /dev/null +++ b/dspace/config/modules/suggestion.cfg @@ -0,0 +1,7 @@ +#---------------------------------------------------------------# +#-------------------Suggestion CONFIGURATIONS-------------------# +#---------------------------------------------------------------# +# Configuration properties used by publication claim # +# (suggestion) service # +#---------------------------------------------------------------# +suggestion.solr.server = ${solr.server}/${solr.multicorePrefix}suggestion diff --git a/dspace/config/registries/dspace-types.xml b/dspace/config/registries/dspace-types.xml index ab0c1bc40f..38033cd5c5 100644 --- a/dspace/config/registries/dspace-types.xml +++ b/dspace/config/registries/dspace-types.xml @@ -43,4 +43,12 @@ enabled Stores a boolean text value (true or false) to indicate if the iiif feature is enabled or not for the dspace object. If absent the value is derived from the parent dspace object + + + dspace + object + owner + + + diff --git a/dspace/config/spring/api/external-services.xml b/dspace/config/spring/api/external-services.xml index 9e28e5d559..c85f6cb368 100644 --- a/dspace/config/spring/api/external-services.xml +++ b/dspace/config/spring/api/external-services.xml @@ -67,6 +67,18 @@ + + + + + + + + + + + + diff --git a/dspace/config/spring/api/openaire-integration.xml b/dspace/config/spring/api/openaire-integration.xml new file mode 100644 index 0000000000..12a013af68 --- /dev/null +++ b/dspace/config/spring/api/openaire-integration.xml @@ -0,0 +1,226 @@ + + + + + + + Defines which metadatum is mapped on which metadatum. Note that while the key must be unique it + only matters here for postprocessing of the value. The mapped MetadatumContributor has full control over + what metadatafield is generated. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Defines which metadatum is mapped on which metadatum. Note that while the key must be unique it + only matters here for postprocessing of the value. The mapped MetadatumContributor has full control over + what metadatafield is generated. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dspace/config/spring/api/scripts.xml b/dspace/config/spring/api/scripts.xml index e7c55549c7..5aee3bd9b2 100644 --- a/dspace/config/spring/api/scripts.xml +++ b/dspace/config/spring/api/scripts.xml @@ -50,4 +50,10 @@ + + + + + + diff --git a/dspace/config/spring/api/solr-services.xml b/dspace/config/spring/api/solr-services.xml index 698a824184..b6129a9f67 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/api/suggestions.xml b/dspace/config/spring/api/suggestions.xml new file mode 100644 index 0000000000..03a96d1329 --- /dev/null +++ b/dspace/config/spring/api/suggestions.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + dc.title + crisrp.name + crisrp.name.translated + crisrp.name.variant + + + + + + + + dc.contributor.author + + + + + dc.title + crisrp.name + crisrp.name.translated + crisrp.name.variant + + + + + + + + + + + + + diff --git a/dspace/solr/suggestion/conf/admin-extra.html b/dspace/solr/suggestion/conf/admin-extra.html new file mode 100644 index 0000000000..aa739da862 --- /dev/null +++ b/dspace/solr/suggestion/conf/admin-extra.html @@ -0,0 +1,31 @@ + + + diff --git a/dspace/solr/suggestion/conf/elevate.xml b/dspace/solr/suggestion/conf/elevate.xml new file mode 100644 index 0000000000..7630ebe20f --- /dev/null +++ b/dspace/solr/suggestion/conf/elevate.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + diff --git a/dspace/solr/suggestion/conf/protwords.txt b/dspace/solr/suggestion/conf/protwords.txt new file mode 100644 index 0000000000..1dfc0abecb --- /dev/null +++ b/dspace/solr/suggestion/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/suggestion/conf/schema.xml b/dspace/solr/suggestion/conf/schema.xml new file mode 100644 index 0000000000..ddb9954653 --- /dev/null +++ b/dspace/solr/suggestion/conf/schema.xml @@ -0,0 +1,547 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +suggestion_fullid + + + + + diff --git a/dspace/solr/suggestion/conf/scripts.conf b/dspace/solr/suggestion/conf/scripts.conf new file mode 100644 index 0000000000..f58b262ae0 --- /dev/null +++ b/dspace/solr/suggestion/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/suggestion/conf/solrconfig.xml b/dspace/solr/suggestion/conf/solrconfig.xml new file mode 100644 index 0000000000..1ffbffe57c --- /dev/null +++ b/dspace/solr/suggestion/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 + + + + + + + + + + + + + + + + + + + + + + false + + + + + + 20 + + + 200 + + + + + + + + + + + + static firstSearcher warming in solrconfig.xml + + + + + + false + + + 2 + + + + + + + + + + + + + + + + + + + + + + + explicit + 10 + suggestion_id + + + + + + + + + + + + + + explicit + json + true + suggestion_id + + + + + + + + true + json + true + + + + + + + + explicit + + + velocity + browse + layout + Solritas + + + edismax + + text^0.5 features^1.0 name^1.2 sku^1.5 suggestion_id^10.0 manu^1.1 cat^1.4 + title^10.0 description^5.0 keywords^5.0 author^2.0 resourcename^1.0 + + suggestion_id + 100% + *:* + 10 + *,score + + + text^0.5 features^1.0 name^1.2 sku^1.5 suggestion_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,suggestion_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 + suggestion_id + solr.DirectSolrSpellChecker + + internal + + 0.5 + + 2 + + 1 + + 5 + + 4 + + 0.01 + + + + + + wordbreak + solr.WordBreakSolrSpellChecker + name + true + true + 10 + + + + + + + + + + + + + + + + suggestion_id + + default + wordbreak + on + true + 10 + 5 + 5 + true + true + 10 + 5 + + + spellcheck + + + + + + + + + + suggestion_id + true + + + tvComponent + + + + + + + + + default + + + org.carrot2.clustering.lingo.LingoClusteringAlgorithm + + + 20 + + + clustering/carrot2 + + + ENGLISH + + + stc + org.carrot2.clustering.stc.STCClusteringAlgorithm + + + + + + + true + default + true + + name + suggestion_id + + features + + true + + + + false + + edismax + + text^0.5 features^1.0 name^1.2 sku^1.5 suggestion_id^10.0 manu^1.1 cat^1.4 + + *:* + 10 + *,score + + + clustering + + + + + + + + + + true + false + + + terms + + + + + + + + string + elevate.xml + + + + + + explicit + suggestion_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/suggestion/conf/spellings.txt b/dspace/solr/suggestion/conf/spellings.txt new file mode 100644 index 0000000000..d7ede6f561 --- /dev/null +++ b/dspace/solr/suggestion/conf/spellings.txt @@ -0,0 +1,2 @@ +pizza +history \ No newline at end of file diff --git a/dspace/solr/suggestion/conf/stopwords.txt b/dspace/solr/suggestion/conf/stopwords.txt new file mode 100644 index 0000000000..8433c832d2 --- /dev/null +++ b/dspace/solr/suggestion/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/suggestion/conf/synonyms.txt b/dspace/solr/suggestion/conf/synonyms.txt new file mode 100644 index 0000000000..b0e31cb7ec --- /dev/null +++ b/dspace/solr/suggestion/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/suggestion/core.properties b/dspace/solr/suggestion/core.properties new file mode 100644 index 0000000000..e69de29bb2 From d59c0b72cf218b1f2b7d661d82ecf70f897b6fb2 Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Wed, 30 Mar 2022 13:43:01 +0200 Subject: [PATCH 009/412] [CST-5249] Added EPersonAuthority class --- .../content/authority/EPersonAuthority.java | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 dspace-api/src/main/java/org/dspace/content/authority/EPersonAuthority.java diff --git a/dspace-api/src/main/java/org/dspace/content/authority/EPersonAuthority.java b/dspace-api/src/main/java/org/dspace/content/authority/EPersonAuthority.java new file mode 100644 index 0000000000..7a1510d721 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/authority/EPersonAuthority.java @@ -0,0 +1,93 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.content.authority; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import org.apache.log4j.Logger; +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; +import org.dspace.eperson.factory.EPersonServiceFactory; +import org.dspace.eperson.service.EPersonService; +import org.dspace.util.UUIDUtils; + +/** + * + * @author Mykhaylo Boychuk (4science.it) + */ +public class EPersonAuthority implements ChoiceAuthority { + private static final Logger log = Logger.getLogger(EPersonAuthority.class); + + /** + * the name assigned to the specific instance by the PluginService, @see + * {@link NameAwarePlugin} + **/ + private String authorityName; + + private EPersonService ePersonService = EPersonServiceFactory.getInstance().getEPersonService(); + + @Override + public Choices getBestMatch(String text, String locale) { + return getMatches(text, 0, 2, locale); + } + + @Override + public Choices getMatches(String text, int start, int limit, String locale) { + Context context = null; + if (limit <= 0) { + limit = 20; + } + context = new Context(); + List ePersons = null; + try { + ePersons = ePersonService.search(context, text, start, limit); + } catch (SQLException e) { + log.error(e.getMessage(), e); + throw new RuntimeException(e.getMessage(), e); + } + List choiceList = new ArrayList(); + for (EPerson eperson : ePersons) { + choiceList.add(new Choice(eperson.getID().toString(), eperson.getFullName(), eperson.getFullName())); + } + Choice[] results = new Choice[choiceList.size()]; + results = choiceList.toArray(results); + return new Choices(results, start, ePersons.size(), Choices.CF_AMBIGUOUS, ePersons.size() > (start + limit), 0); + } + + @Override + public String getLabel(String key, String locale) { + + UUID uuid = UUIDUtils.fromString(key); + if (uuid == null) { + return null; + } + + Context context = new Context(); + try { + EPerson ePerson = ePersonService.find(context, uuid); + return ePerson != null ? ePerson.getFullName() : null; + } catch (SQLException e) { + log.error(e.getMessage(), e); + throw new RuntimeException(e.getMessage(), e); + } + + } + + @Override + public String getPluginInstanceName() { + return authorityName; + } + + @Override + public void setPluginInstanceName(String name) { + this.authorityName = name; + } +} \ No newline at end of file From 561902b123cef0e6ad62bc842282ab4cfd9041ad Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Wed, 30 Mar 2022 17:23:13 +0200 Subject: [PATCH 010/412] [CST-5249] Fixed ExternalSourcesRestControllerIT test --- .../org/dspace/app/rest/ExternalSourcesRestControllerIT.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 4c610c74bc..0744bc9508 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 @@ -53,7 +53,7 @@ public class ExternalSourcesRestControllerIT extends AbstractControllerIntegrati ExternalSourceMatcher.matchExternalSource( "openAIREFunding", "openAIREFunding", false) ))) - .andExpect(jsonPath("$.page.totalElements", Matchers.is(7))); + .andExpect(jsonPath("$.page.totalElements", Matchers.is(8))); } @Test From 714293cc9e0f68cac7cfd878f46385443893a727 Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Fri, 22 Apr 2022 16:46:56 +0200 Subject: [PATCH 011/412] [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 70e7624197..099982d289 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 8051362cfa..e16c18edd0 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.sql.SQLException; 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 @@ import org.springframework.beans.factory.annotation.Autowired; * @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 class NBEntityMetadataAction implements NBAction { } @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 class NBEntityMetadataAction implements NBAction { } } + /** + * 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 @@ public class NBEntityMetadataAction implements NBAction { 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 ac9fdd5c3f..9ebd796a80 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 class NBEventActionServiceImpl implements NBEventActionService { 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 class NBEventActionServiceImpl implements NBEventActionService { @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 3d7e2114ce..216f44a2d5 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 @@ package org.dspace.app.nbevent; 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 @@ import org.springframework.beans.factory.annotation.Autowired; * @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 class NBMetadataMapAction implements NBAction { 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 class NBMetadataMapAction implements NBAction { } 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 0bff9e05ff..f08b3f7db4 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 @@ package org.dspace.app.nbevent; 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 @@ import org.springframework.beans.factory.annotation.Autowired; * @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 class NBSimpleMetadataAction implements NBAction { } @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 e74547d531..42a416bf90 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 @@ package org.dspace.app.nbevent; 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 4c59ab1c85..e341c9bd60 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 @@ import org.dspace.content.NBEvent; * @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 188139afef..5558aa3cb0 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 @@ package org.dspace.app.nbevent.service.dto; 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 03fbde444a..84f02474ec 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.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; import org.apache.solr.client.solrj.SolrQuery.ORDER; @@ -55,8 +54,6 @@ 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; 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 e99fbaefa1..a029d1f3e3 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 java.util.Date; 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 @@ public class NBEvent { } - 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 21bbdfff29..1acbbf51bb 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.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.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 class NBEventConverter implements DSpaceConverter { 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 a09d115359..f0a0a12194 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.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.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 class NBEventMatcher { 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 class NBEventMatcher { } } - 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 68baec3d1d..993a637a18 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 34dca93ebb..8cb039c39f 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 aa739da862..0000000000 --- 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 7630ebe20f..0000000000 --- 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 338fbdcdcd..68eb79afd0 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 f58b262ae0..0000000000 --- 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 a4cfbed4a9..0565e56df4 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} + + + - - + + + - - - - + this cache will not be autowarmed. --> + - - + + + + + - - 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 + application/json - - - - - - - 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 deleted file mode 100644 index d7ede6f561..0000000000 --- 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 013/412] [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 c5909de962..216f44a2d5 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 class NBOpenaireMetadataMapAction implements NBAction { @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 8666353f8a..f08b3f7db4 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 class NBOpenaireSimpleMetadataAction implements NBAction { @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 f7bb4b2e48..30875080a0 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 class NBEventRestController { 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 4fc5a67ac82ce329ea0c986b2647d4ebbae7fbda Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Wed, 4 May 2022 13:16:57 +0200 Subject: [PATCH 014/412] [CST-5249] Fixed suggestion import --- .../service/OpenAireImportMetadataSourceServiceImpl.java | 2 +- dspace/config/spring/api/suggestions.xml | 6 ------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/importer/external/openaire/service/OpenAireImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/openaire/service/OpenAireImportMetadataSourceServiceImpl.java index 5c19b63970..45737b8b22 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/openaire/service/OpenAireImportMetadataSourceServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/openaire/service/OpenAireImportMetadataSourceServiceImpl.java @@ -333,7 +333,7 @@ public class OpenAireImportMetadataSourceServiceImpl extends AbstractImportMetad Namespace.getNamespace("dri", "http://www.driver-repository.eu/namespace/dri"), Namespace.getNamespace("oaf", "http://namespace.openaire.eu/oaf"), Namespace.getNamespace("xsi", "http://www.w3.org/2001/XMLSchema-instance")); - XPathExpression xpath = XPathFactory.instance().compile("/results/result", + XPathExpression xpath = XPathFactory.instance().compile("//results/result", Filters.element(), null, namespaces); List recordsList = xpath.evaluate(root); diff --git a/dspace/config/spring/api/suggestions.xml b/dspace/config/spring/api/suggestions.xml index 03a96d1329..c576c34427 100644 --- a/dspace/config/spring/api/suggestions.xml +++ b/dspace/config/spring/api/suggestions.xml @@ -32,9 +32,6 @@ dc.title - crisrp.name - crisrp.name.translated - crisrp.name.variant @@ -49,9 +46,6 @@ dc.title - crisrp.name - crisrp.name.translated - crisrp.name.variant From 71946ac2213e06f7dbb97bd7a01583f0fd1b389b Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Wed, 4 May 2022 14:02:30 +0200 Subject: [PATCH 015/412] [CST-5249] Fixed EntityTypeRestRepositoryIT test --- .../java/org/dspace/app/rest/EntityTypeRestRepositoryIT.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/EntityTypeRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/EntityTypeRestRepositoryIT.java index 740a2c0dc3..2de61bb43d 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/EntityTypeRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/EntityTypeRestRepositoryIT.java @@ -416,6 +416,8 @@ public class EntityTypeRestRepositoryIT extends AbstractEntityIntegrationTest { .setSupportedEntityTypes(Arrays.asList("Publication")); ((AbstractExternalDataProvider) externalDataService.getExternalDataProvider("pubmed")) .setSupportedEntityTypes(Arrays.asList("Publication")); + ((AbstractExternalDataProvider) externalDataService.getExternalDataProvider("suggestion")) + .setSupportedEntityTypes(Arrays.asList("Publication")); // these are similar to the previous checks but now we have restricted the mock and pubmed providers // to support only publication, this mean that there are no providers suitable for funding @@ -439,6 +441,8 @@ public class EntityTypeRestRepositoryIT extends AbstractEntityIntegrationTest { .setSupportedEntityTypes(null); ((AbstractExternalDataProvider) externalDataService.getExternalDataProvider("pubmed")) .setSupportedEntityTypes(null); + ((AbstractExternalDataProvider) externalDataService.getExternalDataProvider("suggestion")) + .setSupportedEntityTypes(null); } } From 7013673318fd18ba6bd0c5e00bac1dfa66359cb1 Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Thu, 26 May 2022 12:41:15 +0200 Subject: [PATCH 016/412] [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 30875080a0..10618288aa 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 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; From 7259393600140adf7e983cd87989078bc2d1b835 Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Mon, 4 Jul 2022 16:40:15 +0200 Subject: [PATCH 017/412] [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 b53aef2815..64f8a12026 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.security.NoSuchAlgorithmException; 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 @@ public class NBEvent { } - 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 62f8222e24..3657c2fdc4 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 @@ import org.dspace.eperson.EPerson; * */ @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 5263bc559b..bad7ec5d5b 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 e2b66cd749..6a0f5cf8e5 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.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.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.Logger; 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 class OpenaireEventsRunnable extends DSpaceRunnable>() { + 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 class OpenaireEventsRunnable extends DSpaceRunnable 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 926160aa48..c272df1cf4 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.RelationshipService; 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 class NBEntityOpenaireMetadataAction implements NBAction { } @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 @@ public class NBEntityOpenaireMetadataAction implements NBAction { .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 e6a2917384..048e2fe775 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 a14dcf5b22..7bfb940cbb 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.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.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 class NBEventActionServiceImpl implements NBEventActionService { */ 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 8297599bc5..68976430e6 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 class NBEventsDeleteCascadeConsumer implements Consumer { 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 216f44a2d5..038c42bb38 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 class NBOpenaireMetadataMapAction implements NBAction { * 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 f08b3f7db4..6f63c2a64f 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 class NBOpenaireSimpleMetadataAction implements NBAction { } @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 42a416bf90..b3f7be5f52 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 afa9990d3d..1ce09fe45d 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 099982d289..f2aebba799 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 @@ import org.dspace.core.Context; * @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 db93eb95c5..30a74e55ba 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 db3977c109..550027441b 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 class NBEventsDaoImpl extends AbstractHibernateDAO impl @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 599806f425..8e840037de 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 interface NBEventService { * @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 interface NBEventService { * @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 interface NBEventService { * @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 interface NBEventService { * @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 interface NBEventService { * @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 5558aa3cb0..117b764ca0 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 e341c9bd60..2a63f42e61 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 94e9e32eb6..04a830358c 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.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.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 class NBEventServiceImpl implements NBEventService { 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 class NBEventServiceImpl implements NBEventService { } @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 class NBEventServiceImpl implements NBEventService { 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 class NBEventServiceImpl implements NBEventService { } @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 class NBEventServiceImpl implements NBEventService { 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 class NBEventServiceImpl implements NBEventService { 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 class NBEventServiceImpl implements NBEventService { } @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 class NBEventServiceImpl implements NBEventService { 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 class NBEventServiceImpl implements NBEventService { 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 @@ public class NBEventServiceImpl implements NBEventService { } @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 class NBEventServiceImpl implements NBEventService { 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 @@ public class NBEventServiceImpl implements NBEventService { } @Override - public NBSource findSource(String sourceName) { + public QASource findSource(String sourceName) { if (isNotSupportedSource(sourceName)) { return null; @@ -392,7 +392,7 @@ public class NBEventServiceImpl implements NBEventService { 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 class NBEventServiceImpl implements NBEventService { throw new RuntimeException(e); } - NBSource source = new NBSource(); + QASource source = new QASource(); source.setName(sourceName); source.setTotalEvents(0L); @@ -411,10 +411,10 @@ public class NBEventServiceImpl implements NBEventService { } @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 @@ public class NBEventServiceImpl implements NBEventService { } 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 b64c52248b..467de85f85 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 5cf9a0484f..5c3f0fac73 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 5cf9a0484f..5c3f0fac73 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 328daa72be..39d6a6f6b5 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 b71736c468..29703e3ee0 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 107a1a6c02..8053774ea9 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 java.util.List; 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.factory.OrcidServiceFactory; 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 abstract class AbstractBuilder { 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 abstract class AbstractBuilder { 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 3ad22738c3..154bf737d9 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 @@ 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.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 @@ public class NBEventBuilder extends AbstractBuilder { 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 class NBEventBuilder extends AbstractBuilder { @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 12058fbf73..443e6f8d39 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 10618288aa..1584c48e65 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.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.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.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" + * 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 class NBEventRestController { } /** - * 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 class NBEventRestController { */ @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 1acbbf51bb..06c83d0d4e 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.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.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 class NBEventConverter implements DSpaceConverter { 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 @@ public class NBEventConverter implements DSpaceConverter { } @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 a1b496df04..6c1bc0d66c 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 f9ab34da89..efa32baba2 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 ca6ee5d06d..c8c4c88909 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 @@ package org.dspace.app.rest.model; 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 df6187651c..cc460f604c 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 0ccc1a55da..e02755d2ec 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 java.util.Date; 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 @@ import org.dspace.app.rest.RestResourceController; @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 class NBEventRest extends BaseObjectRest { 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 69e230f378..15c8096e02 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 java.util.Date; 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 4bebce27e8..34d5655eb7 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 java.util.Date; 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 b052d3d4da..43e1584a25 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 55db5d6343..860e16cee3 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 78af04a764..139d1e59eb 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 53b1a4be6c..0000000000 --- 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 901d600c10..c711d2ec37 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 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.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.security.access.prepost.PreAuthorize; 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 f173ebebc9..110ebe0e13 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.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.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.security.access.prepost.PreAuthorize; 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 @@ public class NBEventRestRepository extends DSpaceRestRepository 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 76584c4179..2df3836b9b 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 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.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.security.access.prepost.PreAuthorize; 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 9b3ac3fd5c..f9ed484428 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 @@ 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.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.security.access.prepost.PreAuthorize; 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 872aa4d439..dad2310a77 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 @@ 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.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.security.access.prepost.PreAuthorize; 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 0000000000..a279cac83a --- /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 55bfe3d2f1..5f58f014ab 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 @@ 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.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 a9f5d29427..5ccbc98d95 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 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.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.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.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 class NBEventRestRepositoryIT extends AbstractControllerIntegrationTest { 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 class NBEventRestRepositoryIT extends AbstractControllerIntegrationTest { 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 class NBEventRestRepositoryIT extends AbstractControllerIntegrationTest { 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 class NBEventRestRepositoryIT extends AbstractControllerIntegrationTest { 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 class NBEventRestRepositoryIT extends AbstractControllerIntegrationTest { 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 class NBEventRestRepositoryIT extends AbstractControllerIntegrationTest { 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 class NBEventRestRepositoryIT extends AbstractControllerIntegrationTest { 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 class NBEventRestRepositoryIT extends AbstractControllerIntegrationTest { .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 class NBEventRestRepositoryIT extends AbstractControllerIntegrationTest { .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 class NBEventRestRepositoryIT extends AbstractControllerIntegrationTest { 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 class NBEventRestRepositoryIT extends AbstractControllerIntegrationTest { 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 class NBEventRestRepositoryIT extends AbstractControllerIntegrationTest { 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 class NBEventRestRepositoryIT extends AbstractControllerIntegrationTest { .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 class NBEventRestRepositoryIT extends AbstractControllerIntegrationTest { + "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 class NBEventRestRepositoryIT extends AbstractControllerIntegrationTest { + "\"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 class NBEventRestRepositoryIT extends AbstractControllerIntegrationTest { 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 class NBEventRestRepositoryIT extends AbstractControllerIntegrationTest { .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 class NBEventRestRepositoryIT extends AbstractControllerIntegrationTest { 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 class NBEventRestRepositoryIT extends AbstractControllerIntegrationTest { 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 class NBEventRestRepositoryIT extends AbstractControllerIntegrationTest { .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 class NBEventRestRepositoryIT extends AbstractControllerIntegrationTest { 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 class NBEventRestRepositoryIT extends AbstractControllerIntegrationTest { 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 class NBEventRestRepositoryIT extends AbstractControllerIntegrationTest { 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 class NBEventRestRepositoryIT extends AbstractControllerIntegrationTest { 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 class NBEventRestRepositoryIT extends AbstractControllerIntegrationTest { .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 2af54b74da..1fdcd5e0df 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.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.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 class NBSourceRestRepositoryIT extends AbstractControllerIntegrationTest context.restoreAuthSystemState(); - configurationService.setProperty("nbevent.sources", + configurationService.setProperty("qaevent.sources", new String[] { "openaire", "test-source", "test-source-2" }); } @@ -80,13 +80,13 @@ public class NBSourceRestRepositoryIT extends AbstractControllerIntegrationTest 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 class NBSourceRestRepositoryIT extends AbstractControllerIntegrationTest 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 class NBSourceRestRepositoryIT extends AbstractControllerIntegrationTest context.restoreAuthSystemState(); - getClient().perform(get("/api/integration/nbsources")) + getClient().perform(get("/api/integration/qasources")) .andExpect(status().isUnauthorized()); } @@ -138,22 +138,22 @@ public class NBSourceRestRepositoryIT extends AbstractControllerIntegrationTest 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 class NBSourceRestRepositoryIT extends AbstractControllerIntegrationTest 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 class NBSourceRestRepositoryIT extends AbstractControllerIntegrationTest 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 7fe9dbc8b2..d510d713a0 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. 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.junit.Test; 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 class NBTopicRestRepositoryIT extends AbstractControllerIntegrationTest { .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 class NBTopicRestRepositoryIT extends AbstractControllerIntegrationTest { .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 class NBTopicRestRepositoryIT extends AbstractControllerIntegrationTest { .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 class NBTopicRestRepositoryIT extends AbstractControllerIntegrationTest { .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 class NBTopicRestRepositoryIT extends AbstractControllerIntegrationTest { .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 class NBTopicRestRepositoryIT extends AbstractControllerIntegrationTest { .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 class NBTopicRestRepositoryIT extends AbstractControllerIntegrationTest { .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 f0a0a12194..ab696aa338 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.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.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 class NBEventMatcher { ); } - 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 class NBEventMatcher { 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 35031202f0..0340315600 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 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.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 7ad6972b1e..26ef1e92e9 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 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.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 class NBTopicMatcher { } - 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 c5bb2207bd..b2ff6b0581 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 24d9f04d90..f8b1c2d56d 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 d2b5dd12f9..79870b1979 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 90acf62b18..8818b8a9cc 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 e71072314c..93f6b93faf 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 0565e56df4..76f17a3ef3 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 e27fb19a68..6884b949a6 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.factory.EPersonServiceFactory; 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 class AbstractIntegrationTestWithDatabase extends AbstractDSpaceIntegrati .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 aced81cbdf..7cc1e8cb45 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 class TestDSpaceRunnableHandler extends CommandLineDSpaceRunnableHandler 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 0000000000..1e53bff4f9 --- /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 0000000000..0392efd3b1 --- /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 0000000000..e69de29bb2 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 0000000000..7d8dbd37f1 --- /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 0000000000..d281db450f --- /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 5f58f014ab..5f6ff02acc 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.apache.commons.lang3.StringUtils; 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 8818b8a9cc..c292cb4beb 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 024/412] [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 443e6f8d39..3d460015f7 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 class MockQAEventService extends QAEventServiceImpl implements Initializi /** 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 025/412] [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 a7519aa7b0..dc3ca914e6 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 interface QAEventService { /** * 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 d536c8a1e9..bbb6990bb6 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 @@ public class QAEventServiceImpl implements QAEventService { // 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 026/412] [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 dc3ca914e6..ea923251b8 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 431c236de7..bd9b31e14c 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 class QAEventRestRepository extends DSpaceRestRepository 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 b68a8987c7929cd18ee438a731c0982a0735e4f0 Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Tue, 12 Jul 2022 12:43:17 +0200 Subject: [PATCH 027/412] [CST-5249] Fixed LGTM alerts --- .../app/suggestion/OAIREPublicationLoaderRunnable.java | 6 +++--- .../java/org/dspace/app/suggestion/SuggestionSource.java | 2 +- .../org/dspace/app/suggestion/oaire/EvidenceScorer.java | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/suggestion/OAIREPublicationLoaderRunnable.java b/dspace-api/src/main/java/org/dspace/app/suggestion/OAIREPublicationLoaderRunnable.java index 1349a1a40c..fd79ea5e9f 100644 --- a/dspace-api/src/main/java/org/dspace/app/suggestion/OAIREPublicationLoaderRunnable.java +++ b/dspace-api/src/main/java/org/dspace/app/suggestion/OAIREPublicationLoaderRunnable.java @@ -85,9 +85,9 @@ public class OAIREPublicationLoaderRunnable * researcher, the method returns an empty array list. If uuid is null, all * research will be return. * - * @param profile uuid of the researcher. If null, all researcher will be - * returned. - * @return the researcher with specified UUID or all researchers + * @param profileUUID uuid of the researcher. If null, all researcher will be + * returned. + * @return the researcher with specified UUID or all researchers */ @SuppressWarnings("rawtypes") private List getResearchers(String profileUUID) { diff --git a/dspace-api/src/main/java/org/dspace/app/suggestion/SuggestionSource.java b/dspace-api/src/main/java/org/dspace/app/suggestion/SuggestionSource.java index b9df687dec..ea9698d45e 100644 --- a/dspace-api/src/main/java/org/dspace/app/suggestion/SuggestionSource.java +++ b/dspace-api/src/main/java/org/dspace/app/suggestion/SuggestionSource.java @@ -24,7 +24,7 @@ public class SuggestionSource { /** * Summarize the available suggestions from a source. * - * @param the name must be not null + * @param name the name must be not null */ public SuggestionSource(String name) { super(); diff --git a/dspace-api/src/main/java/org/dspace/app/suggestion/oaire/EvidenceScorer.java b/dspace-api/src/main/java/org/dspace/app/suggestion/oaire/EvidenceScorer.java index 9df7621b46..6e00fdef0d 100644 --- a/dspace-api/src/main/java/org/dspace/app/suggestion/oaire/EvidenceScorer.java +++ b/dspace-api/src/main/java/org/dspace/app/suggestion/oaire/EvidenceScorer.java @@ -32,6 +32,6 @@ public interface EvidenceScorer { * @return the generated suggestion evidence or null if the record should be * discarded */ - public SuggestionEvidence computeEvidence(Item researcher, ExternalDataObject importRecords); + public SuggestionEvidence computeEvidence(Item researcher, ExternalDataObject importRecord); } From dd46e54b811f31e29b9634e0b1507ae074f896c3 Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Tue, 12 Jul 2022 12:53:01 +0200 Subject: [PATCH 028/412] [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 cf37158fdc..dc67ae8d71 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 ecc6cef116..d0d21d2e4f 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 @@ import org.dspace.utils.DSpace; * @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 fc32aa6818..1bf5ea85d8 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 @@ import org.springframework.beans.factory.annotation.Autowired; * @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 OpenaireEventsScriptConfiguration /** * 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 55b21da4f2..512c34aa26 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 0392efd3b1..258ede4ef0 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.Before; 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 132dd347c8..de4072b44f 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 b86427bb8b..39f55fc2f7 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 029/412] [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 | 370 ++++++++++++++---- .../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, 671 insertions(+), 115 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 d0d21d2e4f..e45dc3e159 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.content.QAEvent; 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 @@ import org.dspace.utils.DSpace; * * * @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 class OpenaireEventsImport @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 { - 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)); - } + 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(); } } /** - * 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 + * Read the OPENAIRE events from the given file location and try to store them. */ - private QAEvent[] readOpenaireQAEventsFromJsonFile() { - try { + private void importOpenaireEventsFromFile() throws Exception { - ObjectMapper jsonMapper = new JsonMapper(); - jsonMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - return jsonMapper.readValue(getQAEventsFileInputStream(), QAEvent[].class); + InputStream eventsFileInputStream = getQAEventsFileInputStream(); + List qaEvents = readOpenaireQAEventsFromJson(eventsFileInputStream); + + handler.logInfo("Found " + qaEvents.size() + " events in the given file"); + + storeOpenaireQAEvents(qaEvents); + + } + + /** + * Import the OPENAIRE events from the Broker using the subscription related to + * the given email and try to store them. + */ + private void importOpenaireEventsFromBroker() { + + 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 @@ public class OpenaireEventsImport + "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 @@ public class OpenaireEventsImport } + /** + * 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 1bf5ea85d8..1a6f94f6a5 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 class OpenaireEventsImportScriptConfiguration { 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 0000000000..fe3b7130b5 --- /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 0000000000..dd93972814 --- /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 258ede4ef0..22da785d31 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.containsInAnyOrder; 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.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.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 class OpenaireEventsImportIT extends AbstractIntegrationTestWithDatabase .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 class OpenaireEventsImportIT extends AbstractIntegrationTestWithDatabase assertThat(handler.getErrorMessages(), empty()); assertThat(handler.getWarningMessages(), empty()); - 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, 2L))); - 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))); + 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 class OpenaireEventsImportIT extends AbstractIntegrationTestWithDatabase 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 class OpenaireEventsImportIT extends AbstractIntegrationTestWithDatabase 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 class OpenaireEventsImportIT extends AbstractIntegrationTestWithDatabase 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 class OpenaireEventsImportIT extends AbstractIntegrationTestWithDatabase 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")); - 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.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")); + + 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))); + + 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 0000000000..0637a088a0 --- /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 d281db450f..3caa72cf35 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 d30c5181f5..d9a6fba962 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 c292cb4beb..25bb282672 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 030/412] [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 dc67ae8d71..d09fcf004d 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 031/412] [CST-5247] Updated qaevent core configuration --- dspace/solr/qaevent/conf/schema.xml | 231 +++++++++++------------- dspace/solr/qaevent/conf/solrconfig.xml | 6 + 2 files changed, 113 insertions(+), 124 deletions(-) diff --git a/dspace/solr/qaevent/conf/schema.xml b/dspace/solr/qaevent/conf/schema.xml index 68eb79afd0..d523e99c9f 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,32 +85,52 @@ ignoreCase="true" words="stopwords.txt" /> - - + + - + + - + - + - - + + @@ -116,83 +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 76f17a3ef3..2a5f1ef4e9 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 5c9aaa0a8c38410c25c49c15c7a4c25f4bc8440a Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Wed, 9 Nov 2022 15:14:08 +0100 Subject: [PATCH 032/412] [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 8716d07937..538d99b3ce 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.RequestParam; 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 e02755d2ec..7a12ade61d 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 @@ import org.dspace.app.rest.RestResourceController; 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 15c8096e02..a1480f409c 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 34d5655eb7..05820b9194 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 173f233de0..dc021f2904 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 class QAEventRestRepositoryIT extends AbstractControllerIntegrationTest { .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 class QAEventRestRepositoryIT extends AbstractControllerIntegrationTest { 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 class QAEventRestRepositoryIT extends AbstractControllerIntegrationTest { .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 class QAEventRestRepositoryIT extends AbstractControllerIntegrationTest { .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 class QAEventRestRepositoryIT extends AbstractControllerIntegrationTest { 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 class QAEventRestRepositoryIT extends AbstractControllerIntegrationTest { 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 class QAEventRestRepositoryIT extends AbstractControllerIntegrationTest { .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 class QAEventRestRepositoryIT extends AbstractControllerIntegrationTest { .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 class QAEventRestRepositoryIT extends AbstractControllerIntegrationTest { .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 class QAEventRestRepositoryIT extends AbstractControllerIntegrationTest { 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 class QAEventRestRepositoryIT extends AbstractControllerIntegrationTest { .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 class QAEventRestRepositoryIT extends AbstractControllerIntegrationTest { 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 class QAEventRestRepositoryIT extends AbstractControllerIntegrationTest { 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 class QAEventRestRepositoryIT extends AbstractControllerIntegrationTest { 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 class QAEventRestRepositoryIT extends AbstractControllerIntegrationTest { .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 class QAEventRestRepositoryIT extends AbstractControllerIntegrationTest { 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 class QAEventRestRepositoryIT extends AbstractControllerIntegrationTest { 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 class QAEventRestRepositoryIT extends AbstractControllerIntegrationTest { 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 class QAEventRestRepositoryIT extends AbstractControllerIntegrationTest { 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 class QAEventRestRepositoryIT extends AbstractControllerIntegrationTest { .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 class QAEventRestRepositoryIT extends AbstractControllerIntegrationTest { 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 1fdcd5e0df..ac0ccc4cce 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 class QASourceRestRepositoryIT extends AbstractControllerIntegrationTest 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 class QASourceRestRepositoryIT extends AbstractControllerIntegrationTest 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 class QASourceRestRepositoryIT extends AbstractControllerIntegrationTest context.restoreAuthSystemState(); - getClient().perform(get("/api/integration/qasources")) + getClient().perform(get("/api/integration/qualityassurancesources")) .andExpect(status().isUnauthorized()); } @@ -138,22 +138,22 @@ public class QASourceRestRepositoryIT extends AbstractControllerIntegrationTest 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 class QASourceRestRepositoryIT extends AbstractControllerIntegrationTest 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 class QASourceRestRepositoryIT extends AbstractControllerIntegrationTest 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 d510d713a0..55bfd0feca 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 class QATopicRestRepositoryIT extends AbstractControllerIntegrationTest { .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 class QATopicRestRepositoryIT extends AbstractControllerIntegrationTest { @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 class QATopicRestRepositoryIT extends AbstractControllerIntegrationTest { .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 class QATopicRestRepositoryIT extends AbstractControllerIntegrationTest { .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 class QATopicRestRepositoryIT extends AbstractControllerIntegrationTest { 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 class QATopicRestRepositoryIT extends AbstractControllerIntegrationTest { .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 class QATopicRestRepositoryIT extends AbstractControllerIntegrationTest { .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 class QATopicRestRepositoryIT extends AbstractControllerIntegrationTest { 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 class QATopicRestRepositoryIT extends AbstractControllerIntegrationTest { .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 afd2ff0358..68359023e3 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 class QAEventMatcher { 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 0340315600..c0466ee408 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 @@ public class 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 class QASourceMatcher { 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 26ef1e92e9..6428a97125 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 @@ public class 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 class QATopicMatcher { 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 d6446b15ae75028fa84a7153e96f1a4f01f6f08b Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Thu, 9 Mar 2023 18:27:32 +0100 Subject: [PATCH 033/412] [DSC-963] Create new project to run dspace with embedded tomcat --- dspace-server-webapp/pom.xml | 54 --- .../{Application.java => WebApplication.java} | 32 +- .../java/org/dspace/app/TestApplication.java | 16 + .../AbstractControllerIntegrationTest.java | 4 +- .../AbstractWebClientIntegrationTest.java | 4 +- dspace-webapp-boot/pom.xml | 135 +++++++ .../main/java/org/dspace/app/Application.java | 45 +++ .../src/main/resources/application.properties | 1 + .../src/main/resources/static}/index.html | 0 .../resources/static}/js/hal/http/client.js | 0 .../static}/js/vendor/CustomPostForm.js | 0 .../src/main/resources/static}/login.html | 0 .../src/main/resources/static}/styles.css | 0 .../app/rest/example/ExampleController.java | 0 .../app/rest/example/ExampleControllerIT.java | 0 dspace/config/log4j2-console.xml | 2 +- dspace/modules/server/pom.xml | 348 ------------------ .../modules/server/src/main/webapp/.gitignore | 0 pom.xml | 36 ++ 19 files changed, 242 insertions(+), 435 deletions(-) rename dspace-server-webapp/src/main/java/org/dspace/app/rest/{Application.java => WebApplication.java} (87%) create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/TestApplication.java create mode 100644 dspace-webapp-boot/pom.xml create mode 100644 dspace-webapp-boot/src/main/java/org/dspace/app/Application.java rename {dspace-server-webapp => dspace-webapp-boot}/src/main/resources/application.properties (99%) rename {dspace-server-webapp/src/main/webapp => dspace-webapp-boot/src/main/resources/static}/index.html (100%) rename {dspace-server-webapp/src/main/webapp => dspace-webapp-boot/src/main/resources/static}/js/hal/http/client.js (100%) rename {dspace-server-webapp/src/main/webapp => dspace-webapp-boot/src/main/resources/static}/js/vendor/CustomPostForm.js (100%) rename {dspace-server-webapp/src/main/webapp => dspace-webapp-boot/src/main/resources/static}/login.html (100%) rename {dspace-server-webapp/src/main/webapp => dspace-webapp-boot/src/main/resources/static}/styles.css (100%) rename {dspace/modules/server => dspace-webapp-boot}/src/test/java/org/dspace/app/rest/example/ExampleController.java (100%) rename {dspace/modules/server => dspace-webapp-boot}/src/test/java/org/dspace/app/rest/example/ExampleControllerIT.java (100%) delete mode 100644 dspace/modules/server/pom.xml delete mode 100644 dspace/modules/server/src/main/webapp/.gitignore diff --git a/dspace-server-webapp/pom.xml b/dspace-server-webapp/pom.xml index 99aa88bebf..78334431df 100644 --- a/dspace-server-webapp/pom.xml +++ b/dspace-server-webapp/pom.xml @@ -2,7 +2,6 @@ 4.0.0 org.dspace dspace-server-webapp - war DSpace Server Webapp DSpace Server Webapp (Spring Boot) @@ -25,55 +24,10 @@ @ - - org.dspace.app.rest.Application - - org.apache.maven.plugins - maven-war-plugin - - true - - true - - - - prepare-package - - - - - org.apache.maven.plugins - maven-jar-plugin - - - - - test-jar - - - - - - - com.mycila - license-maven-plugin - - - **/src/test/resources/** - **/src/test/data/** - - src/main/webapp/index.html - src/main/webapp/login.html - src/main/webapp/styles.css - src/main/webapp/js/hal/** - src/main/webapp/js/vendor/** - - - + + org.dspace + dspace-parent + cris-2022.03.01-SNAPSHOT + .. + + + + + ${basedir}/.. + + @ + + + + + + org.dspace.modules + additions + + + org.dspace + dspace-server-webapp + + + org.apache.solr + solr-solrj + + + + + org.dspace + dspace-api + test-jar + test + + + org.dspace + dspace-server-webapp + test-jar + test + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework.security + spring-security-test + ${spring-security.version} + test + + + com.jayway.jsonpath + json-path-assert + ${json-path.version} + test + + + junit + junit + test + + + com.h2database + h2 + test + + + org.mockito + mockito-inline + test + + + + + org.apache.solr + solr-core + ${solr.client.version} + test + + + + org.apache.commons + commons-text + + + + + org.apache.lucene + lucene-analyzers-icu + test + + + + + + + + + com.mycila + license-maven-plugin + + + **/src/test/resources/** + **/src/test/data/** + + src/main/resources/static/index.html + src/main/resources/static/login.html + src/main/resources/static/styles.css + src/main/resources/static/js/hal/** + src/main/resources/static/js/vendor/** + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/dspace-webapp-boot/src/main/java/org/dspace/app/Application.java b/dspace-webapp-boot/src/main/java/org/dspace/app/Application.java new file mode 100644 index 0000000000..90039887f8 --- /dev/null +++ b/dspace-webapp-boot/src/main/java/org/dspace/app/Application.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; + +import org.dspace.app.rest.WebApplication; +import org.dspace.app.rest.utils.DSpaceConfigurationInitializer; +import org.dspace.app.rest.utils.DSpaceKernelInitializer; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; + +@SpringBootApplication(scanBasePackageClasses = WebApplication.class) +public class Application extends SpringBootServletInitializer { + + public static void main(String[] args) { + new SpringApplicationBuilder(Application.class) + .initializers(new DSpaceKernelInitializer(), new DSpaceConfigurationInitializer()) + .run(args); + } + + /** + * Override the default SpringBootServletInitializer.configure() method, + * passing it this Application class. + *

+ * This is necessary to allow us to build a deployable WAR, rather than + * always relying on embedded Tomcat. + *

+ * See: http://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#howto-create-a-deployable-war-file + * + * @param application + * @return + */ + @Override + protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { + // Pass this Application class, and our initializers for DSpace Kernel and Configuration + // NOTE: Kernel must be initialized before Configuration + return application.sources(Application.class) + .initializers(new DSpaceKernelInitializer(), new DSpaceConfigurationInitializer()); + } +} diff --git a/dspace-server-webapp/src/main/resources/application.properties b/dspace-webapp-boot/src/main/resources/application.properties similarity index 99% rename from dspace-server-webapp/src/main/resources/application.properties rename to dspace-webapp-boot/src/main/resources/application.properties index f6fba076c0..bfedd6379e 100644 --- a/dspace-server-webapp/src/main/resources/application.properties +++ b/dspace-webapp-boot/src/main/resources/application.properties @@ -27,6 +27,7 @@ # http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html # +server.servlet.context-path=/server ######################## # DSpace Settings # diff --git a/dspace-server-webapp/src/main/webapp/index.html b/dspace-webapp-boot/src/main/resources/static/index.html similarity index 100% rename from dspace-server-webapp/src/main/webapp/index.html rename to dspace-webapp-boot/src/main/resources/static/index.html diff --git a/dspace-server-webapp/src/main/webapp/js/hal/http/client.js b/dspace-webapp-boot/src/main/resources/static/js/hal/http/client.js similarity index 100% rename from dspace-server-webapp/src/main/webapp/js/hal/http/client.js rename to dspace-webapp-boot/src/main/resources/static/js/hal/http/client.js diff --git a/dspace-server-webapp/src/main/webapp/js/vendor/CustomPostForm.js b/dspace-webapp-boot/src/main/resources/static/js/vendor/CustomPostForm.js similarity index 100% rename from dspace-server-webapp/src/main/webapp/js/vendor/CustomPostForm.js rename to dspace-webapp-boot/src/main/resources/static/js/vendor/CustomPostForm.js diff --git a/dspace-server-webapp/src/main/webapp/login.html b/dspace-webapp-boot/src/main/resources/static/login.html similarity index 100% rename from dspace-server-webapp/src/main/webapp/login.html rename to dspace-webapp-boot/src/main/resources/static/login.html diff --git a/dspace-server-webapp/src/main/webapp/styles.css b/dspace-webapp-boot/src/main/resources/static/styles.css similarity index 100% rename from dspace-server-webapp/src/main/webapp/styles.css rename to dspace-webapp-boot/src/main/resources/static/styles.css diff --git a/dspace/modules/server/src/test/java/org/dspace/app/rest/example/ExampleController.java b/dspace-webapp-boot/src/test/java/org/dspace/app/rest/example/ExampleController.java similarity index 100% rename from dspace/modules/server/src/test/java/org/dspace/app/rest/example/ExampleController.java rename to dspace-webapp-boot/src/test/java/org/dspace/app/rest/example/ExampleController.java diff --git a/dspace/modules/server/src/test/java/org/dspace/app/rest/example/ExampleControllerIT.java b/dspace-webapp-boot/src/test/java/org/dspace/app/rest/example/ExampleControllerIT.java similarity index 100% rename from dspace/modules/server/src/test/java/org/dspace/app/rest/example/ExampleControllerIT.java rename to dspace-webapp-boot/src/test/java/org/dspace/app/rest/example/ExampleControllerIT.java diff --git a/dspace/config/log4j2-console.xml b/dspace/config/log4j2-console.xml index 3d51b12336..a0322abf19 100644 --- a/dspace/config/log4j2-console.xml +++ b/dspace/config/log4j2-console.xml @@ -25,7 +25,7 @@ For command line / Ant scripts, we are only concerned about significant warnings/errors. For the full detail, change this to INFO and re-run Ant. --> - + diff --git a/dspace/modules/server/pom.xml b/dspace/modules/server/pom.xml deleted file mode 100644 index 9b696fa0cb..0000000000 --- a/dspace/modules/server/pom.xml +++ /dev/null @@ -1,348 +0,0 @@ - - 4.0.0 - org.dspace.modules - server - war - DSpace Server Webapp:: Local Customizations - Overlay customizations. -This is probably a temporary solution to the build problems. We like to investigate about -the possibility to remove the overlays enable a more flexible extension mechanism. -The use of web-fragment and spring mvc technology allow us to add request handlers -just adding new jar in the classloader - - - modules - org.dspace - 7.6-SNAPSHOT - .. - - - - - ${basedir}/../../.. - - - - - - - org.apache.maven.plugins - maven-dependency-plugin - - - unpack - prepare-package - - unpack-dependencies - - - org.dspace.modules - additions - - ${project.build.directory}/additions - META-INF/** - - - - - - org.apache.maven.plugins - maven-war-plugin - - false - - true - - - - ${project.build.directory}/additions - WEB-INF/classes - - - - - - prepare-package - - - - - - org.codehaus.gmaven - groovy-maven-plugin - - - setproperty - initialize - - execute - - - - project.properties['agnostic.build.dir'] = project.build.directory.replace(File.separator, '/'); - log.info("Initializing Maven property 'agnostic.build.dir' to: {}", project.properties['agnostic.build.dir']); - - - - - - - - - - - - unit-test-environment - - false - - skipUnitTests - false - - - - - - - maven-dependency-plugin - - ${project.build.directory}/testing - - - org.dspace - dspace-parent - ${project.version} - zip - testEnvironment - - - - - - setupUnitTestEnvironment - generate-test-resources - - unpack - - - - - - - - maven-surefire-plugin - - - - - - ${agnostic.build.dir}/testing/dspace/ - - true - ${agnostic.build.dir}/testing/dspace/solr/ - - - - - - - - - org.dspace - dspace-server-webapp - test-jar - test - - - - - - - integration-test-environment - - false - - skipIntegrationTests - false - - - - - - - maven-dependency-plugin - - ${project.build.directory}/testing - - - org.dspace - dspace-parent - ${project.version} - zip - testEnvironment - - - - - - setupIntegrationTestEnvironment - pre-integration-test - - unpack - - - - - - - - maven-failsafe-plugin - - - - - ${agnostic.build.dir}/testing/dspace/ - - true - ${agnostic.build.dir}/testing/dspace/solr/ - - - - - - - - - org.dspace - dspace-server-webapp - test-jar - test - - - - - - oracle-support - - - db.name - oracle - - - - - com.oracle - ojdbc6 - - - - - - - - - org.dspace.modules - additions - - - org.dspace - dspace-server-webapp - classes - - - org.dspace - dspace-server-webapp - war - - - org.apache.solr - solr-solrj - ${solr.client.version} - - - - - org.dspace - dspace-api - test-jar - test - - - org.dspace - dspace-server-webapp - test-jar - test - - - org.springframework.boot - spring-boot-starter-test - test - - - org.springframework.security - spring-security-test - ${spring-security.version} - test - - - com.jayway.jsonpath - json-path-assert - ${json-path.version} - test - - - junit - junit - test - - - com.h2database - h2 - test - - - org.mockito - mockito-inline - test - - - - - org.apache.solr - solr-core - ${solr.client.version} - test - - - org.apache.lucene - lucene-analyzers-icu - test - - - - - diff --git a/dspace/modules/server/src/main/webapp/.gitignore b/dspace/modules/server/src/main/webapp/.gitignore deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/pom.xml b/pom.xml index 3d9e6851d1..357f3f447d 100644 --- a/pom.xml +++ b/pom.xml @@ -793,6 +793,21 @@ + + + dspace-webapp-boot + + + dspace-webapp-boot/pom.xml + + + + dspace-webapp-boot + + + @@ -1068,6 +1083,7 @@ org.dspace dspace-server-webapp +<<<<<<< HEAD test-jar 7.6-SNAPSHOT test @@ -1094,6 +1110,26 @@ dspace-server-webapp 7.6-SNAPSHOT war +======= + cris-2022.03.01-SNAPSHOT + + + org.dspace + dspace-server-webapp + test-jar + cris-2022.03.01-SNAPSHOT + test + + + org.dspace + dspace-rdf + cris-2022.03.01-SNAPSHOT + + + org.dspace + dspace-iiif + cris-2022.03.01-SNAPSHOT +>>>>>>> 0dad4dd713... [DSC-963] Create new project to run dspace with embedded tomcat From 0400b38121d07a2b1f7e10f5f106c6cfcb313904 Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Fri, 10 Mar 2023 15:32:59 +0100 Subject: [PATCH 034/412] [DSC-963] Fixed test configuration --- .../src/test/resources/application.properties | 64 +++++++++++++++++++ .../src/main/resources/application.properties | 6 +- 2 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 dspace-server-webapp/src/test/resources/application.properties diff --git a/dspace-server-webapp/src/test/resources/application.properties b/dspace-server-webapp/src/test/resources/application.properties new file mode 100644 index 0000000000..9b408d9612 --- /dev/null +++ b/dspace-server-webapp/src/test/resources/application.properties @@ -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/ +# + +# Spring Boot's Test application.properties + +######################## +# Jackson serialization settings +# +spring.jackson.serialization.fail-on-empty-beans=false + +######################## +# Internationalization +# +# Base Path for our messages file (i18n) +spring.messages.basename=i18n/messages +spring.messages.encoding=UTF-8 + +######################## +# URI Encoding and Decoding +# +# +# Charset of HTTP requests and responses. Added to the "Content-Type" header if not set explicitly. +server.servlet.encoding.charset=UTF-8 +# Force the encoding to the configured charset on HTTP requests and responses. +server.servlet.encoding.force=true + +########################### +# Server Properties +# +# Error handling settings +server.error.include-stacktrace = never + +# When to include the error message in error responses (introduced in Spring 2.3.x) +server.error.include-message = always + +# Spring Boot proxy configuration (can be overridden in local.cfg). +server.forward-headers-strategy=FRAMEWORK + +###################### +# Cache Properties +# Added for IIIF cache support. +# Path to configuration file. +spring.cache.jcache.config=classpath:iiif/cache/ehcache.xml + +###################### +# Spring Boot Autoconfigure +# +# TODO: At some point we may want to investigate whether we can re-enable these and remove the custom DSpace init code +spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration, \ + org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration, \ + org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration, \ + org.springframework.boot.autoconfigure.solr.SolrAutoConfiguration, \ + org.springframework.boot.autoconfigure.velocity.VelocityAutoConfiguration + +spring.main.allow-bean-definition-overriding = true + +######################### +# Spring Boot Logging levels +logging.config = classpath:log4j2-test.xml diff --git a/dspace-webapp-boot/src/main/resources/application.properties b/dspace-webapp-boot/src/main/resources/application.properties index bfedd6379e..8233298ef0 100644 --- a/dspace-webapp-boot/src/main/resources/application.properties +++ b/dspace-webapp-boot/src/main/resources/application.properties @@ -27,7 +27,6 @@ # http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html # -server.servlet.context-path=/server ######################## # DSpace Settings # @@ -39,6 +38,11 @@ server.servlet.context-path=/server # interact with or read its configuration from dspace.cfg. dspace.dir=${dspace.dir} +######################## +# Servlet context path configuration for spring boot application running with embedded tomcat +# +server.servlet.context-path=/server + ######################## # Jackson serialization settings # From dcde7dbeeacfc508d922d4a26e7ef1dde2b3bb53 Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Fri, 10 Mar 2023 15:37:57 +0100 Subject: [PATCH 035/412] [DSC-963] Fixed dspace pom --- dspace/pom.xml | 6 +++++- pom.xml | 5 +++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/dspace/pom.xml b/dspace/pom.xml index 7916648e47..8ab9599998 100644 --- a/dspace/pom.xml +++ b/dspace/pom.xml @@ -217,7 +217,11 @@ org.dspace dspace-server-webapp - war + compile + + + org.dspace + dspace-webapp-boot compile diff --git a/pom.xml b/pom.xml index 357f3f447d..10af1fff3e 100644 --- a/pom.xml +++ b/pom.xml @@ -1120,6 +1120,11 @@ cris-2022.03.01-SNAPSHOT test + + org.dspace + dspace-webapp-boot + cris-2022.03.01-SNAPSHOT + org.dspace dspace-rdf From 5d592df6bb6a4ec2fc9a220709c108e733e6d437 Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Fri, 10 Mar 2023 16:14:50 +0100 Subject: [PATCH 036/412] [DSC-963] Improved tests configuration --- .../src/main/resources/application.properties | 0 .../src/test/resources/application.properties | 64 ------------------- 2 files changed, 64 deletions(-) rename {dspace-webapp-boot => dspace-server-webapp}/src/main/resources/application.properties (100%) delete mode 100644 dspace-server-webapp/src/test/resources/application.properties diff --git a/dspace-webapp-boot/src/main/resources/application.properties b/dspace-server-webapp/src/main/resources/application.properties similarity index 100% rename from dspace-webapp-boot/src/main/resources/application.properties rename to dspace-server-webapp/src/main/resources/application.properties diff --git a/dspace-server-webapp/src/test/resources/application.properties b/dspace-server-webapp/src/test/resources/application.properties deleted file mode 100644 index 9b408d9612..0000000000 --- a/dspace-server-webapp/src/test/resources/application.properties +++ /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/ -# - -# Spring Boot's Test application.properties - -######################## -# Jackson serialization settings -# -spring.jackson.serialization.fail-on-empty-beans=false - -######################## -# Internationalization -# -# Base Path for our messages file (i18n) -spring.messages.basename=i18n/messages -spring.messages.encoding=UTF-8 - -######################## -# URI Encoding and Decoding -# -# -# Charset of HTTP requests and responses. Added to the "Content-Type" header if not set explicitly. -server.servlet.encoding.charset=UTF-8 -# Force the encoding to the configured charset on HTTP requests and responses. -server.servlet.encoding.force=true - -########################### -# Server Properties -# -# Error handling settings -server.error.include-stacktrace = never - -# When to include the error message in error responses (introduced in Spring 2.3.x) -server.error.include-message = always - -# Spring Boot proxy configuration (can be overridden in local.cfg). -server.forward-headers-strategy=FRAMEWORK - -###################### -# Cache Properties -# Added for IIIF cache support. -# Path to configuration file. -spring.cache.jcache.config=classpath:iiif/cache/ehcache.xml - -###################### -# Spring Boot Autoconfigure -# -# TODO: At some point we may want to investigate whether we can re-enable these and remove the custom DSpace init code -spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration, \ - org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration, \ - org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration, \ - org.springframework.boot.autoconfigure.solr.SolrAutoConfiguration, \ - org.springframework.boot.autoconfigure.velocity.VelocityAutoConfiguration - -spring.main.allow-bean-definition-overriding = true - -######################### -# Spring Boot Logging levels -logging.config = classpath:log4j2-test.xml From 2819b6f2e42814cb7291d055f492e67e97c6bdee Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Fri, 10 Mar 2023 16:42:00 +0100 Subject: [PATCH 037/412] [DSC-963] Fixed dspace-server-webapp pom --- dspace-server-webapp/pom.xml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/dspace-server-webapp/pom.xml b/dspace-server-webapp/pom.xml index 78334431df..7ccb75244e 100644 --- a/dspace-server-webapp/pom.xml +++ b/dspace-server-webapp/pom.xml @@ -57,6 +57,25 @@ + + org.apache.maven.plugins + maven-jar-plugin + + + + true + true + + + + + + + test-jar + + + + From 1bbd478cf6b3805d988104e57c06cf957017bc19 Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Fri, 10 Mar 2023 17:39:33 +0100 Subject: [PATCH 038/412] [DSC-963] Fixed Sword tests --- .../src/test/java/org/dspace/app/rdf/RdfIT.java | 2 +- .../src/test/java/org/dspace/app/sword/Swordv1IT.java | 2 +- .../src/test/java/org/dspace/app/sword2/Swordv2IT.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rdf/RdfIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rdf/RdfIT.java index 85ab3dcadd..10f06370ad 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rdf/RdfIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rdf/RdfIT.java @@ -47,7 +47,7 @@ import org.springframework.test.context.TestPropertySource; */ // Ensure the RDF endpoint IS ENABLED before any tests run. // This annotation overrides default DSpace config settings loaded into Spring Context -@TestPropertySource(properties = {"rdf.enabled = true"}) +@TestPropertySource(properties = {"rdf.enabled = true", "server.servlet.context-path = /"}) public class RdfIT extends AbstractWebClientIntegrationTest { @Autowired diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/sword/Swordv1IT.java b/dspace-server-webapp/src/test/java/org/dspace/app/sword/Swordv1IT.java index 24244e1773..ffef89316b 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/sword/Swordv1IT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/sword/Swordv1IT.java @@ -34,7 +34,7 @@ import org.springframework.test.context.TestPropertySource; */ // Ensure the SWORD SERVER IS ENABLED before any tests run. // This annotation overrides default DSpace config settings loaded into Spring Context -@TestPropertySource(properties = {"sword-server.enabled = true"}) +@TestPropertySource(properties = { "sword-server.enabled = true", "server.servlet.context-path = /" }) public class Swordv1IT extends AbstractWebClientIntegrationTest { @Autowired diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/sword2/Swordv2IT.java b/dspace-server-webapp/src/test/java/org/dspace/app/sword2/Swordv2IT.java index 95ec762514..f9caeead66 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/sword2/Swordv2IT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/sword2/Swordv2IT.java @@ -34,7 +34,7 @@ import org.springframework.test.context.TestPropertySource; */ // Ensure the SWORDv2 SERVER IS ENABLED before any tests run. // This annotation overrides default DSpace config settings loaded into Spring Context -@TestPropertySource(properties = {"swordv2-server.enabled = true"}) +@TestPropertySource(properties = {"swordv2-server.enabled = true", "server.servlet.context-path = /"}) public class Swordv2IT extends AbstractWebClientIntegrationTest { @Autowired From 944f4a10949df3fd48df2bcc445be91802e93bfb Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Fri, 10 Mar 2023 17:50:08 +0100 Subject: [PATCH 039/412] [DSC-963] Fixed dspace pom --- dspace-webapp-boot/pom.xml | 2 +- dspace/pom.xml | 1 + pom.xml | 33 ++------------------------------- 3 files changed, 4 insertions(+), 32 deletions(-) diff --git a/dspace-webapp-boot/pom.xml b/dspace-webapp-boot/pom.xml index 1a110886cf..4a14ef03fc 100644 --- a/dspace-webapp-boot/pom.xml +++ b/dspace-webapp-boot/pom.xml @@ -12,7 +12,7 @@ org.dspace dspace-parent - cris-2022.03.01-SNAPSHOT + 7.6-SNAPSHOT .. diff --git a/dspace/pom.xml b/dspace/pom.xml index 8ab9599998..9cfe7da366 100644 --- a/dspace/pom.xml +++ b/dspace/pom.xml @@ -222,6 +222,7 @@ org.dspace dspace-webapp-boot + war compile diff --git a/pom.xml b/pom.xml index 10af1fff3e..2da6e8aa3c 100644 --- a/pom.xml +++ b/pom.xml @@ -1083,7 +1083,6 @@ org.dspace dspace-server-webapp -<<<<<<< HEAD test-jar 7.6-SNAPSHOT test @@ -1102,39 +1101,11 @@ org.dspace dspace-server-webapp 7.6-SNAPSHOT - jar - classes - - - org.dspace - dspace-server-webapp - 7.6-SNAPSHOT - war -======= - cris-2022.03.01-SNAPSHOT - - - org.dspace - dspace-server-webapp - test-jar - cris-2022.03.01-SNAPSHOT - test - org.dspace dspace-webapp-boot - cris-2022.03.01-SNAPSHOT - - - org.dspace - dspace-rdf - cris-2022.03.01-SNAPSHOT - - - org.dspace - dspace-iiif - cris-2022.03.01-SNAPSHOT ->>>>>>> 0dad4dd713... [DSC-963] Create new project to run dspace with embedded tomcat + 7.6-SNAPSHOT + war From 882485b61528a3f240434f0d0eb448c9d75fa04e Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Fri, 10 Mar 2023 19:32:46 +0100 Subject: [PATCH 040/412] [DSC-963] Set default servlet context path on application-test.properties --- .../src/test/java/org/dspace/app/rdf/RdfIT.java | 2 +- .../src/test/java/org/dspace/app/sword/Swordv1IT.java | 2 +- .../src/test/java/org/dspace/app/sword2/Swordv2IT.java | 2 +- .../src/test/resources/application-test.properties | 4 +++- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rdf/RdfIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rdf/RdfIT.java index 10f06370ad..85ab3dcadd 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rdf/RdfIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rdf/RdfIT.java @@ -47,7 +47,7 @@ import org.springframework.test.context.TestPropertySource; */ // Ensure the RDF endpoint IS ENABLED before any tests run. // This annotation overrides default DSpace config settings loaded into Spring Context -@TestPropertySource(properties = {"rdf.enabled = true", "server.servlet.context-path = /"}) +@TestPropertySource(properties = {"rdf.enabled = true"}) public class RdfIT extends AbstractWebClientIntegrationTest { @Autowired diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/sword/Swordv1IT.java b/dspace-server-webapp/src/test/java/org/dspace/app/sword/Swordv1IT.java index ffef89316b..24244e1773 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/sword/Swordv1IT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/sword/Swordv1IT.java @@ -34,7 +34,7 @@ import org.springframework.test.context.TestPropertySource; */ // Ensure the SWORD SERVER IS ENABLED before any tests run. // This annotation overrides default DSpace config settings loaded into Spring Context -@TestPropertySource(properties = { "sword-server.enabled = true", "server.servlet.context-path = /" }) +@TestPropertySource(properties = {"sword-server.enabled = true"}) public class Swordv1IT extends AbstractWebClientIntegrationTest { @Autowired diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/sword2/Swordv2IT.java b/dspace-server-webapp/src/test/java/org/dspace/app/sword2/Swordv2IT.java index f9caeead66..95ec762514 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/sword2/Swordv2IT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/sword2/Swordv2IT.java @@ -34,7 +34,7 @@ import org.springframework.test.context.TestPropertySource; */ // Ensure the SWORDv2 SERVER IS ENABLED before any tests run. // This annotation overrides default DSpace config settings loaded into Spring Context -@TestPropertySource(properties = {"swordv2-server.enabled = true", "server.servlet.context-path = /"}) +@TestPropertySource(properties = {"swordv2-server.enabled = true"}) public class Swordv2IT extends AbstractWebClientIntegrationTest { @Autowired diff --git a/dspace-server-webapp/src/test/resources/application-test.properties b/dspace-server-webapp/src/test/resources/application-test.properties index 9a396cf8e5..e92e1166e3 100644 --- a/dspace-server-webapp/src/test/resources/application-test.properties +++ b/dspace-server-webapp/src/test/resources/application-test.properties @@ -14,4 +14,6 @@ ## Log4j2 configuration for test environment ## This file is found on classpath at src/test/resources/log4j2-test.xml -logging.config = classpath:log4j2-test.xml \ No newline at end of file +logging.config = classpath:log4j2-test.xml + +server.servlet.context-path=/ \ No newline at end of file From c82588ab5430463bd75f804e4c83a797099c0f47 Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Fri, 10 Mar 2023 20:41:07 +0100 Subject: [PATCH 041/412] [DSC-963] Improved TestApplication configuration --- .../test/java/org/dspace/app/{ => rest}/TestApplication.java | 5 ++--- .../app/rest/test/AbstractControllerIntegrationTest.java | 2 +- .../app/rest/test/AbstractWebClientIntegrationTest.java | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) rename dspace-server-webapp/src/test/java/org/dspace/app/{ => rest}/TestApplication.java (70%) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/TestApplication.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/TestApplication.java similarity index 70% rename from dspace-server-webapp/src/test/java/org/dspace/app/TestApplication.java rename to dspace-server-webapp/src/test/java/org/dspace/app/rest/TestApplication.java index 0f80e866ed..e387e3f002 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/TestApplication.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/TestApplication.java @@ -5,12 +5,11 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app; +package org.dspace.app.rest; -import org.dspace.app.rest.WebApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -@SpringBootApplication(scanBasePackageClasses = WebApplication.class) +@SpringBootApplication public class TestApplication { } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/test/AbstractControllerIntegrationTest.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/test/AbstractControllerIntegrationTest.java index 4ec66fb000..a27e0ab75c 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/test/AbstractControllerIntegrationTest.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/test/AbstractControllerIntegrationTest.java @@ -23,7 +23,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.commons.lang3.StringUtils; import org.dspace.AbstractIntegrationTestWithDatabase; -import org.dspace.app.TestApplication; +import org.dspace.app.rest.TestApplication; import org.dspace.app.rest.model.patch.Operation; import org.dspace.app.rest.utils.DSpaceConfigurationInitializer; import org.dspace.app.rest.utils.DSpaceKernelInitializer; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/test/AbstractWebClientIntegrationTest.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/test/AbstractWebClientIntegrationTest.java index be0a27b4eb..7f58a9999d 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/test/AbstractWebClientIntegrationTest.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/test/AbstractWebClientIntegrationTest.java @@ -9,7 +9,7 @@ package org.dspace.app.rest.test; import org.apache.commons.lang3.StringUtils; import org.dspace.AbstractIntegrationTestWithDatabase; -import org.dspace.app.TestApplication; +import org.dspace.app.rest.TestApplication; import org.dspace.app.rest.utils.DSpaceConfigurationInitializer; import org.dspace.app.rest.utils.DSpaceKernelInitializer; import org.junit.runner.RunWith; From aff1de4153994eb69bc151fde159881ba830bde5 Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Mon, 13 Mar 2023 10:33:06 +0100 Subject: [PATCH 042/412] [DSC-963] Added @Order on AdminRestPermissionEvaluatorPlugin --- .../app/rest/security/AdminRestPermissionEvaluatorPlugin.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/AdminRestPermissionEvaluatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/AdminRestPermissionEvaluatorPlugin.java index 0d251f6400..338eed4a73 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/AdminRestPermissionEvaluatorPlugin.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/AdminRestPermissionEvaluatorPlugin.java @@ -20,6 +20,8 @@ import org.dspace.services.model.Request; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; import org.springframework.security.core.Authentication; import org.springframework.stereotype.Component; @@ -29,6 +31,7 @@ import org.springframework.stereotype.Component; * the authenticated EPerson is allowed to perform the requested action. */ @Component +@Order(value = Ordered.HIGHEST_PRECEDENCE) public class AdminRestPermissionEvaluatorPlugin extends RestObjectPermissionEvaluatorPlugin { private static final Logger log = LoggerFactory.getLogger(RestObjectPermissionEvaluatorPlugin.class); From b3a3acf91058f748feb8c1c3fb6d4be8bcb5d5cc Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Mon, 13 Mar 2023 11:46:20 +0100 Subject: [PATCH 043/412] [DSC-963] Minor improvements --- dspace-server-webapp/pom.xml | 31 +++++++------------ .../org/dspace/app/rest/WebApplication.java | 10 ++---- .../app/{rest => }/TestApplication.java | 11 +++++-- .../AbstractControllerIntegrationTest.java | 2 +- .../AbstractWebClientIntegrationTest.java | 2 +- .../main/java/org/dspace/app/Application.java | 13 ++++++++ dspace/config/log4j2-console.xml | 2 +- 7 files changed, 39 insertions(+), 32 deletions(-) rename dspace-server-webapp/src/test/java/org/dspace/app/{rest => }/TestApplication.java (55%) diff --git a/dspace-server-webapp/pom.xml b/dspace-server-webapp/pom.xml index 7ccb75244e..fbba57301f 100644 --- a/dspace-server-webapp/pom.xml +++ b/dspace-server-webapp/pom.xml @@ -28,6 +28,18 @@ + + org.apache.maven.plugins + maven-jar-plugin + + + + + test-jar + + + + - + From abb17db890e6bf6d6088746723d69ae80fd8d3b8 Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Mon, 13 Mar 2023 17:44:07 +0100 Subject: [PATCH 044/412] [DSC-963] Fixed ItemRestRepositoryIT and GenericAuthorizationFeatureIT integration tests --- .../ExternalSourceItemUriListHandler.java | 8 +++++--- .../GenericAuthorizationFeatureIT.java | 18 ++++++++++++------ 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/handler/ExternalSourceItemUriListHandler.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/handler/ExternalSourceItemUriListHandler.java index d619100bf6..201a7ba163 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/handler/ExternalSourceItemUriListHandler.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/handler/ExternalSourceItemUriListHandler.java @@ -30,16 +30,19 @@ import org.springframework.stereotype.Component; @Component public class ExternalSourceItemUriListHandler extends ExternalSourceEntryItemUriListHandler { + private Pattern pattern = Pattern.compile("\\/api\\/core\\/items\\/(.*)"); + @Autowired private ItemService itemService; @Override @SuppressWarnings("rawtypes") public boolean supports(List uriList, String method,Class clazz) { - if (clazz != Item.class) { + if (clazz != Item.class || uriList.size() != 1) { return false; } - return true; + + return pattern.matcher(uriList.get(0)).find(); } @Override @@ -61,7 +64,6 @@ public class ExternalSourceItemUriListHandler extends ExternalSourceEntryItemUri private Item getObjectFromUriList(Context context, List uriList) { Item item = null; String url = uriList.get(0); - Pattern pattern = Pattern.compile("\\/api\\/core\\/items\\/(.*)"); Matcher matcher = pattern.matcher(url); if (!matcher.find()) { throw new DSpaceBadRequestException("The uri: " + url + " doesn't resolve to an item"); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/GenericAuthorizationFeatureIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/GenericAuthorizationFeatureIT.java index 1d3b5b0516..e6ccf5954c 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/GenericAuthorizationFeatureIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/GenericAuthorizationFeatureIT.java @@ -757,7 +757,8 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration // Verify the general admin has this feature on item 1 getClient(adminToken).perform( get("/api/authz/authorizations/search/object?embed=feature&uri=" - + "http://localhost/api/core/items/" + item1.getID())) + + "http://localhost/api/core/items/" + item1.getID()) + .param("size", "1000")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" + feature + "')]").exists()); @@ -765,7 +766,8 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration // Verify community A admin has this feature on item 1 getClient(communityAAdminToken).perform( get("/api/authz/authorizations/search/object?embed=feature&uri=" - + "http://localhost/api/core/items/" + item1.getID())) + + "http://localhost/api/core/items/" + item1.getID()) + .param("size", "1000")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" + feature + "')]").exists()); @@ -773,7 +775,8 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration // Verify collection X admin has this feature on item 1 getClient(collectionXAdminToken).perform( get("/api/authz/authorizations/search/object?embed=feature&uri=" - + "http://localhost/api/core/items/" + item1.getID())) + + "http://localhost/api/core/items/" + item1.getID()) + .param("size", "1000")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" + feature + "')]").exists()); @@ -781,7 +784,8 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration // Verify item 1 admin doesn’t have this feature on item 1 getClient(item1AdminToken).perform( get("/api/authz/authorizations/search/object?embed=feature&uri=" - + "http://localhost/api/core/items/" + item1.getID())) + + "http://localhost/api/core/items/" + item1.getID()) + .param("size", "1000")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" + feature + "')]").doesNotExist()); @@ -789,7 +793,8 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration // Verify community A admin doesn’t have this feature on item 2 getClient(communityAAdminToken).perform( get("/api/authz/authorizations/search/object?embed=feature&uri=" - + "http://localhost/api/core/items/" + item2.getID())) + + "http://localhost/api/core/items/" + item2.getID()) + .param("size", "1000")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" + feature + "')]").doesNotExist()); @@ -808,7 +813,8 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration // verify item 1 write has this feature on item 1 getClient(item1WriterToken).perform( get("/api/authz/authorizations/search/object?embed=feature&uri=" - + "http://localhost/api/core/items/" + item1.getID())) + + "http://localhost/api/core/items/" + item1.getID()) + .param("size", "1000")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='canMove')]") .exists()); From 08c547805e96f24282bbeec62cd579b597569a7d Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Tue, 14 Mar 2023 12:57:50 +0100 Subject: [PATCH 045/412] [DSC-963] Fixed SubmissionCCLicenseUrlRepositoryIT tests --- .../org/dspace/app/rest/link/DSpaceResourceHalLinkFactory.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/DSpaceResourceHalLinkFactory.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/DSpaceResourceHalLinkFactory.java index c306691eb3..30404e030a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/DSpaceResourceHalLinkFactory.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/DSpaceResourceHalLinkFactory.java @@ -21,6 +21,8 @@ import org.dspace.app.rest.model.RestModel; import org.dspace.app.rest.model.hateoas.DSpaceResource; import org.dspace.app.rest.utils.Utils; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; import org.springframework.data.domain.Pageable; import org.springframework.hateoas.IanaLinkRelations; import org.springframework.hateoas.Link; @@ -33,6 +35,7 @@ import org.springframework.stereotype.Component; * @author Tom Desair (tom dot desair at atmire dot com) */ @Component +@Order(Ordered.HIGHEST_PRECEDENCE) public class DSpaceResourceHalLinkFactory extends HalLinkFactory { @Autowired From 12cb9a82df62636ac8114ddf59ed5b27bb57554d Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Tue, 14 Mar 2023 17:12:09 +0100 Subject: [PATCH 046/412] [DSC-963] Refactoring to maintain server module --- dspace-server-webapp/pom.xml | 17 + .../src/main/resources/static/index.html | 0 .../resources/static/js/hal/http/client.js | 0 .../static/js/vendor/CustomPostForm.js | 0 .../src/main/resources/static/login.html | 0 .../src/main/resources/static/styles.css | 0 dspace/modules/pom.xml | 11 + .../modules/server-boot}/pom.xml | 30 +- .../org/dspace/app/ServerBootApplication.java | 36 ++ dspace/modules/server/pom.xml | 349 ++++++++++++++++++ .../org/dspace/app/ServerApplication.java | 10 +- .../modules/server/src/main/webapp/.gitignore | 0 .../app/rest/example/ExampleController.java | 0 .../app/rest/example/ExampleControllerIT.java | 0 dspace/pom.xml | 6 - pom.xml | 20 - 16 files changed, 420 insertions(+), 59 deletions(-) rename {dspace-webapp-boot => dspace-server-webapp}/src/main/resources/static/index.html (100%) rename {dspace-webapp-boot => dspace-server-webapp}/src/main/resources/static/js/hal/http/client.js (100%) rename {dspace-webapp-boot => dspace-server-webapp}/src/main/resources/static/js/vendor/CustomPostForm.js (100%) rename {dspace-webapp-boot => dspace-server-webapp}/src/main/resources/static/login.html (100%) rename {dspace-webapp-boot => dspace-server-webapp}/src/main/resources/static/styles.css (100%) rename {dspace-webapp-boot => dspace/modules/server-boot}/pom.xml (73%) create mode 100644 dspace/modules/server-boot/src/main/java/org/dspace/app/ServerBootApplication.java create mode 100644 dspace/modules/server/pom.xml rename dspace-webapp-boot/src/main/java/org/dspace/app/Application.java => dspace/modules/server/src/main/java/org/dspace/app/ServerApplication.java (85%) create mode 100644 dspace/modules/server/src/main/webapp/.gitignore rename {dspace-webapp-boot => dspace/modules/server}/src/test/java/org/dspace/app/rest/example/ExampleController.java (100%) rename {dspace-webapp-boot => dspace/modules/server}/src/test/java/org/dspace/app/rest/example/ExampleControllerIT.java (100%) diff --git a/dspace-server-webapp/pom.xml b/dspace-server-webapp/pom.xml index fbba57301f..57f3399f98 100644 --- a/dspace-server-webapp/pom.xml +++ b/dspace-server-webapp/pom.xml @@ -40,6 +40,23 @@ + + + com.mycila + license-maven-plugin + + + **/src/test/resources/** + **/src/test/data/** + + src/main/resources/static/index.html + src/main/resources/static/login.html + src/main/resources/static/styles.css + src/main/resources/static/js/hal/** + src/main/resources/static/js/vendor/** + + + + modules org.dspace - dspace-parent 7.6-SNAPSHOT .. - ${basedir}/.. - - @ + ${basedir}/../../.. @@ -105,26 +102,9 @@ - + - - - com.mycila - license-maven-plugin - - - **/src/test/resources/** - **/src/test/data/** - - src/main/resources/static/index.html - src/main/resources/static/login.html - src/main/resources/static/styles.css - src/main/resources/static/js/hal/** - src/main/resources/static/js/vendor/** - - - org.springframework.boot spring-boot-maven-plugin diff --git a/dspace/modules/server-boot/src/main/java/org/dspace/app/ServerBootApplication.java b/dspace/modules/server-boot/src/main/java/org/dspace/app/ServerBootApplication.java new file mode 100644 index 0000000000..f46532ff14 --- /dev/null +++ b/dspace/modules/server-boot/src/main/java/org/dspace/app/ServerBootApplication.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; + +import org.dspace.app.rest.WebApplication; +import org.dspace.app.rest.utils.DSpaceConfigurationInitializer; +import org.dspace.app.rest.utils.DSpaceKernelInitializer; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.builder.SpringApplicationBuilder; + +/** + * Define the Spring Boot Application settings itself to be runned using an + * embedded application server. + * + * @author Luca Giamminonni (luca.giamminonni at 4science.it) + * + */ +@SpringBootApplication(scanBasePackageClasses = WebApplication.class) +public class ServerBootApplication { + + private ServerBootApplication() { + + } + + public static void main(String[] args) { + new SpringApplicationBuilder(ServerBootApplication.class) + .initializers(new DSpaceKernelInitializer(), new DSpaceConfigurationInitializer()) + .run(args); + } + +} diff --git a/dspace/modules/server/pom.xml b/dspace/modules/server/pom.xml new file mode 100644 index 0000000000..65849295e8 --- /dev/null +++ b/dspace/modules/server/pom.xml @@ -0,0 +1,349 @@ + + 4.0.0 + org.dspace.modules + server + war + DSpace Server Webapp:: Local Customizations + + modules + org.dspace + cris-2022.03.01-SNAPSHOT + .. + + + + + ${basedir}/../../.. + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + + + unpack + prepare-package + + unpack-dependencies + + + org.dspace.modules + additions + + ${project.build.directory}/additions + META-INF/** + + + + + + org.apache.maven.plugins + maven-war-plugin + + false + + true + + + + ${project.build.directory}/additions + WEB-INF/classes + + + + + + prepare-package + + + + + + org.codehaus.gmaven + groovy-maven-plugin + + + setproperty + initialize + + execute + + + + project.properties['agnostic.build.dir'] = project.build.directory.replace(File.separator, '/'); + log.info("Initializing Maven property 'agnostic.build.dir' to: {}", project.properties['agnostic.build.dir']); + + + + + + + + + + + + unit-test-environment + + false + + skipUnitTests + false + + + + + + + maven-dependency-plugin + + ${project.build.directory}/testing + + + org.dspace + dspace-parent + ${project.version} + zip + testEnvironment + + + + + + setupUnitTestEnvironment + generate-test-resources + + unpack + + + + + + + + maven-surefire-plugin + + + + + + ${agnostic.build.dir}/testing/dspace + + true + ${agnostic.build.dir}/testing/dspace/solr/ + + + + + + + + + org.dspace + dspace-server-webapp + test-jar + test + + + + + + + integration-test-environment + + false + + skipIntegrationTests + false + + + + + + + maven-dependency-plugin + + ${project.build.directory}/testing + + + org.dspace + dspace-parent + ${project.version} + zip + testEnvironment + + + + + + setupIntegrationTestEnvironment + pre-integration-test + + unpack + + + + + + + + maven-failsafe-plugin + + + + + ${agnostic.build.dir}/testing/dspace + + true + ${agnostic.build.dir}/testing/dspace/solr/ + + + + + + + + + org.dspace + dspace-server-webapp + test-jar + test + + + + + + oracle-support + + + db.name + oracle + + + + + com.oracle + ojdbc6 + + + + + + + + + org.dspace.modules + additions + + + org.dspace + dspace-server-webapp + + + org.springframework.boot + spring-boot-starter-tomcat + provided + ${spring-boot.version} + + + org.apache.solr + solr-solrj + ${solr.client.version} + + + + + org.dspace + dspace-api + test-jar + test + + + org.dspace + dspace-server-webapp + test-jar + test + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework.security + spring-security-test + ${spring-security.version} + test + + + com.jayway.jsonpath + json-path-assert + ${json-path.version} + test + + + junit + junit + test + + + com.h2database + h2 + test + + + org.mockito + mockito-inline + test + + + + + org.apache.solr + solr-core + ${solr.client.version} + test + + + + org.apache.commons + commons-text + + + + + org.apache.lucene + lucene-analyzers-icu + test + + + + + diff --git a/dspace-webapp-boot/src/main/java/org/dspace/app/Application.java b/dspace/modules/server/src/main/java/org/dspace/app/ServerApplication.java similarity index 85% rename from dspace-webapp-boot/src/main/java/org/dspace/app/Application.java rename to dspace/modules/server/src/main/java/org/dspace/app/ServerApplication.java index dc84b29a56..34acc778b7 100644 --- a/dspace-webapp-boot/src/main/java/org/dspace/app/Application.java +++ b/dspace/modules/server/src/main/java/org/dspace/app/ServerApplication.java @@ -28,13 +28,7 @@ import org.springframework.boot.web.servlet.support.SpringBootServletInitializer * */ @SpringBootApplication(scanBasePackageClasses = WebApplication.class) -public class Application extends SpringBootServletInitializer { - - public static void main(String[] args) { - new SpringApplicationBuilder(Application.class) - .initializers(new DSpaceKernelInitializer(), new DSpaceConfigurationInitializer()) - .run(args); - } +public class ServerApplication extends SpringBootServletInitializer { /** * Override the default SpringBootServletInitializer.configure() method, @@ -52,7 +46,7 @@ public class Application extends SpringBootServletInitializer { protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { // Pass this Application class, and our initializers for DSpace Kernel and Configuration // NOTE: Kernel must be initialized before Configuration - return application.sources(Application.class) + return application.sources(ServerApplication.class) .initializers(new DSpaceKernelInitializer(), new DSpaceConfigurationInitializer()); } } diff --git a/dspace/modules/server/src/main/webapp/.gitignore b/dspace/modules/server/src/main/webapp/.gitignore new file mode 100644 index 0000000000..e69de29bb2 diff --git a/dspace-webapp-boot/src/test/java/org/dspace/app/rest/example/ExampleController.java b/dspace/modules/server/src/test/java/org/dspace/app/rest/example/ExampleController.java similarity index 100% rename from dspace-webapp-boot/src/test/java/org/dspace/app/rest/example/ExampleController.java rename to dspace/modules/server/src/test/java/org/dspace/app/rest/example/ExampleController.java diff --git a/dspace-webapp-boot/src/test/java/org/dspace/app/rest/example/ExampleControllerIT.java b/dspace/modules/server/src/test/java/org/dspace/app/rest/example/ExampleControllerIT.java similarity index 100% rename from dspace-webapp-boot/src/test/java/org/dspace/app/rest/example/ExampleControllerIT.java rename to dspace/modules/server/src/test/java/org/dspace/app/rest/example/ExampleControllerIT.java diff --git a/dspace/pom.xml b/dspace/pom.xml index 9cfe7da366..b3ebbc4fae 100644 --- a/dspace/pom.xml +++ b/dspace/pom.xml @@ -219,12 +219,6 @@ dspace-server-webapp compile - - org.dspace - dspace-webapp-boot - war - compile - org.dspace dspace-sword diff --git a/pom.xml b/pom.xml index 2da6e8aa3c..6e65afac32 100644 --- a/pom.xml +++ b/pom.xml @@ -793,21 +793,6 @@ - - - dspace-webapp-boot - - - dspace-webapp-boot/pom.xml - - - - dspace-webapp-boot - - - @@ -1101,11 +1086,6 @@ org.dspace dspace-server-webapp 7.6-SNAPSHOT - - org.dspace - dspace-webapp-boot - 7.6-SNAPSHOT - war From 4b72466d74982d5f9da43d03dc76fb16f0d90d3e Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Tue, 14 Mar 2023 18:36:50 +0100 Subject: [PATCH 047/412] [DSC-963] Configured spring boot maven plugin --- dspace/modules/server-boot/pom.xml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/dspace/modules/server-boot/pom.xml b/dspace/modules/server-boot/pom.xml index 138329845a..fbfab5f93b 100644 --- a/dspace/modules/server-boot/pom.xml +++ b/dspace/modules/server-boot/pom.xml @@ -108,6 +108,14 @@ org.springframework.boot spring-boot-maven-plugin + ${spring-boot.version} + + + + repackage + + + From 313a1d8d68f6721ffe4b7a13eaa220df5b9f0a21 Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Wed, 15 Mar 2023 17:58:16 +0100 Subject: [PATCH 048/412] [DSC-963] Suppress checkstyle warning --- .../src/main/java/org/dspace/app/ServerBootApplication.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/dspace/modules/server-boot/src/main/java/org/dspace/app/ServerBootApplication.java b/dspace/modules/server-boot/src/main/java/org/dspace/app/ServerBootApplication.java index f46532ff14..5efa79a02a 100644 --- a/dspace/modules/server-boot/src/main/java/org/dspace/app/ServerBootApplication.java +++ b/dspace/modules/server-boot/src/main/java/org/dspace/app/ServerBootApplication.java @@ -20,13 +20,10 @@ import org.springframework.boot.builder.SpringApplicationBuilder; * @author Luca Giamminonni (luca.giamminonni at 4science.it) * */ +@SuppressWarnings({ "checkstyle:hideutilityclassconstructor" }) @SpringBootApplication(scanBasePackageClasses = WebApplication.class) public class ServerBootApplication { - private ServerBootApplication() { - - } - public static void main(String[] args) { new SpringApplicationBuilder(ServerBootApplication.class) .initializers(new DSpaceKernelInitializer(), new DSpaceConfigurationInitializer()) From c6e00a1a302844bd52ffdaf88f3c03089eafa4b5 Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Thu, 23 Mar 2023 15:06:32 +0100 Subject: [PATCH 049/412] [DSC-963] Fixed webjars classpath --- .../src/main/java/org/dspace/app/rest/WebApplication.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/WebApplication.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/WebApplication.java index 4f3002dd49..c9d859a440 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/WebApplication.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/WebApplication.java @@ -208,7 +208,7 @@ public class WebApplication { // Make all other Webjars available off the /webjars path registry .addResourceHandler("/webjars/**") - .addResourceLocations("/webjars/"); + .addResourceLocations("/webjars/", "classpath:/META-INF/resources/webjars/"); } @Override From 3767ae8ab18fa621a1caa4461d7fd94a4d0cd799 Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Thu, 20 Apr 2023 11:47:04 +0200 Subject: [PATCH 050/412] [DSC-963] Fixed porting on main --- .../GenericAuthorizationFeatureIT.java | 18 ++++++------------ dspace/modules/server/pom.xml | 13 +++---------- 2 files changed, 9 insertions(+), 22 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/GenericAuthorizationFeatureIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/GenericAuthorizationFeatureIT.java index e6ccf5954c..1d3b5b0516 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/GenericAuthorizationFeatureIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/GenericAuthorizationFeatureIT.java @@ -757,8 +757,7 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration // Verify the general admin has this feature on item 1 getClient(adminToken).perform( get("/api/authz/authorizations/search/object?embed=feature&uri=" - + "http://localhost/api/core/items/" + item1.getID()) - .param("size", "1000")) + + "http://localhost/api/core/items/" + item1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" + feature + "')]").exists()); @@ -766,8 +765,7 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration // Verify community A admin has this feature on item 1 getClient(communityAAdminToken).perform( get("/api/authz/authorizations/search/object?embed=feature&uri=" - + "http://localhost/api/core/items/" + item1.getID()) - .param("size", "1000")) + + "http://localhost/api/core/items/" + item1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" + feature + "')]").exists()); @@ -775,8 +773,7 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration // Verify collection X admin has this feature on item 1 getClient(collectionXAdminToken).perform( get("/api/authz/authorizations/search/object?embed=feature&uri=" - + "http://localhost/api/core/items/" + item1.getID()) - .param("size", "1000")) + + "http://localhost/api/core/items/" + item1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" + feature + "')]").exists()); @@ -784,8 +781,7 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration // Verify item 1 admin doesn’t have this feature on item 1 getClient(item1AdminToken).perform( get("/api/authz/authorizations/search/object?embed=feature&uri=" - + "http://localhost/api/core/items/" + item1.getID()) - .param("size", "1000")) + + "http://localhost/api/core/items/" + item1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" + feature + "')]").doesNotExist()); @@ -793,8 +789,7 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration // Verify community A admin doesn’t have this feature on item 2 getClient(communityAAdminToken).perform( get("/api/authz/authorizations/search/object?embed=feature&uri=" - + "http://localhost/api/core/items/" + item2.getID()) - .param("size", "1000")) + + "http://localhost/api/core/items/" + item2.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" + feature + "')]").doesNotExist()); @@ -813,8 +808,7 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration // verify item 1 write has this feature on item 1 getClient(item1WriterToken).perform( get("/api/authz/authorizations/search/object?embed=feature&uri=" - + "http://localhost/api/core/items/" + item1.getID()) - .param("size", "1000")) + + "http://localhost/api/core/items/" + item1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='canMove')]") .exists()); diff --git a/dspace/modules/server/pom.xml b/dspace/modules/server/pom.xml index 65849295e8..152c905f45 100644 --- a/dspace/modules/server/pom.xml +++ b/dspace/modules/server/pom.xml @@ -7,7 +7,7 @@ modules org.dspace - cris-2022.03.01-SNAPSHOT + 7.6-SNAPSHOT .. @@ -149,7 +149,7 @@ - ${agnostic.build.dir}/testing/dspace + ${agnostic.build.dir}/testing/dspace/ true ${agnostic.build.dir}/testing/dspace/solr/ @@ -218,7 +218,7 @@ - ${agnostic.build.dir}/testing/dspace + ${agnostic.build.dir}/testing/dspace/ true ${agnostic.build.dir}/testing/dspace/solr/ @@ -330,13 +330,6 @@ solr-core ${solr.client.version} test - - - - org.apache.commons - commons-text - - org.apache.lucene From fe621b37b0e340419631c95704c2a99ee8c7e4be Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Thu, 20 Apr 2023 12:06:34 +0200 Subject: [PATCH 051/412] [DSC-963] Fixed GenericAuthorizationFeatureIT tests --- .../GenericAuthorizationFeatureIT.java | 322 ++++++++++-------- 1 file changed, 182 insertions(+), 140 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/GenericAuthorizationFeatureIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/GenericAuthorizationFeatureIT.java index 1d3b5b0516..d59ef00018 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/GenericAuthorizationFeatureIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/GenericAuthorizationFeatureIT.java @@ -209,7 +209,7 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration String siteId = ContentServiceFactory.getInstance().getSiteService().findSite(context).getID().toString(); // Verify the general admin has this feature on the site - getClient(adminToken).perform(get("/api/authz/authorizations/search/object?embed=feature&uri=" + getClient(adminToken).perform(get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/sites/" + siteId)) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" @@ -217,14 +217,14 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration // Verify community A admin doesn’t have this feature on the site getClient(communityAAdminToken).perform( - get("/api/authz/authorizations/search/object?embed=feature&uri=" + get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/sites/" + siteId)) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" + feature + "')]").doesNotExist()); // Verify the general admin has this feature on community A - getClient(adminToken).perform(get("/api/authz/authorizations/search/object?embed=feature&uri=" + getClient(adminToken).perform(get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/communities/" + communityA.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" @@ -232,7 +232,7 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration // Verify community A admin has this feature on community A getClient(communityAAdminToken).perform( - get("/api/authz/authorizations/search/object?embed=feature&uri=" + get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/communities/" + communityA.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" @@ -240,7 +240,7 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration // Verify community A admin has this feature on community AA getClient(communityAAdminToken).perform( - get("/api/authz/authorizations/search/object?embed=feature&uri=" + get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/communities/" + communityAA.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" @@ -248,7 +248,7 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration // Verify collection X admin doesn’t have this feature on community A getClient(collectionXAdminToken).perform( - get("/api/authz/authorizations/search/object?embed=feature&uri=" + get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/communities/" + communityA.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" @@ -256,7 +256,7 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration // Verify community A admin doesn’t have this feature on community B getClient(communityAAdminToken).perform( - get("/api/authz/authorizations/search/object?embed=feature&uri=" + get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/communities/" + communityB.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" @@ -264,7 +264,7 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration // Verify the general admin has this feature on collection X getClient(adminToken).perform( - get("/api/authz/authorizations/search/object?embed=feature&uri=" + get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/collections/" + collectionX.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" @@ -272,7 +272,7 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration // Verify community A admin has this feature on collection X getClient(communityAAdminToken).perform( - get("/api/authz/authorizations/search/object?embed=feature&uri=" + get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/collections/" + collectionX.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" @@ -280,7 +280,7 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration // Verify collection X admin has this feature on collection X getClient(collectionXAdminToken).perform( - get("/api/authz/authorizations/search/object?embed=feature&uri=" + get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/collections/" + collectionX.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" @@ -288,7 +288,7 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration // Verify item 1 admin doesn’t have this feature on collection X getClient(item1AdminToken).perform( - get("/api/authz/authorizations/search/object?embed=feature&uri=" + get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/collections/" + collectionX.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" @@ -296,7 +296,7 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration // Verify collection X admin doesn’t have this feature on collection Y getClient(collectionXAdminToken).perform( - get("/api/authz/authorizations/search/object?embed=feature&uri=" + get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/collections/" + collectionY.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" @@ -304,7 +304,7 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration // Verify the general admin has this feature on item 1 getClient(adminToken).perform( - get("/api/authz/authorizations/search/object?embed=feature&uri=" + get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/items/" + item1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" @@ -312,7 +312,7 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration // Verify community A admin has this feature on item 1 getClient(communityAAdminToken).perform( - get("/api/authz/authorizations/search/object?embed=feature&uri=" + get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/items/" + item1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" @@ -320,7 +320,7 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration // Verify collection X admin has this feature on item 1 getClient(collectionXAdminToken).perform( - get("/api/authz/authorizations/search/object?embed=feature&uri=" + get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/items/" + item1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" @@ -328,7 +328,7 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration // Verify item 1 admin has this feature on item 1 getClient(item1AdminToken).perform( - get("/api/authz/authorizations/search/object?embed=feature&uri=" + get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/items/" + item1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" @@ -336,7 +336,7 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration // Verify item 1 admin doesn’t have this feature on item 2 getClient(item1AdminToken).perform( - get("/api/authz/authorizations/search/object?embed=feature&uri=" + get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/items/" + item2.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" @@ -344,7 +344,7 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration // Verify the general admin has this feature on the bundle in item 1 getClient(adminToken).perform( - get("/api/authz/authorizations/search/object?embed=feature&uri=" + get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/bundles/" + bundle1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" @@ -352,7 +352,7 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration // Verify community A admin has this feature on the bundle in item 1 getClient(communityAAdminToken).perform( - get("/api/authz/authorizations/search/object?embed=feature&uri=" + get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/bundles/" + bundle1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" @@ -360,7 +360,7 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration // Verify collection X admin has this feature on the bundle in item 1 getClient(collectionXAdminToken).perform( - get("/api/authz/authorizations/search/object?embed=feature&uri=" + get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/bundles/" + bundle1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" @@ -368,7 +368,7 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration // Verify item 1 admin has this feature on the bundle in item 1 getClient(item1AdminToken).perform( - get("/api/authz/authorizations/search/object?embed=feature&uri=" + get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/bundles/" + bundle1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" @@ -376,7 +376,7 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration // Verify item 1 admin doesn’t have this feature on the bundle in item 2 getClient(item1AdminToken).perform( - get("/api/authz/authorizations/search/object?embed=feature&uri=" + get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/bundles/" + bundle2.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" @@ -384,7 +384,7 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration // Verify the general admin has this feature on the bitstream in item 1 getClient(adminToken).perform( - get("/api/authz/authorizations/search/object?embed=feature&uri=" + get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/bitstreams/" + bitstream1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" @@ -392,7 +392,7 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration // Verify community A admin has this feature on the bitstream in item 1 getClient(communityAAdminToken).perform( - get("/api/authz/authorizations/search/object?embed=feature&uri=" + get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/bitstreams/" + bitstream1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" @@ -400,7 +400,7 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration // Verify collection X admin has this feature on the bitstream in item 1 getClient(collectionXAdminToken).perform( - get("/api/authz/authorizations/search/object?embed=feature&uri=" + get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/bitstreams/" + bitstream1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" @@ -408,7 +408,7 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration // Verify item 1 admin has this feature on the bitstream in item 1 getClient(item1AdminToken).perform( - get("/api/authz/authorizations/search/object?embed=feature&uri=" + get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/bitstreams/" + bitstream1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" @@ -416,7 +416,7 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration // Verify item 1 admin doesn’t have this feature on the bitstream in item 2 getClient(item1AdminToken).perform( - get("/api/authz/authorizations/search/object?embed=feature&uri=" + get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/bitstreams/" + bitstream2.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" @@ -431,7 +431,7 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration // Verify the general admin has this feature on item 1 getClient(adminToken).perform( - get("/api/authz/authorizations/search/object?embed=feature&uri=" + get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/items/" + item1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" @@ -439,7 +439,7 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration // Verify community A admin has this feature on item 1 getClient(communityAAdminToken).perform( - get("/api/authz/authorizations/search/object?embed=feature&uri=" + get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/items/" + item1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" @@ -447,7 +447,7 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration // Verify collection X admin has this feature on item 1 getClient(collectionXAdminToken).perform( - get("/api/authz/authorizations/search/object?embed=feature&uri=" + get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/items/" + item1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" @@ -455,7 +455,7 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration // Verify item 1 admin has this feature on item 1 getClient(item1AdminToken).perform( - get("/api/authz/authorizations/search/object?embed=feature&uri=" + get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/items/" + item1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" @@ -463,7 +463,7 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration // Verify community A admin doesn’t have this feature on item 2 getClient(communityAAdminToken).perform( - get("/api/authz/authorizations/search/object?embed=feature&uri=" + get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/items/" + item2.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" @@ -480,14 +480,14 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration // (or doesn’t have access otherwise) if (hasDSOAccess) { getClient(communityAWriterToken).perform( - get("/api/authz/authorizations/search/object?embed=feature&uri=" + get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/communities/" + communityA.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" + feature + "')]").exists()); } else { getClient(communityAWriterToken).perform( - get("/api/authz/authorizations/search/object?embed=feature&uri=" + get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/communities/" + communityA.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" @@ -496,7 +496,7 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration // Verify community A write doesn’t have this feature on community AA getClient(communityAWriterToken).perform( - get("/api/authz/authorizations/search/object?embed=feature&uri=" + get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/communities/" + communityAA.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" @@ -504,7 +504,7 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration // Verify community A write doesn’t have this feature on collection X getClient(communityAWriterToken).perform( - get("/api/authz/authorizations/search/object?embed=feature&uri=" + get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/collections/" + collectionX.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" @@ -512,7 +512,7 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration // Verify community A write doesn’t have this feature on item 1 getClient(communityAWriterToken).perform( - get("/api/authz/authorizations/search/object?embed=feature&uri=" + get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/items/" + item1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" @@ -520,7 +520,7 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration // Verify community A write doesn’t have this feature on the bundle in item 1 getClient(communityAWriterToken).perform( - get("/api/authz/authorizations/search/object?embed=feature&uri=" + get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/bundles/" + bundle1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" @@ -528,7 +528,7 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration // Verify community A write doesn’t have this feature on the bitstream in item 1 getClient(communityAWriterToken).perform( - get("/api/authz/authorizations/search/object?embed=feature&uri=" + get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/items/" + item1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" @@ -536,7 +536,7 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration // Verify collection X write doesn’t have this feature on community A getClient(collectionXWriterToken).perform( - get("/api/authz/authorizations/search/object?embed=feature&uri=" + get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/communities/" + communityA.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" @@ -544,7 +544,7 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration // Verify collection X write doesn’t have this feature on community AA getClient(collectionXWriterToken).perform( - get("/api/authz/authorizations/search/object?embed=feature&uri=" + get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/communities/" + communityAA.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" @@ -554,14 +554,14 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration // (or doesn’t have access otherwise) if (hasDSOAccess) { getClient(collectionXWriterToken).perform( - get("/api/authz/authorizations/search/object?embed=feature&uri=" + get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/collections/" + collectionX.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" + feature + "')]").exists()); } else { getClient(collectionXWriterToken).perform( - get("/api/authz/authorizations/search/object?embed=feature&uri=" + get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/collections/" + collectionX.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" @@ -570,7 +570,7 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration // Verify collection X write doesn’t have this feature on item 1 getClient(collectionXWriterToken).perform( - get("/api/authz/authorizations/search/object?embed=feature&uri=" + get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/items/" + item1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" @@ -578,7 +578,7 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration // Verify collection X write doesn’t have this feature on the bundle in item 1 getClient(collectionXWriterToken).perform( - get("/api/authz/authorizations/search/object?embed=feature&uri=" + get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/bundles/" + bundle1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" @@ -586,7 +586,7 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration // Verify collection X write doesn’t have this feature on the bitstream in item 1 getClient(collectionXWriterToken).perform( - get("/api/authz/authorizations/search/object?embed=feature&uri=" + get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/bitstreams/" + bitstream1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" @@ -594,7 +594,7 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration // Verify item 1 write doesn’t have this feature on community A getClient(item1WriterToken).perform( - get("/api/authz/authorizations/search/object?embed=feature&uri=" + get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/communities/" + communityA.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" @@ -602,7 +602,7 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration // Verify item 1 write doesn’t have this feature on community AA getClient(item1WriterToken).perform( - get("/api/authz/authorizations/search/object?embed=feature&uri=" + get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/communities/" + communityAA.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" @@ -610,7 +610,7 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration // Verify item 1 write doesn’t have this feature on collection X getClient(item1WriterToken).perform( - get("/api/authz/authorizations/search/object?embed=feature&uri=" + get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/collections/" + collectionX.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" @@ -620,14 +620,14 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration // (or doesn’t have access otherwise) if (hasDSOAccess) { getClient(item1WriterToken).perform( - get("/api/authz/authorizations/search/object?embed=feature&uri=" + get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/items/" + item1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" + feature + "')]").exists()); } else { getClient(item1WriterToken).perform( - get("/api/authz/authorizations/search/object?embed=feature&uri=" + get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/items/" + item1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" @@ -636,7 +636,7 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration // Verify item 1 write doesn’t have this feature on the bundle in item 1 getClient(item1WriterToken).perform( - get("/api/authz/authorizations/search/object?embed=feature&uri=" + get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/bundles/" + bundle1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" @@ -644,7 +644,7 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration // Verify item 1 write doesn’t have this feature on the bitstream in item 1 getClient(item1WriterToken).perform( - get("/api/authz/authorizations/search/object?embed=feature&uri=" + get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/bitstreams/" + bitstream1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" @@ -652,7 +652,7 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration // Verify community A write doesn’t have this feature on community B getClient(communityAWriterToken).perform( - get("/api/authz/authorizations/search/object?embed=feature&uri=" + get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/communities/" + communityB.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" @@ -660,7 +660,7 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration // Verify collection X write doesn’t have this feature on collection Y getClient(collectionXWriterToken).perform( - get("/api/authz/authorizations/search/object?embed=feature&uri=" + get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/collections/" + collectionY.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" @@ -668,7 +668,7 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration // Verify item 1 write doesn’t have this feature on item 2 getClient(item1WriterToken).perform( - get("/api/authz/authorizations/search/object?embed=feature&uri=" + get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/items/" + item2.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" @@ -682,7 +682,7 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration // Verify community A write doesn’t have this feature on item 1 getClient(communityAWriterToken).perform( - get("/api/authz/authorizations/search/object?embed=feature&uri=" + get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/items/" + item1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" @@ -690,7 +690,7 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration // Verify collection X write doesn’t have this feature on item 1 getClient(collectionXWriterToken).perform( - get("/api/authz/authorizations/search/object?embed=feature&uri=" + get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/items/" + item1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" @@ -700,14 +700,14 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration // (or doesn’t have access otherwise) if (hasDSOAccess) { getClient(item1WriterToken).perform( - get("/api/authz/authorizations/search/object?embed=feature&uri=" + get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/items/" + item1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" + feature + "')]").exists()); } else { getClient(item1WriterToken).perform( - get("/api/authz/authorizations/search/object?embed=feature&uri=" + get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/items/" + item1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" @@ -716,7 +716,7 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration // Verify item 1 write doesn’t have this feature on item 2 getClient(item1WriterToken).perform( - get("/api/authz/authorizations/search/object?embed=feature&uri=" + get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/items/" + item2.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" @@ -756,7 +756,7 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration // Verify the general admin has this feature on item 1 getClient(adminToken).perform( - get("/api/authz/authorizations/search/object?embed=feature&uri=" + get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/items/" + item1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" @@ -764,7 +764,7 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration // Verify community A admin has this feature on item 1 getClient(communityAAdminToken).perform( - get("/api/authz/authorizations/search/object?embed=feature&uri=" + get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/items/" + item1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" @@ -772,7 +772,7 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration // Verify collection X admin has this feature on item 1 getClient(collectionXAdminToken).perform( - get("/api/authz/authorizations/search/object?embed=feature&uri=" + get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/items/" + item1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" @@ -780,7 +780,7 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration // Verify item 1 admin doesn’t have this feature on item 1 getClient(item1AdminToken).perform( - get("/api/authz/authorizations/search/object?embed=feature&uri=" + get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/items/" + item1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" @@ -788,7 +788,7 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration // Verify community A admin doesn’t have this feature on item 2 getClient(communityAAdminToken).perform( - get("/api/authz/authorizations/search/object?embed=feature&uri=" + get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/items/" + item2.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" @@ -807,7 +807,7 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration // verify item 1 write has this feature on item 1 getClient(item1WriterToken).perform( - get("/api/authz/authorizations/search/object?embed=feature&uri=" + get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/items/" + item1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='canMove')]") @@ -830,7 +830,7 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration String item1WriterToken = getAuthToken(item1Writer.getEmail(), password); // verify item 1 write has this feature on item 1 getClient(item1WriterToken).perform( - get("/api/authz/authorizations/search/object?embed=feature&uri=" + get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/items/" + item1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='canMove')]") @@ -867,28 +867,30 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration final String feature = "canDelete"; // Verify the general admin doesn’t have this feature on the site - getClient(adminToken).perform(get("/api/authz/authorizations/search/object?embed=feature&uri=" + getClient(adminToken).perform(get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/sites/" + siteId)) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" + feature + "')]").doesNotExist()); // Verify the general admin has this feature on community A - getClient(adminToken).perform(get("/api/authz/authorizations/search/object?embed=feature&uri=" + getClient(adminToken).perform(get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/communities/" + communityA.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" + feature + "')]").exists()); // Verify community A admin has this feature on community A - getClient(communityAAdminToken).perform(get("/api/authz/authorizations/search/object?embed=feature&uri=" + getClient(communityAAdminToken) + .perform(get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/communities/" + communityA.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" + feature + "')]").exists()); // Verify community A admin has this feature on community AA - getClient(communityAAdminToken).perform(get("/api/authz/authorizations/search/object?embed=feature&uri=" + getClient(communityAAdminToken) + .perform(get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/communities/" + communityAA.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" @@ -908,161 +910,173 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration .build(); context.restoreAuthSystemState(); String communityAAAdminToken = getAuthToken(communityAAAdmin.getEmail(), password); - getClient(communityAAAdminToken).perform(get("/api/authz/authorizations/search/object?embed=feature&uri=" + getClient(communityAAAdminToken) + .perform(get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/communities/" + communityAA.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" + feature + "')]").doesNotExist()); // Verify collection X admin doesn’t have this feature on community A - getClient(collectionXAdminToken).perform(get("/api/authz/authorizations/search/object?embed=feature&uri=" + getClient(collectionXAdminToken) + .perform(get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/communities/" + communityA.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" + feature + "')]").doesNotExist()); // Verify community A admin doesn’t have this feature on community B - getClient(communityAAdminToken).perform(get("/api/authz/authorizations/search/object?embed=feature&uri=" + getClient(communityAAdminToken) + .perform(get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/communities/" + communityB.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" + feature + "')]").doesNotExist()); // Verify the general admin has this feature on collection X - getClient(adminToken).perform(get("/api/authz/authorizations/search/object?embed=feature&uri=" + getClient(adminToken).perform(get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/collections/" + collectionX.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" + feature + "')]").exists()); // Verify community A admin has this feature on collection X - getClient(communityAAdminToken).perform(get("/api/authz/authorizations/search/object?embed=feature&uri=" + getClient(communityAAdminToken) + .perform(get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/collections/" + collectionX.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" + feature + "')]").exists()); // Verify collection X admin doesn’t have this feature on collection X - getClient(collectionXAdminToken).perform(get("/api/authz/authorizations/search/object?embed=feature&uri=" + getClient(collectionXAdminToken) + .perform(get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/collections/" + collectionX.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" + feature + "')]").doesNotExist()); // Verify item 1 admin doesn’t have this feature on collection X - getClient(item1AdminToken).perform(get("/api/authz/authorizations/search/object?embed=feature&uri=" + getClient(item1AdminToken).perform(get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/collections/" + collectionX.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" + feature + "')]").doesNotExist()); // Verify collection X admin doesn’t have this feature on collection Y - getClient(collectionXAdminToken).perform(get("/api/authz/authorizations/search/object?embed=feature&uri=" + getClient(collectionXAdminToken) + .perform(get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/collections/" + collectionY.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" + feature + "')]").doesNotExist()); // Verify the general admin has this feature on item 1 - getClient(adminToken).perform(get("/api/authz/authorizations/search/object?embed=feature&uri=" + getClient(adminToken).perform(get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/items/" + item1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" + feature + "')]").exists()); // Verify community A admin has this feature on item 1 - getClient(communityAAdminToken).perform(get("/api/authz/authorizations/search/object?embed=feature&uri=" + getClient(communityAAdminToken) + .perform(get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/items/" + item1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" + feature + "')]").exists()); // Verify collection X admin has this feature on item 1 - getClient(collectionXAdminToken).perform(get("/api/authz/authorizations/search/object?embed=feature&uri=" + getClient(collectionXAdminToken) + .perform(get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/items/" + item1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" + feature + "')]").exists()); // Verify item 1 admin doesn’t have this feature on item 1 - getClient(item1AdminToken).perform(get("/api/authz/authorizations/search/object?embed=feature&uri=" + getClient(item1AdminToken).perform(get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/items/" + item1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" + feature + "')]").doesNotExist()); // Verify item 1 admin doesn’t have this feature on item 2 - getClient(item1AdminToken).perform(get("/api/authz/authorizations/search/object?embed=feature&uri=" + getClient(item1AdminToken).perform(get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/items/" + item2.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" + feature + "')]").doesNotExist()); // Verify the general admin has this feature on the bundle in item 1 - getClient(adminToken).perform(get("/api/authz/authorizations/search/object?embed=feature&uri=" + getClient(adminToken).perform(get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/items/" + item1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" + feature + "')]").exists()); // Verify community A admin has this feature on the bundle in item 1 - getClient(communityAAdminToken).perform(get("/api/authz/authorizations/search/object?embed=feature&uri=" + getClient(communityAAdminToken) + .perform(get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/items/" + item1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" + feature + "')]").exists()); // Verify collection X admin has this feature on the bundle in item 1 - getClient(collectionXAdminToken).perform(get("/api/authz/authorizations/search/object?embed=feature&uri=" + getClient(collectionXAdminToken) + .perform(get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/items/" + item1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" + feature + "')]").exists()); // Verify item 1 admin has this feature on the bundle in item 1 - getClient(item1AdminToken).perform(get("/api/authz/authorizations/search/object?embed=feature&uri=" + getClient(item1AdminToken).perform(get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/bundles/" + bundle1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" + feature + "')]").exists()); // Verify item 1 admin doesn’t have this feature on the bundle in item 2 - getClient(item1AdminToken).perform(get("/api/authz/authorizations/search/object?embed=feature&uri=" + getClient(item1AdminToken).perform(get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/bundles/" + bundle2.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" + feature + "')]").doesNotExist()); // Verify the general admin has this feature on the bitstream in item 1 - getClient(adminToken).perform(get("/api/authz/authorizations/search/object?embed=feature&uri=" + getClient(adminToken).perform(get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/bitstreams/" + bitstream1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" + feature + "')]").exists()); // Verify community A admin has this feature on the bitstream in item 1 - getClient(communityAAdminToken).perform(get("/api/authz/authorizations/search/object?embed=feature&uri=" + getClient(communityAAdminToken) + .perform(get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/bitstreams/" + bitstream1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" + feature + "')]").exists()); // Verify collection X admin has this feature on the bitstream in item 1 - getClient(collectionXAdminToken).perform(get("/api/authz/authorizations/search/object?embed=feature&uri=" + getClient(collectionXAdminToken) + .perform(get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/bitstreams/" + bitstream1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" + feature + "')]").exists()); // Verify item 1 admin has this feature on the bitstream in item 1 - getClient(item1AdminToken).perform(get("/api/authz/authorizations/search/object?embed=feature&uri=" + getClient(item1AdminToken).perform(get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/bitstreams/" + bitstream1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" + feature + "')]").exists()); // Verify item 1 admin doesn’t have this feature on the bitstream in item 2 - getClient(item1AdminToken).perform(get("/api/authz/authorizations/search/object?embed=feature&uri=" + getClient(item1AdminToken).perform(get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/bitstreams/" + bitstream2.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" @@ -1090,7 +1104,8 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration context.restoreAuthSystemState(); String communityAAAdminToken = getAuthToken(communityAAAdmin.getEmail(), password); //verify the community AA admin has this feature on community AA - getClient(communityAAAdminToken).perform(get("/api/authz/authorizations/search/object?embed=feature&uri=" + getClient(communityAAAdminToken) + .perform(get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/communities/" + communityAA.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" @@ -1105,7 +1120,8 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration .build(); context.restoreAuthSystemState(); // verify collection X admin has this feature on collection X - getClient(collectionXAdminToken).perform(get("/api/authz/authorizations/search/object?embed=feature&uri=" + getClient(collectionXAdminToken) + .perform(get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/collections/" + collectionX.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" @@ -1120,7 +1136,7 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration .build(); context.restoreAuthSystemState(); // verify item 1 admin has this feature on item 1 - getClient(item1AdminToken).perform(get("/api/authz/authorizations/search/object?embed=feature&uri=" + getClient(item1AdminToken).perform(get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/items/" + item1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" @@ -1151,13 +1167,15 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration context.restoreAuthSystemState(); String communityADeleterToken = getAuthToken(communityADeleter.getEmail(), password); // Verify the user has this feature on community A - getClient(communityADeleterToken).perform(get("/api/authz/authorizations/search/object?embed=feature&uri=" + getClient(communityADeleterToken) + .perform(get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/communities/" + communityA.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" + feature + "')]").exists()); // Verify this user doesn’t have this feature on community AA - getClient(communityADeleterToken).perform(get("/api/authz/authorizations/search/object?embed=feature&uri=" + getClient(communityADeleterToken) + .perform(get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/communities/" + communityAA.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" @@ -1179,19 +1197,22 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration context.restoreAuthSystemState(); String communityARemoverToken = getAuthToken(communityARemover.getEmail(), password); // Verify the user has this feature on community AA - getClient(communityARemoverToken).perform(get("/api/authz/authorizations/search/object?embed=feature&uri=" + getClient(communityARemoverToken) + .perform(get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/communities/" + communityAA.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" + feature + "')]").exists()); // Verify this user doesn’t have this feature on community A - getClient(communityARemoverToken).perform(get("/api/authz/authorizations/search/object?embed=feature&uri=" + getClient(communityARemoverToken) + .perform(get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/communities/" + communityA.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" + feature + "')]").doesNotExist()); // Verify this user doesn’t have this feature on collection X - getClient(communityARemoverToken).perform(get("/api/authz/authorizations/search/object?embed=feature&uri=" + getClient(communityARemoverToken) + .perform(get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/collections/" + collectionX.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" @@ -1212,19 +1233,22 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration context.restoreAuthSystemState(); String communityAARemoverToken = getAuthToken(communityAARemover.getEmail(), password); // Verify the user has this feature on collection X - getClient(communityAARemoverToken).perform(get("/api/authz/authorizations/search/object?embed=feature&uri=" + getClient(communityAARemoverToken) + .perform(get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/collections/" + collectionX.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" + feature + "')]").exists()); // Verify this user doesn’t have this feature on community AA - getClient(communityAARemoverToken).perform(get("/api/authz/authorizations/search/object?embed=feature&uri=" + getClient(communityAARemoverToken) + .perform(get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/communities/" + communityAA.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" + feature + "')]").doesNotExist()); // Verify this user doesn’t have this feature on item 1 - getClient(communityAARemoverToken).perform(get("/api/authz/authorizations/search/object?embed=feature&uri=" + getClient(communityAARemoverToken) + .perform(get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/items/" + item1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" @@ -1245,7 +1269,8 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration context.restoreAuthSystemState(); String collectionXRemoverToken = getAuthToken(collectionXRemover.getEmail(), password); // Verify the user doesn’t have this feature on item 1 - getClient(collectionXRemoverToken).perform(get("/api/authz/authorizations/search/object?embed=feature&uri=" + getClient(collectionXRemoverToken) + .perform(get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/items/" + item1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" @@ -1266,7 +1291,7 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration context.restoreAuthSystemState(); String item1DeleterToken = getAuthToken(item1Deleter.getEmail(), password); // Verify the user doesn’t have this feature on item 1 - getClient(item1DeleterToken).perform(get("/api/authz/authorizations/search/object?embed=feature&uri=" + getClient(item1DeleterToken).perform(get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/items/" + item1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" @@ -1293,21 +1318,21 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration String collectionXRemoverItem1DeleterToken = getAuthToken(collectionXRemoverItem1Deleter.getEmail(), password); // Verify the user has this feature on item 1 getClient(collectionXRemoverItem1DeleterToken).perform( - get("/api/authz/authorizations/search/object?embed=feature&uri=" + get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/items/" + item1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" + feature + "')]").exists()); // Verify this user doesn’t have this feature on collection X getClient(collectionXRemoverItem1DeleterToken).perform( - get("/api/authz/authorizations/search/object?embed=feature&uri=" + get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/collections/" + collectionX.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" + feature + "')]").doesNotExist()); // Verify this user doesn’t have this feature on the bundle in item 1 getClient(collectionXRemoverItem1DeleterToken).perform( - get("/api/authz/authorizations/search/object?embed=feature&uri=" + get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/bundles/" + bundle1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" @@ -1328,19 +1353,19 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration context.restoreAuthSystemState(); String item1RemoverToken = getAuthToken(item1Remover.getEmail(), password); // Verify the user has this feature on the bundle in item 1 - getClient(item1RemoverToken).perform(get("/api/authz/authorizations/search/object?embed=feature&uri=" + getClient(item1RemoverToken).perform(get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/bundles/" + bundle1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" + feature + "')]").exists()); // Verify this user doesn’t have this feature on item 1 - getClient(item1RemoverToken).perform(get("/api/authz/authorizations/search/object?embed=feature&uri=" + getClient(item1RemoverToken).perform(get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/items/" + item1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" + feature + "')]").doesNotExist()); // Verify this user doesn’t have this feature on the bitstream in item 1 - getClient(item1RemoverToken).perform(get("/api/authz/authorizations/search/object?embed=feature&uri=" + getClient(item1RemoverToken).perform(get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/bitstreams/" + bitstream1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" @@ -1361,7 +1386,8 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration context.restoreAuthSystemState(); String bundle1RemoverToken = getAuthToken(bundle1Remover.getEmail(), password); // Verify the user doesn’t have this feature on the bitstream in item 1 - getClient(bundle1RemoverToken).perform(get("/api/authz/authorizations/search/object?embed=feature&uri=" + getClient(bundle1RemoverToken) + .perform(get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/bitstreams/" + bitstream1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" @@ -1388,7 +1414,8 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration context.restoreAuthSystemState(); String bundle1item1RemoverToken = getAuthToken(bundle1item1Remover.getEmail(), password); // Verify the user has this feature on the bitstream in item 1 - getClient(bundle1item1RemoverToken).perform(get("/api/authz/authorizations/search/object?embed=feature&uri=" + getClient(bundle1item1RemoverToken) + .perform(get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/bitstreams/" + bitstream1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" @@ -1404,35 +1431,38 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration final String feature = "canReorderBitstreams"; // Verify the general admin has this feature on the bundle in item 1 - getClient(adminToken).perform(get("/api/authz/authorizations/search/object?embed=feature&uri=" + getClient(adminToken).perform(get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/bundles/" + bundle1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" + feature + "')]").exists()); // Verify community A admin has this feature on the bundle in item 1 - getClient(communityAAdminToken).perform(get("/api/authz/authorizations/search/object?embed=feature&uri=" + getClient(communityAAdminToken) + .perform(get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/bundles/" + bundle1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" + feature + "')]").exists()); // Verify collection X admin has this feature on the bundle in item 1 - getClient(collectionXAdminToken).perform(get("/api/authz/authorizations/search/object?embed=feature&uri=" + getClient(collectionXAdminToken) + .perform(get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/bundles/" + bundle1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" + feature + "')]").exists()); // Verify item 1 admin has this feature on the bundle in item 1 - getClient(item1AdminToken).perform(get("/api/authz/authorizations/search/object?embed=feature&uri=" + getClient(item1AdminToken).perform(get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/bundles/" + bundle1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" + feature + "')]").exists()); // Verify community A admin doesn’t have this feature on the bundle in item 2 - getClient(communityAAdminToken).perform(get("/api/authz/authorizations/search/object?embed=feature&uri=" + getClient(communityAAdminToken) + .perform(get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/bundles/" + bundle2.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" @@ -1447,19 +1477,21 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration final String feature = "canReorderBitstreams"; // Verify community A write doesn’t have this feature on the bundle in item 1 - getClient(communityAWriterToken).perform(get("/api/authz/authorizations/search/object?embed=feature&uri=" + getClient(communityAWriterToken) + .perform(get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/bundles/" + bundle1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" + feature + "')]").doesNotExist()); // Verify collection X write doesn’t have this feature on the bundle in item 1 - getClient(collectionXWriterToken).perform(get("/api/authz/authorizations/search/object?embed=feature&uri=" + getClient(collectionXWriterToken) + .perform(get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/bundles/" + bundle1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" + feature + "')]").doesNotExist()); // Verify item 1 write doesn’t have this feature on the bundle in item 1 - getClient(item1WriterToken).perform(get("/api/authz/authorizations/search/object?embed=feature&uri=" + getClient(item1WriterToken).perform(get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/bundles/" + bundle1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" @@ -1467,7 +1499,8 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration // Create a new user, grant WRITE permissions on the bundle in item 1 to this user // Verify the user has this feature on the bundle in item 1 - getClient(communityAWriterToken).perform(get("/api/authz/authorizations/search/object?embed=feature&uri=" + getClient(communityAWriterToken) + .perform(get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/bundles/" + bundle1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" @@ -1483,35 +1516,38 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration final String feature = "canCreateBitstream"; // Verify the general admin has this feature on the bundle in item 1 - getClient(adminToken).perform(get("/api/authz/authorizations/search/object?embed=feature&uri=" + getClient(adminToken).perform(get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/bundles/" + bundle1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" + feature + "')]").exists()); // Verify community A admin has this feature on the bundle in item 1 - getClient(communityAAdminToken).perform(get("/api/authz/authorizations/search/object?embed=feature&uri=" + getClient(communityAAdminToken) + .perform(get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/bundles/" + bundle1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" + feature + "')]").exists()); // Verify collection X admin has this feature on the bundle in item 1 - getClient(collectionXAdminToken).perform(get("/api/authz/authorizations/search/object?embed=feature&uri=" + getClient(collectionXAdminToken) + .perform(get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/bundles/" + bundle1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" + feature + "')]").exists()); // Verify item 1 admin has this feature on the bundle in item 1 - getClient(item1AdminToken).perform(get("/api/authz/authorizations/search/object?embed=feature&uri=" + getClient(item1AdminToken).perform(get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/bundles/" + bundle1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" + feature + "')]").exists()); // Verify community A admin doesn’t have this feature on the bundle in item 2 - getClient(communityAAdminToken).perform(get("/api/authz/authorizations/search/object?embed=feature&uri=" + getClient(communityAAdminToken) + .perform(get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/bundles/" + bundle2.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" @@ -1526,21 +1562,23 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration final String feature = "canCreateBitstream"; // Verify community A write doesn’t have this feature on the bundle in item 1 - getClient(communityAWriterToken).perform(get("/api/authz/authorizations/search/object?embed=feature&uri=" + getClient(communityAWriterToken) + .perform(get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/bundles/" + bundle1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" + feature + "')]").doesNotExist()); // Verify collection X write doesn’t have this feature on the bundle in item 1 - getClient(collectionXWriterToken).perform(get("/api/authz/authorizations/search/object?embed=feature&uri=" + getClient(collectionXWriterToken) + .perform(get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/bundles/" + bundle1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" + feature + "')]").doesNotExist()); // Verify item 1 write doesn’t have this feature on the bundle in item 1 - getClient(item1WriterToken).perform(get("/api/authz/authorizations/search/object?embed=feature&uri=" + getClient(item1WriterToken).perform(get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/bundles/" + bundle1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" @@ -1561,7 +1599,7 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration context.restoreAuthSystemState(); String bundle1WriterToken = getAuthToken(bundle1Writer.getEmail(), password); // Verify the user doesn’t have this feature on the bundle in item 1 - getClient(bundle1WriterToken).perform(get("/api/authz/authorizations/search/object?embed=feature&uri=" + getClient(bundle1WriterToken).perform(get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/bundles/" + bundle1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" @@ -1582,7 +1620,7 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration context.restoreAuthSystemState(); String bundle1AdderToken = getAuthToken(bundle1Adder.getEmail(), password); // Verify the user doesn’t have this feature on the bundle in item 1 - getClient(bundle1AdderToken).perform(get("/api/authz/authorizations/search/object?embed=feature&uri=" + getClient(bundle1AdderToken).perform(get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/bundles/" + bundle1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" @@ -1619,7 +1657,8 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration context.restoreAuthSystemState(); String bundle1WriterAdderToken = getAuthToken(bundle1WriterAdder.getEmail(), password); // Verify the user has this feature on the bundle in item 1 - getClient(bundle1WriterAdderToken).perform(get("/api/authz/authorizations/search/object?embed=feature&uri=" + getClient(bundle1WriterAdderToken) + .perform(get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/bundles/" + bundle1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" @@ -1639,21 +1678,23 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration final String feature = "canCreateBundle"; // Verify community A write doesn’t have this feature on item 1 - getClient(communityAWriterToken).perform(get("/api/authz/authorizations/search/object?embed=feature&uri=" + getClient(communityAWriterToken) + .perform(get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/items/" + item1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" + feature + "')]").doesNotExist()); // Verify collection X write doesn’t have this feature on item 1 - getClient(collectionXWriterToken).perform(get("/api/authz/authorizations/search/object?embed=feature&uri=" + getClient(collectionXWriterToken) + .perform(get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/items/" + item1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" + feature + "')]").doesNotExist()); // Verify item 1 write doesn’t have this feature on item 1 - getClient(item1WriterToken).perform(get("/api/authz/authorizations/search/object?embed=feature&uri=" + getClient(item1WriterToken).perform(get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/items/" + item1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" @@ -1679,7 +1720,8 @@ public class GenericAuthorizationFeatureIT extends AbstractControllerIntegration context.restoreAuthSystemState(); String item1AdderWriterToken = getAuthToken(item1AdderWriter.getEmail(), password); // Verify the user has this feature on item 1 - getClient(item1AdderWriterToken).perform(get("/api/authz/authorizations/search/object?embed=feature&uri=" + getClient(item1AdderWriterToken) + .perform(get("/api/authz/authorizations/search/object?size=1000&embed=feature&uri=" + "http://localhost/api/core/items/" + item1.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" From 3fd93df91e7db9364e9c331a8a43904004bfc0bf Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Thu, 20 Apr 2023 13:38:58 +0200 Subject: [PATCH 052/412] [DSC-963] Added missing applicationContext set on DefaultMethodSecurityExpressionHandler --- .../dspace/app/rest/security/MethodSecurityConfig.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/MethodSecurityConfig.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/MethodSecurityConfig.java index 29bfc13d83..5ee308c73e 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/MethodSecurityConfig.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/MethodSecurityConfig.java @@ -8,6 +8,7 @@ package org.dspace.app.rest.security; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Configuration; import org.springframework.security.access.PermissionEvaluator; import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler; @@ -22,10 +23,13 @@ public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration { @Autowired private PermissionEvaluator dSpacePermissionEvaluator; + @Autowired + private ApplicationContext applicationContext; + @Override protected MethodSecurityExpressionHandler createExpressionHandler() { - DefaultMethodSecurityExpressionHandler expressionHandler = - new DefaultMethodSecurityExpressionHandler(); + DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler(); + expressionHandler.setApplicationContext(applicationContext); expressionHandler.setPermissionEvaluator(dSpacePermissionEvaluator); return expressionHandler; } From ced1c79d1ac4d0adfe5a28a36fb0089a730b8783 Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Thu, 20 Apr 2023 13:56:08 +0200 Subject: [PATCH 053/412] [DSC-963] Removed duplicated @EnableGlobalMethodSecurity --- .../org/dspace/app/rest/security/WebSecurityConfiguration.java | 1 - 1 file changed, 1 deletion(-) 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 fad60c20d2..fed978e7fa 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 @@ -41,7 +41,6 @@ import org.springframework.security.web.util.matcher.AntPathRequestMatcher; @EnableWebSecurity @Configuration @EnableConfigurationProperties(SecurityProperties.class) -@EnableGlobalMethodSecurity(prePostEnabled = true) public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter { public static final String ADMIN_GRANT = "ADMIN"; From b35b837a2a713b0620d43d0d54ea046b26191c30 Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Thu, 20 Apr 2023 14:23:30 +0200 Subject: [PATCH 054/412] [DSC-963] Fixed checkstyle --- .../org/dspace/app/rest/security/WebSecurityConfiguration.java | 1 - 1 file changed, 1 deletion(-) 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 fed978e7fa..7aa22533fd 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 @@ -20,7 +20,6 @@ import org.springframework.context.annotation.Lazy; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; -import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.WebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; From efcf9dba20ac4c49516d8486a33e14f06db86080 Mon Sep 17 00:00:00 2001 From: MajoBerger Date: Fri, 9 Jun 2023 11:22:50 +0200 Subject: [PATCH 055/412] added failsafe while creating admin when db is not connected --- .../org/dspace/administer/CreateAdministrator.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/dspace-api/src/main/java/org/dspace/administer/CreateAdministrator.java b/dspace-api/src/main/java/org/dspace/administer/CreateAdministrator.java index 81250e9c82..58b8549391 100644 --- a/dspace-api/src/main/java/org/dspace/administer/CreateAdministrator.java +++ b/dspace-api/src/main/java/org/dspace/administer/CreateAdministrator.java @@ -116,6 +116,17 @@ public final class CreateAdministrator { protected CreateAdministrator() throws Exception { context = new Context(); + try { + context.getDBConfig(); + } catch (NullPointerException npr) { + // if database is null, there is no point in continuing. Prior to this exception and catch, + // NullPointerException was thrown, that wasn't very helpful. + throw new IllegalStateException("Problem connecting to database. This " + + "indicates issue with either network or version (or possibly some other). " + + "If you are running this in docker-compose, please make sure dspace-cli was " + + "built from the same sources as running dspace container AND that they are in " + + "the same project/network."); + } groupService = EPersonServiceFactory.getInstance().getGroupService(); ePersonService = EPersonServiceFactory.getInstance().getEPersonService(); } From 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 056/412] 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 d41c51638c..b21a76f352 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 057/412] 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 b21a76f352..c4c0b2182a 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 a533704a27ed97f16125590c4569589991119356 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 23 Jun 2023 12:10:56 -0500 Subject: [PATCH 058/412] [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 d0d2bf8608..0e70fc52e0 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 8ffa26e967..30e20489b8 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 8b6a3ee414..08e732d457 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 80c0614781..09c3e704de 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 dcc47b1753..320567f3c1 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 6f4b2871df..2221f9ca0e 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 488fa05c96..fe7c6ab8b6 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 02f293dbbb..777041775d 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 3bc838d3ea..c3fbdecb79 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 100561849d..dd6dbcb1ff 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 c12b13a38d..e66622ce6b 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 4f3e7cc1c6..14c6ff93f7 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 c3921bfa27..bebb6d183d 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 75f16a4205..bfa21f5d66 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 432a928b5b..4c9799243f 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 059/412] 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 544dc99cb4..1ce3673452 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.content.Item; 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 class AccessStatusServiceImpl implements AccessStatusService { 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 10a608bb76..6ca0292fdb 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 @@ package org.dspace.authority; 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.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.apache.solr.common.SolrDocument; import org.apache.solr.common.SolrInputDocument; @@ -25,9 +30,6 @@ import org.dspace.content.MetadataValue; 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 class AuthorityValue { } /** - * Information that can be used the choice ui + * Information that can be used the choice ui. * * @return map */ @@ -200,42 +202,51 @@ public class AuthorityValue { 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 class AuthorityValue { 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 dec0b050f3..c83abbf2b2 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 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. + * Beans are configured in the {@code crossref-integration.xml} file. * * @author Francesco Pio Scognamiglio (francescopio.scognamiglio at 4science.com) */ @@ -41,22 +41,25 @@ public class CrossRefDateMetadataProcessor implements JsonPathMetadataProcessor 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 a41e985deb..b9bca5e913 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 static org.junit.Assert.fail; 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.core.Constants; 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 class DefaultAccessStatusHelperTest extends AbstractUnitTest { 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 class DefaultAccessStatusHelperTest extends AbstractUnitTest { 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 class DefaultAccessStatusHelperTest extends AbstractUnitTest { 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 class DefaultAccessStatusHelperTest extends AbstractUnitTest { 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 class DefaultAccessStatusHelperTest extends AbstractUnitTest { 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 class DefaultAccessStatusHelperTest extends AbstractUnitTest { 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 ee6723480e..c2543ca17b 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.ByteArrayInputStream; 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.core.Constants; 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 class GoogleMetadataTest extends AbstractUnitTest { /** * Test empty bitstreams + * @throws java.lang.Exception passed through. */ @Test public void testGetPDFURLWithEmptyBitstreams() throws Exception { @@ -348,8 +349,9 @@ public class GoogleMetadataTest extends AbstractUnitTest { } /** - * 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 class GoogleMetadataTest extends AbstractUnitTest { 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 0000000000..74b1e77766 --- /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 b20515017a..4455668146 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.Constants; 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 @@ public abstract class AbstractDSpaceObjectBuilder } /** - * 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 @@ public abstract class AbstractDSpaceObjectBuilder } /** - * 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 @@ public abstract class AbstractDSpaceObjectBuilder } 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 @@ public abstract class AbstractDSpaceObjectBuilder /** * 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 @@ public abstract class AbstractDSpaceObjectBuilder /** * 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 @@ public abstract class AbstractDSpaceObjectBuilder /** * 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 424833e5cc..2822d3624e 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 @@ package org.dspace.builder; 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 @@ public class BitstreamBuilder extends AbstractDSpaceObjectBuilder { 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 3e5ab0f38f..f6190c5751 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 static org.dspace.content.authority.Choices.CF_ACCEPTED; 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 class ItemBuilder extends AbstractDSpaceObjectBuilder { 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 0000000000..89b860d36f --- /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 4813cc6596..5fbf669bae 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.IOException; 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 class BitstreamRestControllerIT extends AbstractControllerIntegrationTest .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 class BitstreamRestControllerIT extends AbstractControllerIntegrationTest .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 class BitstreamRestControllerIT extends AbstractControllerIntegrationTest .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 class BitstreamRestControllerIT extends AbstractControllerIntegrationTest .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 8b34edb938..bc276557df 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. 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 class BitstreamRestRepositoryIT extends AbstractControllerIntegrationTest .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 class BitstreamRestRepositoryIT extends AbstractControllerIntegrationTest .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 class BitstreamRestRepositoryIT extends AbstractControllerIntegrationTest .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 class BitstreamRestRepositoryIT extends AbstractControllerIntegrationTest .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 class BitstreamRestRepositoryIT extends AbstractControllerIntegrationTest .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 class BitstreamRestRepositoryIT extends AbstractControllerIntegrationTest .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 class BitstreamRestRepositoryIT extends AbstractControllerIntegrationTest .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 class BitstreamRestRepositoryIT extends AbstractControllerIntegrationTest .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 class BitstreamRestRepositoryIT extends AbstractControllerIntegrationTest .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 class BitstreamRestRepositoryIT extends AbstractControllerIntegrationTest .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 d1791ab872..75df0feb34 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. 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 class BrowsesResourceControllerIT extends AbstractControllerIntegrationTe .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 class BrowsesResourceControllerIT extends AbstractControllerIntegrationTe .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 a115c8aa2f..6095c20c9e 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. 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 class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest .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 class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest /** * 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 class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest /** * 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 801976be9f..08463fb222 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 static org.springframework.test.web.servlet.result.MockMvcResultMatchers. 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 class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { .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 class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { // 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 class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { .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 class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { // 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 class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { 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 class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { // 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 class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { 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 class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { 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 class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { 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 class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { 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 class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { 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 class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { 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 class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { 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 class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { 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 class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { 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 class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { 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 class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { .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 class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { .withIssueDate("2015-10-21") .withAuthor("Smith, Donald") .withSubject("ExtraEntry") - .withEmbargoPeriod("1 week") + .withEmbargoPeriod(Period.ofWeeks(1)) .build(); context.restoreAuthSystemState(); @@ -1778,7 +1778,7 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { .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 class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { 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 class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { 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 class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { 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 class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { 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 class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { .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 060/412] 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 0e70fc52e0..3a4b673f0b 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 2221f9ca0e..b65e3bc238 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 061/412] 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 74b1e77766..81873d5cdb 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 class AuthorityValueTest { // 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 062/412] 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 81873d5cdb..ac0d7880e1 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 89b860d36f..fa9322d72d 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 063/412] 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 ac0d7880e1..07c4b65f40 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 fa9322d72d..323856cd0a 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 064/412] 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 4455668146..e7ebd8768e 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 @@ package org.dspace.builder; 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 @@ import org.dspace.eperson.Group; * * @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 @@ public abstract class AbstractDSpaceObjectBuilder 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 065/412] 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 f6190c5751..f4f504e60f 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 class ItemBuilder extends AbstractDSpaceObjectBuilder { } /** - * 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 class ItemBuilder extends AbstractDSpaceObjectBuilder { 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 6d1d242cad..0bd80a3bf1 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.DateFormat; 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 class LinksetRestControllerIT extends AbstractControllerIntegrationTest { .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 class LinksetRestControllerIT extends AbstractControllerIntegrationTest { .withName("Bitstream") .withDescription("description") .withMimeType(bitstreamMimeType) - .withEmbargoPeriod("6 months") + .withEmbargoPeriod(Period.ofMonths(6)) .build(); } context.restoreAuthSystemState(); @@ -949,7 +950,7 @@ public class LinksetRestControllerIT extends AbstractControllerIntegrationTest { .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 066/412] 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 0bd80a3bf1..b363e4885e 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.io.InputStream; 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 067/412] 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 998d934c95..a95407876e 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.ConfigurationService; 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. *

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

+ * {@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. *

- * 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: - *

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

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

+ * 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 @@ import org.dspace.services.factory.DSpaceServicesFactory;
  *
  *     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 @@ import org.dspace.services.factory.DSpaceServicesFactory;
  *
  *     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 class Email { } /** - * Add a recipient + * Add a recipient. * * @param email the recipient's email address */ @@ -205,7 +230,7 @@ public class Email { } /** - * Set the subject of the message + * Set the subject of the message. * * @param s the subject of the message */ @@ -214,7 +239,7 @@ public class Email { } /** - * Set the reply-to email address + * Set the reply-to email address. * * @param email the reply-to email address */ @@ -223,7 +248,7 @@ public class 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 class Email { 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 class Email { /** 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 class Email { 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 class Email { /** * 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 class Email { } 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 class Email { } /** - * Utility struct class for handling file attachments. + * Utility record class for handling file attachments. * * @author ojd20 */ @@ -563,7 +605,7 @@ public class Email { } /** - * 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 class Email { } /** + * Wrap an {@link InputStream} in a {@link DataSource}. + * * @author arnaldo */ public static class InputStreamDataSource implements DataSource { @@ -587,6 +631,14 @@ public class Email { 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 068/412] 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 a95407876e..a64a85a073 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 class Email { // 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 069/412] 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 a64a85a073..f6df740a53 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.ArrayList; 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.MimeMessage; 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 class Email { moreAttachments = new ArrayList<>(10); subject = ""; template = null; - content = ""; replyTo = null; charset = null; } @@ -221,12 +217,20 @@ public class 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 class Email { * {@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 class Email { 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 class Email { // 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 070/412] 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 df333fa500..7286fb8e83 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 @@ import org.mockito.junit.MockitoJUnitRunner; * @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 d42213da2c..1b6f23032d 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.dspace.services.factory.DSpaceServicesFactory; 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 528568c4e5..44653300e0 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.Before; 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 b6f5da6be0..2d08223b2e 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 @@ import org.junit.Test; * 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 3fff6fec47..ff7d03b49f 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.After; 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 50b4d3f3b4..e40577ef36 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.dspace.versioning.service.VersioningService; 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 1bc6bf1408..7e549f6cae 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.dspace.services.factory.DSpaceServicesFactory; 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 071/412] 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 955c3a78c3..f15e82ce52 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.ByteArrayOutputStream; 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.apache.logging.log4j.Logger; 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.content.service.RelationshipService; 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 @@ public class ItemUtils { 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 @@ public class ItemUtils { 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 a3a4e66670..b9d81aef5d 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 072/412] 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 f15e82ce52..b1b949770d 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 @@ public class ItemUtils { 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 @@ public class ItemUtils { 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 073/412] 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 b1b949770d..107454ecd0 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.sql.SQLException; 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 @@ public class ItemUtils { } /** - * 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 @@ public class ItemUtils { 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 074/412] 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 89010a7308..0732eea2a0 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 class DatabaseUtils { 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 075/412] 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 ec8f8769be..4cac1da314 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 final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService 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 076/412] [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 4c9799243f..3873fa372c 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 077/412] [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 c05546d569..6379572ba4 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 30e20489b8..6e93a88e13 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 08e732d457..808940eb7b 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 09c3e704de..f21381eb4e 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 320567f3c1..d7daf92aba 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 2221f9ca0e..6e07365499 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 fe7c6ab8b6..e3db95abf5 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 777041775d..349ac3abd0 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 c3fbdecb79..740351d5db 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 dd6dbcb1ff..922e0f0fe5 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 e66622ce6b..3a9e5caa8a 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 14c6ff93f7..d3713454f6 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 bebb6d183d..d0a25332aa 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 bfa21f5d66..ac7af682da 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 3873fa372c..a082bd0524 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 078/412] 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 6e07365499..6cec67baa3 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 079/412] 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 6e93a88e13..2f34671139 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 080/412] 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 a082bd0524..65e54dc61a 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 081/412] 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 6379572ba4..ea284c956c 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 082/412] 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 65e54dc61a..be5b6e2fb0 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 083/412] 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 be5b6e2fb0..d6a9d8c4b2 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 084/412] 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 d6a9d8c4b2..4097404e00 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 085/412] 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 4097404e00..9c2021cb2d 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 086/412] 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 9c2021cb2d..c2411d20c0 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 087/412] 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 c2411d20c0..9737ca1680 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 088/412] 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 9737ca1680..e64b994ce8 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 089/412] 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 ea284c956c..0ea86b6e10 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 090/412] 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 0ea86b6e10..aab892ee04 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 091/412] 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 e64b994ce8..191179a4a7 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 092/412] 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 191179a4a7..bdf7caad51 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 093/412] 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 bdf7caad51..dde2af21aa 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 094/412] 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 dde2af21aa..f7fd25bc64 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 095/412] 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 f7fd25bc64..f45ccb3c18 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 096/412] 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 f45ccb3c18..a226dd6c57 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 097/412] 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 a226dd6c57..08e6ceede7 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 098/412] 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 08e6ceede7..f29dd3c306 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 099/412] 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 aab892ee04..a32f3ddfc2 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 100/412] 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 6cec67baa3..f4d47e88c2 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 101/412] 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 f29dd3c306..d3f30a8d1b 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 102/412] 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 f4d47e88c2..d7a8782b73 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 103/412] 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 349ac3abd0..3ddf4d1a83 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 d3f30a8d1b..757c522aa3 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 104/412] 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 a32f3ddfc2..1946eac9a5 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 757c522aa3..930532b8b1 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 105/412] 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 1946eac9a5..4252a44398 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 106/412] 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 4252a44398..ee8c21cb64 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 107/412] 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 930532b8b1..4d01bca240 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 108/412] 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 d7a8782b73..42ed115d91 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 109/412] 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 4d01bca240..5853f19246 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 110/412] 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 5853f19246..c43dc6c597 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 111/412] 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 c43dc6c597..00e1306d59 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 112/412] 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 00e1306d59..5dc53194b3 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 113/412] 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 1ce2e55886..ec4cb199ea 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 class CrossLinks { } 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 0000000000..83aab72d90 --- /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 114/412] 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 7853c3e11a..9f34a42047 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 @@ public class SolrLoggerServiceImpl implements SolrLoggerService, InitializingBea 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 115/412] 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 c169e4712f..e84d9f8591 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.collections.buffer.CircularFifoBuffer; 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 class GoogleAsyncEventListener extends AbstractUsageEventListener { UsageEvent usageEvent = (UsageEvent) event; LOGGER.debug("Usage event received " + event.getName()); - if (isNotBitstreamViewEvent(usageEvent)) { + if (!isContentBitstream(usageEvent)) { return; } @@ -171,9 +172,32 @@ public class GoogleAsyncEventListener extends AbstractUsageEventListener { 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 866d0fafed..e43e9fd820 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.ArrayList; 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 class GoogleAsyncEventListenerIT extends AbstractControllerIntegrationTes .withName("Test collection") .build(); - Item item = ItemBuilder.createItem(context, collection) + item = ItemBuilder.createItem(context, collection) .withTitle("Test item") .build(); @@ -238,6 +244,88 @@ public class GoogleAsyncEventListenerIT extends AbstractControllerIntegrationTes } + @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 @@ public class GoogleAsyncEventListenerIT extends AbstractControllerIntegrationTes 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 8e532310c1..89c8da9255 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 116/412] 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 0dc718c0b9..0000000000 --- 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 0000000000..9b61af72d1 --- /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 117/412] 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 7580b4ba3d..9e6dcc0b23 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 cc0c7099f4..0c3b1a0f2a 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 118/412] 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 971954a5e1..9ec6b85735 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 119/412] 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 9ec6b85735..f1ae184fd5 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 120/412] 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 005f9b4247..e1136a3f59 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 @@ public class ItemIndexFactoryImpl extends DSpaceObjectIndexFactoryImpl Date: Tue, 11 Jul 2023 16:52:20 +0200 Subject: [PATCH 121/412] 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 005f9b4247..39947146dd 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 class ItemIndexFactoryImpl extends DSpaceObjectIndexFactoryImpl Date: Fri, 14 Jul 2023 11:06:35 +0100 Subject: [PATCH 122/412] 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 ee220e5a4f..6d0c57c628 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 @@ public class FullTextContentStreams extends ContentStreamBase { // 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 123/412] 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 6d0c57c628..21468def68 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 @@ public class FullTextContentStreams extends ContentStreamBase { 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 class FullTextContentStreams extends ContentStreamBase { } 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 124/412] 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 955c3a78c3..35bef8c8d7 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 @@ public class ItemUtils { 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 125/412] [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 4ad1e47934..a65ea13bc2 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 @@ public class DSpaceApiExceptionControllerAdvice extends ResponseEntityExceptionH 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 @@ public class DSpaceApiExceptionControllerAdvice extends ResponseEntityExceptionH @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 @@ public class DSpaceApiExceptionControllerAdvice extends ResponseEntityExceptionH 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 @@ public class DSpaceApiExceptionControllerAdvice extends ResponseEntityExceptionH 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 @@ public class DSpaceApiExceptionControllerAdvice extends ResponseEntityExceptionH 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 126/412] 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 8324105a30..f99b3c31e5 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.InputStream; 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 class LicenseServiceImpl implements LicenseService { /** * 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 class LicenseServiceImpl implements LicenseService { * 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 @@ public class LicenseServiceImpl implements LicenseService { } } } + + 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 127/412] 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 8324105a30..5e42b04e71 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.InputStream; 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 class LicenseServiceImpl implements LicenseService { /** * 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 class LicenseServiceImpl implements LicenseService { * 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 @@ public class LicenseServiceImpl implements LicenseService { } } } + + /** + * 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 128/412] 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 f1ff6adf5a..664cba89fa 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 62e83b79ef..d54978375e 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 4e9b2b5b43..16a04d0002 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 9c66fed683..7dbdde3703 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 36ba6af2c9..e623d96079 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 35a6e60554..8660a9796c 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 32c54a5d0b..943f0732c6 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 fc2f30b9d8..5646370a91 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 00dde2e831..6690fb8bc5 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 ac1b4cb923..bee59ad876 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 129/412] 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 0000000000..418498fa44 --- /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 130/412] 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 cafd37931f..c37cfea0b1 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 131/412] 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 c37cfea0b1..c7f1cb80a9 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 132/412] 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 c7f1cb80a9..1feaf05cda 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 133/412] 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 e2c6c9c5db..1a8c2ddd3e 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.Group; 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 class MediaFilterServiceImpl implements MediaFilterService, InitializingB 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 class MediaFilterServiceImpl implements MediaFilterService, InitializingB // 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 class MediaFilterServiceImpl implements MediaFilterService, InitializingB } // 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 class MediaFilterServiceImpl implements MediaFilterService, InitializingB } 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 0000000000..7809e2048a --- /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 134/412] 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 7809e2048a..e1502e89b5 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 @@ public class ThrowableUtils { 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 135/412] 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 418498fa44..6c491e41c2 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 136/412] 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 0c3b1a0f2a..a023f4eef2 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 137/412] 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 6c491e41c2..50faf3f886 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 138/412] 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 5dc53194b3..7822d43109 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 139/412] 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 1a8c2ddd3e..b50fb22355 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 @@ package org.dspace.app.mediafilter; 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 class MediaFilterServiceImpl implements MediaFilterService, InitializingB 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 class MediaFilterServiceImpl implements MediaFilterService, InitializingB } catch (OutOfMemoryError oome) { logError("!!! OutOfMemoryError !!!"); + logError(formatBitstreamDetails(item.getHandle(), source)); } // we are overwriting, so remove old bitstream @@ -498,6 +485,37 @@ public class MediaFilterServiceImpl implements MediaFilterService, InitializingB } } + /** + * 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 140/412] 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 05c7a8d999..dd6c8d5e15 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.sql.SQLException; 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 @@ import org.springframework.stereotype.Service; * 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 class XmlWorkflowCuratorServiceImpl 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 class XmlWorkflowCuratorServiceImpl 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 @@ public class XmlWorkflowCuratorServiceImpl 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 @@ public class XmlWorkflowCuratorServiceImpl // 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 @@ public class XmlWorkflowCuratorServiceImpl 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 61477995c7..e3b743c4a7 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.GroupService; 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 class EPersonServiceImpl extends DSpaceObjectServiceImpl impleme 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 c5c9801c16..c3def01a82 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 interface EPersonService extends DSpaceObjectService, DSpaceObje 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 141/412] 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 1cacbf6aed..d847e907b4 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 544dc99cb4..f0f68b22a1 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 class AccessStatusServiceImpl implements AccessStatusService { 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 a67fa67af3..e7055181aa 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.content.service.ItemService; 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 @@ import org.dspace.eperson.Group; * 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 class DefaultAccessStatusHelper implements AccessStatusHelper { /** * 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 class DefaultAccessStatusHelper implements AccessStatusHelper { .findFirst() .orElse(null); } - return caculateAccessStatusForDso(context, bitstream, threshold); + return calculateAccessStatusForDso(context, bitstream, threshold); } /** @@ -104,7 +110,7 @@ public class DefaultAccessStatusHelper implements AccessStatusHelper { * @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 @@ public class DefaultAccessStatusHelper implements AccessStatusHelper { } 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 43de5e3c47..937cb02692 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 a41e985deb..9d90452bee 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 class DefaultAccessStatusHelperTest extends AbstractUnitTest { 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 class DefaultAccessStatusHelperTest extends AbstractUnitTest { 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 class DefaultAccessStatusHelperTest extends AbstractUnitTest { 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 6b3c5ded98..3201a02291 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 java.util.List; 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 @@ import org.dspace.xoai.util.ItemUtils; * open.access * * + * OR + * + * + * embargo + * 2024-10-10 + * + * * } * * Returning Values are based on: @@ -46,9 +54,15 @@ public class AccessStatusElementItemCompilePlugin implements XOAIExtensionItemCo 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 b9d81aef5d..a180b49c56 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 142/412] 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 937cb02692..2ed47bde4c 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 9d90452bee..f450f72e6a 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 143/412] 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 2d252ff476..2af526c560 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.io.InputStream; 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 @@ public class ItemUtils { 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 144/412] 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 2af526c560..6a0808259e 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.IOException; 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 145/412] 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 dd6c8d5e15..97537befd2 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 class XmlWorkflowCuratorServiceImpl 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 e3b743c4a7..2d0574a630 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 class EPersonServiceImpl extends DSpaceObjectServiceImpl impleme 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 c3def01a82..47be942e97 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.sql.SQLException; 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 interface EPersonService extends DSpaceObjectService, DSpaceObje 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 146/412] 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 a6cd49bdf1..1060a33031 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 147/412] 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 2ad1eac129..778b779cfe 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 interface XmlWorkflowCuratorService { * * @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 interface XmlWorkflowCuratorService { * @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 interface XmlWorkflowCuratorService { * @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 148/412] 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 e7055181aa..9b5227491b 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 class DefaultAccessStatusHelper implements AccessStatusHelper { .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 149/412] 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 6a0808259e..80eb67a2b9 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.IOException; 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 @@ public class ItemUtils { 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 150/412] #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 7f8a11e5ba..40fea6cf54 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 151/412] #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 8c1c534de1..0bb6793398 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 class HealthIndicatorsIT extends AbstractControllerIntegrationTest { 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 152/412] 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 a180b49c56..5c434e49ed 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 153/412] 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 9f34a42047..19c79af34d 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 @@ public class SolrLoggerServiceImpl implements SolrLoggerService, InitializingBea //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 154/412] 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 1a6f94f6a5..14737de635 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 @@ import org.springframework.beans.factory.annotation.Autowired; */ public class OpenaireEventsImportScriptConfiguration extends ScriptConfiguration { - @Autowired + /* private AuthorizeService authorizeService; - + */ private Class dspaceRunnableClass; @Override @@ -43,7 +39,7 @@ public class OpenaireEventsImportScriptConfiguration dspaceRunnableClass) { this.dspaceRunnableClass = dspaceRunnableClass; } - +/* @Override public boolean isAllowedToExecute(Context context) { try { @@ -52,7 +48,7 @@ public class OpenaireEventsImportScriptConfiguration Date: Mon, 7 Aug 2023 09:42:07 +0200 Subject: [PATCH 155/412] 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 fcb2098bd0..e6df016613 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.LogManager; 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 class ScriptLauncher { 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 2319aee317..4f64f69731 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 @@ public abstract class DSpaceRunnable implements R * @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 abstract class DSpaceRunnable implements R 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 642409a924..62f30f99f6 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 @@ package org.dspace.scripts.configuration; 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 abstract class ScriptConfiguration implements B * @return the options value of this ScriptConfiguration */ public abstract Options getOptions(); + + /** + * The getter for the options of the Script (help informations) + * @return the options value of this ScriptConfiguration for help + */ + public Options getHelpOptions() { + Options options = new Options(); + + options.addOption(Option.builder("h").longOpt("help") + .desc("help") + .hasArg(false).required(false).build()); + + return options; + } @Override public void setBeanName(String beanName) { diff --git a/dspace-api/src/test/java/org/dspace/app/bulkedit/MetadataExportIT.java b/dspace-api/src/test/java/org/dspace/app/bulkedit/MetadataExportIT.java index f767ba1663..68e5717012 100644 --- a/dspace-api/src/test/java/org/dspace/app/bulkedit/MetadataExportIT.java +++ b/dspace-api/src/test/java/org/dspace/app/bulkedit/MetadataExportIT.java @@ -99,8 +99,9 @@ public class MetadataExportIT script = scriptService.createDSpaceRunnableForScriptConfiguration(scriptConfiguration); } if (script != null) { - script.initialize(args, testDSpaceRunnableHandler, null); - script.run(); + if (DSpaceRunnable.StepResult.Continue.equals(script.initialize(args, testDSpaceRunnableHandler, null))) { + script.run(); + } } } @@ -206,8 +207,9 @@ public class MetadataExportIT script = scriptService.createDSpaceRunnableForScriptConfiguration(scriptConfiguration); } if (script != null) { - script.initialize(args, testDSpaceRunnableHandler, null); - script.run(); + if (DSpaceRunnable.StepResult.Continue.equals(script.initialize(args, testDSpaceRunnableHandler, null))) { + script.run(); + } } Exception exceptionDuringTestRun = testDSpaceRunnableHandler.getException(); @@ -235,8 +237,9 @@ public class MetadataExportIT script = scriptService.createDSpaceRunnableForScriptConfiguration(scriptConfiguration); } if (script != null) { - script.initialize(args, testDSpaceRunnableHandler, null); - script.run(); + if (DSpaceRunnable.StepResult.Continue.equals(script.initialize(args, testDSpaceRunnableHandler, null))) { + script.run(); + } } Exception exceptionDuringTestRun = testDSpaceRunnableHandler.getException(); diff --git a/dspace-api/src/test/java/org/dspace/app/bulkedit/MetadataImportIT.java b/dspace-api/src/test/java/org/dspace/app/bulkedit/MetadataImportIT.java index ac5e1e6ae6..0a0024ced9 100644 --- a/dspace-api/src/test/java/org/dspace/app/bulkedit/MetadataImportIT.java +++ b/dspace-api/src/test/java/org/dspace/app/bulkedit/MetadataImportIT.java @@ -144,8 +144,9 @@ public class MetadataImportIT extends AbstractIntegrationTestWithDatabase { script = scriptService.createDSpaceRunnableForScriptConfiguration(scriptConfiguration); } if (script != null) { - script.initialize(args, testDSpaceRunnableHandler, null); - script.run(); + if (DSpaceRunnable.StepResult.Continue.equals(script.initialize(args, testDSpaceRunnableHandler, null))) { + script.run(); + } } } diff --git a/dspace-api/src/test/java/org/dspace/app/csv/CSVMetadataImportReferenceIT.java b/dspace-api/src/test/java/org/dspace/app/csv/CSVMetadataImportReferenceIT.java index 5933dff71c..59b9491ccd 100644 --- a/dspace-api/src/test/java/org/dspace/app/csv/CSVMetadataImportReferenceIT.java +++ b/dspace-api/src/test/java/org/dspace/app/csv/CSVMetadataImportReferenceIT.java @@ -702,8 +702,9 @@ public class CSVMetadataImportReferenceIT extends AbstractIntegrationTestWithDat script = scriptService.createDSpaceRunnableForScriptConfiguration(scriptConfiguration); } if (script != null) { - script.initialize(args, testDSpaceRunnableHandler, null); - script.run(); + if (DSpaceRunnable.StepResult.Continue.equals(script.initialize(args, testDSpaceRunnableHandler, null))) { + script.run(); + } } if (testDSpaceRunnableHandler.getException() != null) { throw testDSpaceRunnableHandler.getException(); diff --git a/dspace-api/src/test/java/org/dspace/curate/CurationIT.java b/dspace-api/src/test/java/org/dspace/curate/CurationIT.java index 6232793c74..b50d52e963 100644 --- a/dspace-api/src/test/java/org/dspace/curate/CurationIT.java +++ b/dspace-api/src/test/java/org/dspace/curate/CurationIT.java @@ -43,8 +43,9 @@ public class CurationIT extends AbstractIntegrationTestWithDatabase { script = scriptService.createDSpaceRunnableForScriptConfiguration(scriptConfiguration); } if (script != null) { - script.initialize(args, testDSpaceRunnableHandler, null); - script.run(); + if (DSpaceRunnable.StepResult.Continue.equals(script.initialize(args, testDSpaceRunnableHandler, null))) { + script.run(); + } } } @@ -69,8 +70,9 @@ public class CurationIT extends AbstractIntegrationTestWithDatabase { script = scriptService.createDSpaceRunnableForScriptConfiguration(scriptConfiguration); } if (script != null) { - script.initialize(args, testDSpaceRunnableHandler, null); - script.run(); + if (DSpaceRunnable.StepResult.Continue.equals(script.initialize(args, testDSpaceRunnableHandler, null))) { + script.run(); + } } } } From b1377ca1ef82d80f2ece9b48b8f1571e786c4525 Mon Sep 17 00:00:00 2001 From: Christian Bethge Date: Mon, 7 Aug 2023 11:27:08 +0200 Subject: [PATCH 156/412] 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 e6df016613..bcb61a48ee 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 @@ public class ScriptLauncher { 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 4f64f69731..7fb8567f8c 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 @@ public abstract class DSpaceRunnable implements R * @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 abstract class DSpaceRunnable implements R 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 @@ public abstract class DSpaceRunnable implements R 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 abstract class DSpaceRunnable implements R 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 62f30f99f6..bbedab04e2 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 abstract class ScriptConfiguration implements B * @return the options value of this ScriptConfiguration */ public abstract Options getOptions(); - + /** * The getter for the options of the Script (help informations) + * * @return the options value of this ScriptConfiguration for help */ public Options getHelpOptions() { - Options options = new Options(); - - options.addOption(Option.builder("h").longOpt("help") - .desc("help") - .hasArg(false).required(false).build()); + Options options = new Options(); + + options.addOption(Option.builder("h").longOpt("help").desc("help").hasArg(false).required(false).build()); return options; } diff --git a/dspace-api/src/test/java/org/dspace/app/bulkedit/MetadataExportIT.java b/dspace-api/src/test/java/org/dspace/app/bulkedit/MetadataExportIT.java index 68e5717012..0b7fd80268 100644 --- a/dspace-api/src/test/java/org/dspace/app/bulkedit/MetadataExportIT.java +++ b/dspace-api/src/test/java/org/dspace/app/bulkedit/MetadataExportIT.java @@ -99,9 +99,9 @@ public class MetadataExportIT script = scriptService.createDSpaceRunnableForScriptConfiguration(scriptConfiguration); } if (script != null) { - if (DSpaceRunnable.StepResult.Continue.equals(script.initialize(args, testDSpaceRunnableHandler, null))) { - script.run(); - } + if (DSpaceRunnable.StepResult.Continue.equals(script.initialize(args, testDSpaceRunnableHandler, null))) { + script.run(); + } } } @@ -207,9 +207,9 @@ public class MetadataExportIT script = scriptService.createDSpaceRunnableForScriptConfiguration(scriptConfiguration); } if (script != null) { - if (DSpaceRunnable.StepResult.Continue.equals(script.initialize(args, testDSpaceRunnableHandler, null))) { - script.run(); - } + if (DSpaceRunnable.StepResult.Continue.equals(script.initialize(args, testDSpaceRunnableHandler, null))) { + script.run(); + } } Exception exceptionDuringTestRun = testDSpaceRunnableHandler.getException(); @@ -237,9 +237,9 @@ public class MetadataExportIT script = scriptService.createDSpaceRunnableForScriptConfiguration(scriptConfiguration); } if (script != null) { - if (DSpaceRunnable.StepResult.Continue.equals(script.initialize(args, testDSpaceRunnableHandler, null))) { - script.run(); - } + if (DSpaceRunnable.StepResult.Continue.equals(script.initialize(args, testDSpaceRunnableHandler, null))) { + script.run(); + } } Exception exceptionDuringTestRun = testDSpaceRunnableHandler.getException(); diff --git a/dspace-api/src/test/java/org/dspace/app/bulkedit/MetadataImportIT.java b/dspace-api/src/test/java/org/dspace/app/bulkedit/MetadataImportIT.java index 0a0024ced9..e50f7913ad 100644 --- a/dspace-api/src/test/java/org/dspace/app/bulkedit/MetadataImportIT.java +++ b/dspace-api/src/test/java/org/dspace/app/bulkedit/MetadataImportIT.java @@ -144,9 +144,9 @@ public class MetadataImportIT extends AbstractIntegrationTestWithDatabase { script = scriptService.createDSpaceRunnableForScriptConfiguration(scriptConfiguration); } if (script != null) { - if (DSpaceRunnable.StepResult.Continue.equals(script.initialize(args, testDSpaceRunnableHandler, null))) { - script.run(); - } + if (DSpaceRunnable.StepResult.Continue.equals(script.initialize(args, testDSpaceRunnableHandler, null))) { + script.run(); + } } } diff --git a/dspace-api/src/test/java/org/dspace/app/csv/CSVMetadataImportReferenceIT.java b/dspace-api/src/test/java/org/dspace/app/csv/CSVMetadataImportReferenceIT.java index 59b9491ccd..aee4b4d267 100644 --- a/dspace-api/src/test/java/org/dspace/app/csv/CSVMetadataImportReferenceIT.java +++ b/dspace-api/src/test/java/org/dspace/app/csv/CSVMetadataImportReferenceIT.java @@ -702,9 +702,10 @@ public class CSVMetadataImportReferenceIT extends AbstractIntegrationTestWithDat script = scriptService.createDSpaceRunnableForScriptConfiguration(scriptConfiguration); } if (script != null) { - if (DSpaceRunnable.StepResult.Continue.equals(script.initialize(args, testDSpaceRunnableHandler, null))) { - script.run(); - } + if (DSpaceRunnable.StepResult.Continue + .equals(script.initialize(args, testDSpaceRunnableHandler, null))) { + script.run(); + } } if (testDSpaceRunnableHandler.getException() != null) { throw testDSpaceRunnableHandler.getException(); diff --git a/dspace-api/src/test/java/org/dspace/curate/CurationIT.java b/dspace-api/src/test/java/org/dspace/curate/CurationIT.java index b50d52e963..31bfe2550a 100644 --- a/dspace-api/src/test/java/org/dspace/curate/CurationIT.java +++ b/dspace-api/src/test/java/org/dspace/curate/CurationIT.java @@ -43,9 +43,9 @@ public class CurationIT extends AbstractIntegrationTestWithDatabase { script = scriptService.createDSpaceRunnableForScriptConfiguration(scriptConfiguration); } if (script != null) { - if (DSpaceRunnable.StepResult.Continue.equals(script.initialize(args, testDSpaceRunnableHandler, null))) { - script.run(); - } + if (DSpaceRunnable.StepResult.Continue.equals(script.initialize(args, testDSpaceRunnableHandler, null))) { + script.run(); + } } } @@ -70,9 +70,9 @@ public class CurationIT extends AbstractIntegrationTestWithDatabase { script = scriptService.createDSpaceRunnableForScriptConfiguration(scriptConfiguration); } if (script != null) { - if (DSpaceRunnable.StepResult.Continue.equals(script.initialize(args, testDSpaceRunnableHandler, null))) { - script.run(); - } + if (DSpaceRunnable.StepResult.Continue.equals(script.initialize(args, testDSpaceRunnableHandler, null))) { + script.run(); + } } } } From ada6c021e406dc83c462988c9e7c8fb0bc060ba9 Mon Sep 17 00:00:00 2001 From: Francesco Bacchelli Date: Mon, 7 Aug 2023 13:37:22 +0200 Subject: [PATCH 157/412] 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 22da785d31..dbe44fd2e7 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 class OpenaireEventsImportIT extends AbstractIntegrationTestWithDatabase 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 class OpenaireEventsImportIT extends AbstractIntegrationTestWithDatabase 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 class OpenaireEventsImportIT extends AbstractIntegrationTestWithDatabase 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 class OpenaireEventsImportIT extends AbstractIntegrationTestWithDatabase 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 7d8dbd37f1..9bb8daae36 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 158/412] 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 8d1ba14b2c..3458361f43 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 class ItemServiceImpl extends DSpaceObjectServiceImpl implements It @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 50b4d3f3b4..1847a27c7f 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.AbstractIntegrationTestWithDatabase; 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.WorkspaceItem; 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 class ItemServiceTest extends AbstractIntegrationTestWithDatabase { 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 159/412] 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 32c5b92c60..b52043e267 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 class InstallItemServiceImpl implements InstallItemService { // 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 3458361f43..663585034d 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 class ItemServiceImpl extends DSpaceObjectServiceImpl implements It @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 b6bf7aa5cf..8b93e953eb 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 interface ItemService public void removeGroupPolicies(Context context, Item item, Group group) throws SQLException, AuthorizeException; /** - * remove all policies on an item and its contents, and replace them with + * Remove all policies on an item and its contents, and replace them with * the DEFAULT_ITEM_READ and DEFAULT_BITSTREAM_READ policies belonging to * the collection. * @@ -488,6 +488,26 @@ public interface ItemService public void inheritCollectionDefaultPolicies(Context context, Item item, Collection collection) throws java.sql.SQLException, AuthorizeException; + /** + * Remove all submission and workflow policies on an item and its contents, and add + * default collection policies which are not yet already in place. + * If overrideItemReadPolicies is true, then all read policies on the item are replaced (but only if the + * collection has a default read policy). + * + * @param context DSpace context object + * @param item item to reset policies on + * @param collection Collection + * @param overrideItemReadPolicies 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 + * if an SQL error or if no default policies found. It's a bit + * draconian, but default policies must be enforced. + * @throws AuthorizeException if authorization error + */ + public void inheritCollectionDefaultPolicies(Context context, Item item, Collection collection, + boolean overrideItemReadPolicies) + throws java.sql.SQLException, AuthorizeException; + /** * Adjust the Bundle and Bitstream policies to reflect what have been defined * during the submission/workflow. The temporary SUBMISSION and WORKFLOW From d4eb327ce5e139d44768456921298a4fefb9311e Mon Sep 17 00:00:00 2001 From: Koen Pauwels Date: Fri, 4 Aug 2023 16:38:30 +0200 Subject: [PATCH 160/412] 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 663585034d..ac38f0cca4 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 class ItemServiceImpl extends DSpaceObjectServiceImpl implements It @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 class ItemServiceImpl extends DSpaceObjectServiceImpl implements It @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 class ItemServiceImpl extends DSpaceObjectServiceImpl implements It // 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 class ItemServiceImpl extends DSpaceObjectServiceImpl implements It } @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 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl implements It @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 8b93e953eb..de7644af83 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 interface ItemService 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 interface ItemService 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 interface ItemService 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 interface ItemService 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 161/412] 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 7fb8567f8c..5e16fea5ae 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 @@ public abstract class DSpaceRunnable implements R } 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 162/412] 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 ac38f0cca4..ebea2aa5b8 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 class ItemServiceImpl extends DSpaceObjectServiceImpl implements It } // 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 class ItemServiceImpl extends DSpaceObjectServiceImpl implements It 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 1847a27c7f..18e0047599 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.RelationshipTypeBuilder; 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 class ItemServiceTest extends AbstractIntegrationTestWithDatabase { .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 163/412] 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 18e0047599..16d78a8e3e 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 class ItemServiceTest extends AbstractIntegrationTestWithDatabase { } @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 class ItemServiceTest extends AbstractIntegrationTestWithDatabase { // 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 class ItemServiceTest extends AbstractIntegrationTestWithDatabase { 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 164/412] 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 16d78a8e3e..d2f4b6d851 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 class ItemServiceTest extends AbstractIntegrationTestWithDatabase { 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 class ItemServiceTest extends AbstractIntegrationTestWithDatabase { .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 class ItemServiceTest extends AbstractIntegrationTestWithDatabase { 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 class ItemServiceTest extends AbstractIntegrationTestWithDatabase { .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 165/412] 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 50e1022dbe..7bef232f04 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 @@ public class BulkAccessControl extends DSpaceRunnable 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 166/412] 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 97537befd2..70a36f278e 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.CurationTaskConfig; 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 @@ public class XmlWorkflowCuratorServiceImpl 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 167/412] 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 20c43e4bfc..546d48d430 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 class BundleServiceImpl extends DSpaceObjectServiceImpl implement 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 168/412] 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 4161bbb4d8..9044c723ff 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 @@ public class MetadataImport extends DSpaceRunnable Date: Fri, 11 Aug 2023 08:47:31 +0200 Subject: [PATCH 169/412] 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 19c79af34d..97585f5a47 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 class SolrLoggerServiceImpl implements SolrLoggerService, InitializingBea } - @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 3728318625..61b2bb6013 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 interface SolrLoggerService { */ 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 e45ce163ed..319fe437d6 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 class StatisticsClient { 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 class StatisticsClient { 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 170/412] 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 e84d9f8591..cb9a120fd0 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.commons.lang.StringUtils; 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 @@ public class GoogleAsyncEventListener extends AbstractUsageEventListener { * 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 e43e9fd820..17df839ebf 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 class GoogleAsyncEventListenerIT extends AbstractControllerIntegrationTes } @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 class GoogleAsyncEventListenerIT extends AbstractControllerIntegrationTes 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 class GoogleAsyncEventListenerIT extends AbstractControllerIntegrationTes } @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 class GoogleAsyncEventListenerIT extends AbstractControllerIntegrationTes 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 class GoogleAsyncEventListenerIT extends AbstractControllerIntegrationTes 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 89c8da9255..be11cbf033 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 171/412] 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 8d3853e8cc..d418124ea1 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 a4e580d3c5d33fbf5403dbd8ae9a51ee3d98bc09 Mon Sep 17 00:00:00 2001 From: Francesco Bacchelli Date: Tue, 22 Aug 2023 12:04:24 +0200 Subject: [PATCH 172/412] CST-11299 integration test fix --- .../org/dspace/app/rest/ExternalSourcesRestControllerIT.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 ef38e00980..f22eb94aa0 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 @@ -54,7 +54,7 @@ public class ExternalSourcesRestControllerIT extends AbstractControllerIntegrati ExternalSourceMatcher.matchExternalSource( "openAIREFunding", "openAIREFunding", false) ))) - .andExpect(jsonPath("$.page.totalElements", Matchers.is(10))); + .andExpect(jsonPath("$.page.totalElements", Matchers.is(11))); } @Test From 940183411ea6ea87aa5de9193794c553178637d4 Mon Sep 17 00:00:00 2001 From: Hrafn Malmquist Date: Tue, 22 Aug 2023 21:24:24 +0100 Subject: [PATCH 173/412] 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 664cba89fa..dd633def28 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 ee8c21cb64..45aee2227a 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 174/412] 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 0000000000..afd74a588d --- /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 5e16fea5ae..2ea0a52d6e 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.Option; 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 @@ public abstract class DSpaceRunnable implements R } 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 175/412] 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 bcb61a48ee..89a416bfa8 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 @@ public class ScriptLauncher { 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 176/412] 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 2a828cdc12..5485bb1d0c 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 @@ public class Groomer { 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 177/412] 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 50faf3f886..109835d14d 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 178/412] 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 d65447d311..400b5ecb87 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 class GenerateSitemaps { */ 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 class GenerateSitemaps { 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 class GenerateSitemaps { 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 class GenerateSitemaps { && 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 179/412] 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 808940eb7b..59cee28293 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 e67e9c56bd..83c4486f71 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.FileInputStream; 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 class DSpaceResourceResolver implements ResourceResolver { } @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 class DSpaceResourceResolver implements ResourceResolver { // 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 de76c99245..0f48824159 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 class PipelineTest { 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 180/412] 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 7822d43109..aeb15fbefa 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 181/412] 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 cb9a120fd0..c1c59acf4a 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 @@ public class GoogleAsyncEventListener extends AbstractUsageEventListener { 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 182/412] 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 4cac1da314..f2bc4f0be0 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.Map.Entry; 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 final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService 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 d9eb1dfa7e..3a3d3cc632 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 183/412] 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 3a3d3cc632..61027c5550 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 184/412] 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 657e02b58d..b08f8d5145 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 185/412] 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 e623d96079..38007908c6 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 0000000000..9fd358c72a --- /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 186/412] 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 3bd702bf80..71bb798ae3 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 class HandleDAOImpl extends AbstractHibernateDAO implements Handl @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 187/412] Fix ClassCastException (Collection cannot be cast to Item) in Handle identifier classes --- .../identifier/HandleIdentifierProvider.java | 3 +- ...dentifierProviderWithCanonicalHandles.java | 63 ++++++++++--------- 2 files changed, 33 insertions(+), 33 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 1ded40c8f8..59a1e13a21 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 class HandleIdentifierProvider extends IdentifierProvider { 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 78ad6b7b79..bfa3313199 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 class VersionedHandleIdentifierProviderWithCanonicalHandles extends Ident @Override 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 + VersionHistory itemHistory = getHistory(context, identifier); + if (!identifier.matches(".*/.*\\.\\d+") && itemHistory != null) { - Item item = (Item) dso; + 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 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 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 - createNewIdentifier(context, dso, identifier); - if (dso instanceof Item) { + } else { + // A regular handle to create for an Item + createNewIdentifier(context, dso, identifier); modifyHandleMetadata(context, item, getCanonical(identifier)); } + } else { + // Handle being registered for a different type of object (e.g. Collection or Community) + createNewIdentifier(context, dso, 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 188/412] 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 bfa3313199..e6a092c472 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 class VersionedHandleIdentifierProviderWithCanonicalHandles extends Ident 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 189/412] 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 4ff1f31344..bf1c7da4e1 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 class IndexEventConsumer implements Consumer { 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 190/412] 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 9b5227491b..05f0757060 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 @@ public class DefaultAccessStatusHelper implements AccessStatusHelper { @Override public String getEmbargoFromItem(Context context, Item item) throws SQLException { - Date embargoDate; + Date embargoedDate; if (item == null) { return null; @@ -202,15 +202,15 @@ public class DefaultAccessStatusHelper implements AccessStatusHelper { 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 @@ public class DefaultAccessStatusHelper implements AccessStatusHelper { 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 191/412] 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 80eb67a2b9..35bef8c8d7 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.ByteArrayOutputStream; 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.apache.logging.log4j.Logger; 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.content.service.RelationshipService; 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 @@ public class ItemUtils { 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 @@ public class ItemUtils { 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 192/412] 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 b3af072a32..4d70286e79 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 @@ public class Curation extends DSpaceRunnable { 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 05c7a8d999..f45f4a17b6 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.sql.SQLException; 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 class XmlWorkflowCuratorServiceImpl 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 193/412] added authorization check for license bitstream in OAI import --- .../java/org/dspace/xoai/util/ItemUtils.java | 22 ++++++++++++++----- 1 file changed, 16 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 35bef8c8d7..938cf0d64a 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.apache.logging.log4j.Logger; 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 @@ public class ItemUtils { 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 194/412] 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 e27a3ee947..4f842b8e94 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 @@ public class XOAI { 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 class XOAI { } 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 class XOAI { 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 class XOAI { 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 @@ public class XOAI { 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 195/412] 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 d847e907b4..2d782dc3b8 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 interface AccessStatusHelper { * * @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 1d4dc6088c..01b3707479 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 class AccessStatusServiceImpl implements AccessStatusService { @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 05f0757060..5f0e6d8b25 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 @@ public class DefaultAccessStatusHelper implements AccessStatusHelper { * @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 class DefaultAccessStatusHelper implements AccessStatusHelper { 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 c97349ac7c..51291ee985 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 class DefaultAccessStatusHelperTest extends AbstractUnitTest { 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 class DefaultAccessStatusHelperTest extends AbstractUnitTest { 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 class DefaultAccessStatusHelperTest extends AbstractUnitTest { 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 196/412] 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 e623d96079..d67c9ae1d8 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 197/412] 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 9fe9adf944..5f9b266999 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 198/412] 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 5f9b266999..5d02d0db81 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 199/412] 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 d67c9ae1d8..a9aa161e9e 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 200/412] 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 59a1e13a21..82358362da 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 class HandleIdentifierProvider extends IdentifierProvider { 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 e6a092c472..9993f78b4d 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 class VersionedHandleIdentifierProviderWithCanonicalHandles extends Ident 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 201/412] 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 6b2dd705ea..0000000000 --- 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 202/412] 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 7c0694c52f..8a60867f9e 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 class RequestItemRepository 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 203/412] 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 8a60867f9e..bc276d73d5 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 javax.servlet.http.HttpServletRequest; 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 f821c3a628944623a12289d53a7462aae1655781 Mon Sep 17 00:00:00 2001 From: Agustina Martinez Date: Tue, 3 Oct 2023 10:53:54 +0200 Subject: [PATCH 204/412] Update InstallItemServiceImpl.java Remove setting dc.date.available --- .../org/dspace/content/InstallItemServiceImpl.java | 10 ---------- 1 file changed, 10 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 32c5b92c60..db65c40cba 100644 --- a/dspace-api/src/main/java/org/dspace/content/InstallItemServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/InstallItemServiceImpl.java @@ -150,7 +150,6 @@ public class InstallItemServiceImpl implements InstallItemService { return finishItem(c, item, is); } - protected void populateMetadata(Context c, Item item) throws SQLException, AuthorizeException { // create accession date @@ -158,15 +157,6 @@ public class InstallItemServiceImpl implements InstallItemService { itemService.addMetadata(c, item, MetadataSchemaEnum.DC.getName(), "date", "accessioned", null, now.toString()); - // add date available if not under embargo, otherwise it will - // be set when the embargo is lifted. - // this will flush out fatal embargo metadata - // problems before we set inArchive. - if (embargoService.getEmbargoTermsAsDate(c, item) == null) { - itemService.addMetadata(c, item, MetadataSchemaEnum.DC.getName(), - "date", "available", null, now.toString()); - } - // If issue date is set as "today" (literal string), then set it to current date // In the below loop, we temporarily clear all issued dates and re-add, one-by-one, // replacing "today" with today's date. 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 205/412] 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 6c62c3dc91..e5cbdb6ff2 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 class Bundle extends DSpaceObject implements DSpaceObjectLegacySupport { * 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 4ff35f5b4d..13b943b4d6 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 class BundleTest extends AbstractDSpaceObjectTest { } + /** + * 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 927d345ebe5ae263dfabb84988c9f1742bd2f725 Mon Sep 17 00:00:00 2001 From: Agustina Martinez Date: Thu, 5 Oct 2023 17:48:19 +0100 Subject: [PATCH 206/412] Fix integration test count number of fields --- .../java/org/dspace/app/rest/RelationshipRestRepositoryIT.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RelationshipRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RelationshipRestRepositoryIT.java index d8e53c770c..0ff6fe04e9 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RelationshipRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RelationshipRestRepositoryIT.java @@ -1035,7 +1035,7 @@ public class RelationshipRestRepositoryIT extends AbstractEntityIntegrationTest list = itemService.getMetadata(publication1, "dc", "contributor", Item.ANY, Item.ANY); assertEquals(10, list.size()); //same size as authors list = itemService.getMetadata(publication1, "dc", Item.ANY, Item.ANY, Item.ANY); - assertEquals(16, list.size()); //also includes title, 4 date fields, uri + assertEquals(15, list.size()); //also includes title, 3 date fields, uri list = itemService.getMetadata(publication1, Item.ANY, Item.ANY, Item.ANY, Item.ANY); // also includes type, 3 relation.isAuthorOfPublication and 3 relation.isAuthorOfPublication.latestForDiscovery // values 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 207/412] #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 da7910da29..51292fd477 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 class XmlWorkflowServiceImpl implements XmlWorkflowService { //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 @@ public class XmlWorkflowServiceImpl implements XmlWorkflowService { + "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 @@ public class XmlWorkflowServiceImpl implements XmlWorkflowService { 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 208/412] 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 0000000000..86010a5c19 --- /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 bc276d73d5..863a5c4146 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.content.service.ItemService; 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 class RequestItemRepository 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 209/412] 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 86010a5c19..dfc89ca194 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 210/412] 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 dfc89ca194..0000000000 --- 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 863a5c4146..945afe16e8 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.content.service.ItemService; 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 class RequestItemRepository 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 211/412] 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 945afe16e8..f45dbee66f 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.content.service.ItemService; 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 212/412] 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 cc89cea33a..7433338ad9 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 class BitstreamServiceImpl extends DSpaceObjectServiceImpl imp @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 bc276557df..864acf1a56 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 class BitstreamRestRepositoryIT extends AbstractControllerIntegrationTest .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 213/412] 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 aeb15fbefa..6feb81c884 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 214/412] 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 3a1d75eb56..16c63c9c1a 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 215/412] 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 7433338ad9..92acce6765 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 class BitstreamServiceImpl extends DSpaceObjectServiceImpl imp @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 216/412] 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 6c32819302..511dbabd47 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 217/412] 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 77532249ad..fc64b66e8a 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 class ItemConverter * 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 class ItemConverter 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 218/412] 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 08463fb222..62917b2cde 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 class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { 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 219/412] 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 4ff1f31344..d670f5b339 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 class IndexEventConsumer implements Consumer { @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 class IndexEventConsumer implements Consumer { 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 220/412] 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 82b39dd2df..6382e72430 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 class Context implements AutoCloseable { 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 cb5825eec1..66e4a65dbf 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 3321e4d837..b371af80ee 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 class HibernateDBConnection implements DBConnection { } } } + + /** + * 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 d670f5b339..fd255e9ffc 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 class IndexEventConsumer implements Consumer { @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 class IndexEventConsumer implements Consumer { 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 221/412] 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 6382e72430..09b9c4a32d 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 class Context implements AutoCloseable { 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 class Context implements AutoCloseable { 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 fd255e9ffc..847e235fa9 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 class IndexEventConsumer implements Consumer { 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 222/412] 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 62917b2cde..b25b323fd1 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.matchMetadata; 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 class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { } + @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 223/412] 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 0000000000..f689551f1a --- /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 224/412] 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 4161bbb4d8..983843a1dc 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 class MetadataImport extends DSpaceRunnable Date: Mon, 16 Oct 2023 10:46:32 +0200 Subject: [PATCH 225/412] 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 5c3f0fac73..0000000000 --- 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 68359023e3..b85746c64c 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 @@ public class QAEventMatcher { 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 d9a6fba962..da5080d589 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 226/412] 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 51292fd477..bc91a1fd92 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 @@ public class XmlWorkflowServiceImpl implements XmlWorkflowService { + "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 227/412] [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 274779e928..500ee04a97 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 interface AuthenticationMethod { 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 a9449b87d4..1d67da37ec 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 class AuthenticationServiceImpl implements AuthenticationService { 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 3b23660344..0c2be211a5 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 class IPAuthentication implements AuthenticationMethod { 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 228/412] [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 69e70dbb08..1da807ad71 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 class AuthenticationRestControllerIT extends AbstractControllerIntegratio } } + @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 229/412] 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 cc89cea33a..77f10880ea 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 class BitstreamServiceImpl extends DSpaceObjectServiceImpl imp //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 230/412] 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 0000000000..b1739dbd96 --- /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 231/412] 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 77f10880ea..5391dcf389 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 class BitstreamServiceImpl extends DSpaceObjectServiceImpl imp //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 232/412] 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 13b943b4d6..851d8267ea 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 class BundleTest extends AbstractDSpaceObjectTest { 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 233/412] 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 b1739dbd96..c97d224657 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 234/412] 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 5391dcf389..2746812c1c 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 class BitstreamServiceImpl extends DSpaceObjectServiceImpl imp 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 851d8267ea..4af64b81cb 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 class BundleTest extends AbstractDSpaceObjectTest { * 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 235/412] 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 92acce6765..1653266056 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 class BitstreamServiceImpl extends DSpaceObjectServiceImpl imp @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 class BitstreamServiceImpl extends DSpaceObjectServiceImpl imp 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 e7063fda40c3ca2293d2d5957d7986bb2e081d82 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Thu, 19 Oct 2023 23:35:20 +0200 Subject: [PATCH 236/412] [CST-12042] added logic layer to support patch operation for primary bitstrim --- .../app/rest/model/PrimaryBitstreamDTO.java | 27 ++++++ .../app/rest/model/step/DataUpload.java | 15 +++ .../app/rest/submit/DataProcessingStep.java | 1 + ...tstreamMetadataValueAddPatchOperation.java | 2 +- .../PrimaryBitstreamAddPatchOperation.java | 94 +++++++++++++++++++ .../PrimaryBitstreamRemovePatchOperation.java | 51 ++++++++++ ...PrimaryBitstreamReplacePatchOperation.java | 94 +++++++++++++++++++ .../app/rest/submit/step/UploadStep.java | 14 ++- .../spring/spring-dspace-core-services.xml | 66 ++++++------- 9 files changed, 323 insertions(+), 41 deletions(-) create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/model/PrimaryBitstreamDTO.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/PrimaryBitstreamAddPatchOperation.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/PrimaryBitstreamRemovePatchOperation.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/PrimaryBitstreamReplacePatchOperation.java diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/PrimaryBitstreamDTO.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/PrimaryBitstreamDTO.java new file mode 100644 index 0000000000..d0a64ee217 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/PrimaryBitstreamDTO.java @@ -0,0 +1,27 @@ +/** + * The contents of this file are subject to the license and 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.UUID; + +/** + * @author Mykhaylo Boychuk (mykhaylo.boychuk@4science.com) + */ +public class PrimaryBitstreamDTO { + + private UUID primary; + + public UUID getPrimary() { + return primary; + } + + public void setPrimary(UUID primary) { + this.primary = primary; + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/step/DataUpload.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/step/DataUpload.java index d1cbdeb4b4..a28a5f3ad3 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/step/DataUpload.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/step/DataUpload.java @@ -9,6 +9,7 @@ package org.dspace.app.rest.model.step; import java.util.ArrayList; import java.util.List; +import java.util.UUID; import com.fasterxml.jackson.annotation.JsonUnwrapped; @@ -19,6 +20,11 @@ import com.fasterxml.jackson.annotation.JsonUnwrapped; */ public class DataUpload implements SectionData { + /* + * primary bitstream uuid + */ + private UUID primary; + @JsonUnwrapped private List files; @@ -32,4 +38,13 @@ public class DataUpload implements SectionData { public void setFiles(List files) { this.files = files; } + + public UUID getPrimary() { + return primary; + } + + public void setPrimary(UUID primary) { + this.primary = primary; + } + } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/DataProcessingStep.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/DataProcessingStep.java index 99af309cdb..c6f08c85b3 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/DataProcessingStep.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/DataProcessingStep.java @@ -39,6 +39,7 @@ public interface DataProcessingStep extends RestProcessingStep { public static final String ACCESS_CONDITION_STEP_OPERATION_ENTRY = "discoverable"; public static final String ACCESS_CONDITION_POLICY_STEP_OPERATION_ENTRY = "accessConditions"; public static final String SHOW_IDENTIFIERS_ENTRY = "identifiers"; + public static final String PRIMARY_FLAG_ENTRY = "primary"; public static final String UPLOAD_STEP_METADATA_PATH = "metadata"; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/BitstreamMetadataValueAddPatchOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/BitstreamMetadataValueAddPatchOperation.java index 38ec37e7d7..f9ef16fa58 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/BitstreamMetadataValueAddPatchOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/BitstreamMetadataValueAddPatchOperation.java @@ -53,7 +53,7 @@ public class BitstreamMetadataValueAddPatchOperation extends MetadataValueAddPat bitstreamMetadataValuePathUtils.validate(absolutePath); Item item = source.getItem(); List bundle = itemService.getBundles(item, Constants.CONTENT_BUNDLE_NAME); - ; + for (Bundle bb : bundle) { int idx = 0; for (Bitstream b : bb.getBitstreams()) { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/PrimaryBitstreamAddPatchOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/PrimaryBitstreamAddPatchOperation.java new file mode 100644 index 0000000000..6f32ec979d --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/PrimaryBitstreamAddPatchOperation.java @@ -0,0 +1,94 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.submit.factory.impl; + +import static org.dspace.core.Constants.CONTENT_BUNDLE_NAME; + +import java.util.List; +import java.util.Objects; +import java.util.UUID; +import javax.servlet.http.HttpServletRequest; + +import org.dspace.app.rest.exception.UnprocessableEntityException; +import org.dspace.app.rest.model.PrimaryBitstreamDTO; +import org.dspace.app.rest.model.patch.LateObjectEvaluator; +import org.dspace.content.Bitstream; +import org.dspace.content.Bundle; +import org.dspace.content.InProgressSubmission; +import org.dspace.content.Item; +import org.dspace.content.service.ItemService; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.rest.webmvc.json.patch.PatchException; + +/** + * Submission "add" operation to set primary bitstream. + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk@4science.com) + */ +public class PrimaryBitstreamAddPatchOperation extends AddPatchOperation { + + @Autowired + private ItemService itemService; + + @Override + void add(Context context, HttpServletRequest currentRequest, InProgressSubmission source, String path, Object value) + throws Exception { + Item item = source.getItem(); + UUID primaryUUID = parseValue(value); + List bundles = itemService.getBundles(item, CONTENT_BUNDLE_NAME); + Bundle currentPrimaryBundle = bundles.stream() + .filter(bundle -> Objects.nonNull(bundle.getPrimaryBitstream())) + .findFirst() + .orElse(null); + + Bitstream primaryBitstreamToAdd = null; + for (Bundle bundle : bundles) { + primaryBitstreamToAdd = bundle.getBitstreams().stream() + .filter(b -> b.getID().equals(primaryUUID)) + .findFirst() + .orElse(null); + if (Objects.nonNull(primaryBitstreamToAdd)) { + if (Objects.nonNull(currentPrimaryBundle)) { + currentPrimaryBundle.setPrimaryBitstreamID(null); + } + bundle.setPrimaryBitstreamID(primaryBitstreamToAdd); + break; + } + } + + if (Objects.isNull(primaryBitstreamToAdd)) { + throw new UnprocessableEntityException("The provided uuid: " + primaryUUID + + " of bitstream to set as primary doesn't match any bitstream!"); + } + } + + private UUID parseValue(Object value) { + PrimaryBitstreamDTO primaryBitstreamDTO = null; + try { + primaryBitstreamDTO = evaluateSingleObject((LateObjectEvaluator) value); + } catch (PatchException e) { + throw new UnprocessableEntityException("The provided value is invalid!", e); + } + if (Objects.isNull(primaryBitstreamDTO) || Objects.isNull(primaryBitstreamDTO.getPrimary())) { + throw new UnprocessableEntityException("The provided value is invalid!" + value); + } + return primaryBitstreamDTO.getPrimary(); + } + + @Override + protected Class getArrayClassForEvaluation() { + return PrimaryBitstreamDTO[].class; + } + + @Override + protected Class getClassForEvaluation() { + return PrimaryBitstreamDTO.class; + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/PrimaryBitstreamRemovePatchOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/PrimaryBitstreamRemovePatchOperation.java new file mode 100644 index 0000000000..eb204a2b08 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/PrimaryBitstreamRemovePatchOperation.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.submit.factory.impl; + +import static org.dspace.core.Constants.CONTENT_BUNDLE_NAME; + +import java.util.List; +import javax.servlet.http.HttpServletRequest; + +import org.dspace.app.rest.model.PrimaryBitstreamDTO; +import org.dspace.content.Bundle; +import org.dspace.content.InProgressSubmission; +import org.dspace.content.Item; +import org.dspace.content.service.ItemService; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Submission "remove" operation to remove primary bitstream. + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk@4science.com) + */ +public class PrimaryBitstreamRemovePatchOperation extends RemovePatchOperation { + + @Autowired + private ItemService itemService; + + @Override + void remove(Context context, HttpServletRequest request, InProgressSubmission source, String path, Object value) + throws Exception { + Item item = source.getItem(); + List bundles = itemService.getBundles(item, CONTENT_BUNDLE_NAME); + bundles.forEach(b -> b.setPrimaryBitstreamID(null)); + } + + @Override + protected Class getArrayClassForEvaluation() { + return PrimaryBitstreamDTO[].class; + } + + @Override + protected Class getClassForEvaluation() { + return PrimaryBitstreamDTO.class; + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/PrimaryBitstreamReplacePatchOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/PrimaryBitstreamReplacePatchOperation.java new file mode 100644 index 0000000000..beea2194c2 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/PrimaryBitstreamReplacePatchOperation.java @@ -0,0 +1,94 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.submit.factory.impl; + +import static org.dspace.core.Constants.CONTENT_BUNDLE_NAME; + +import java.util.List; +import java.util.Objects; +import java.util.UUID; +import javax.servlet.http.HttpServletRequest; + +import org.dspace.app.rest.exception.UnprocessableEntityException; +import org.dspace.app.rest.model.PrimaryBitstreamDTO; +import org.dspace.app.rest.model.patch.LateObjectEvaluator; +import org.dspace.content.Bitstream; +import org.dspace.content.Bundle; +import org.dspace.content.InProgressSubmission; +import org.dspace.content.Item; +import org.dspace.content.service.ItemService; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.rest.webmvc.json.patch.PatchException; + +/** + * Submission "replace" operation to replace primary bitstream. + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk@4science.com) + */ +public class PrimaryBitstreamReplacePatchOperation extends ReplacePatchOperation { + + private final String EX_MESSAGE = "It is impossible to replace primary bitstrem if it wasn't set!"; + + @Autowired + private ItemService itemService; + + @Override + void replace(Context context, HttpServletRequest request, InProgressSubmission source, String path, Object value) + throws Exception { + Item item = source.getItem(); + UUID primaryUUID = parseValue(value); + List bundles = itemService.getBundles(item, CONTENT_BUNDLE_NAME); + Bundle currentPrimaryBundle = bundles.stream() + .filter(bundle -> Objects.nonNull(bundle.getPrimaryBitstream())) + .findFirst() + .orElseThrow(() -> new UnprocessableEntityException(EX_MESSAGE)); + + Bitstream primaryBitstream = null; + for (Bundle bundle : bundles) { + primaryBitstream = bundle.getBitstreams().stream() + .filter(b -> b.getID().equals(primaryUUID)) + .findFirst() + .orElse(null); + if (Objects.nonNull(primaryBitstream)) { + currentPrimaryBundle.setPrimaryBitstreamID(null); + bundle.setPrimaryBitstreamID(primaryBitstream); + break; + } + } + + if (Objects.isNull(primaryBitstream)) { + throw new UnprocessableEntityException("The provided uuid: " + primaryUUID + + " of bitstream to set as primary doesn't match any bitstream!"); + } + } + + private UUID parseValue(Object value) { + PrimaryBitstreamDTO primaryBitstreamDTO = null; + try { + primaryBitstreamDTO = evaluateSingleObject((LateObjectEvaluator) value); + } catch (PatchException e) { + throw new UnprocessableEntityException("The provided value is invalid!", e); + } + if (Objects.isNull(primaryBitstreamDTO) || Objects.isNull(primaryBitstreamDTO.getPrimary())) { + throw new UnprocessableEntityException("The provided value is invalid!" + value); + } + return primaryBitstreamDTO.getPrimary(); + } + + @Override + protected Class getArrayClassForEvaluation() { + return PrimaryBitstreamDTO[].class; + } + + @Override + protected Class getClassForEvaluation() { + return PrimaryBitstreamDTO.class; + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/UploadStep.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/UploadStep.java index b91916ca31..57913160d3 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/UploadStep.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/UploadStep.java @@ -10,8 +10,10 @@ package org.dspace.app.rest.submit.step; import java.io.BufferedInputStream; import java.io.InputStream; import java.util.List; +import java.util.Objects; import javax.servlet.http.HttpServletRequest; +import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.ErrorRest; @@ -56,8 +58,12 @@ public class UploadStep extends AbstractProcessingStep List bundles = itemService.getBundles(obj.getItem(), Constants.CONTENT_BUNDLE_NAME); for (Bundle bundle : bundles) { for (Bitstream source : bundle.getBitstreams()) { + Bitstream primaryBitstream = bundle.getPrimaryBitstream(); UploadBitstreamRest b = submissionService.buildUploadBitstream(configurationService, source); result.getFiles().add(b); + if (Objects.nonNull(primaryBitstream)) { + result.setPrimary(primaryBitstream.getID()); + } } } return result; @@ -67,12 +73,14 @@ public class UploadStep extends AbstractProcessingStep public void doPatchProcessing(Context context, HttpServletRequest currentRequest, InProgressSubmission source, Operation op, SubmissionStepConfig stepConf) throws Exception { - String instance = null; + String instance = StringUtils.EMPTY; if ("remove".equals(op.getOp())) { if (op.getPath().contains(UPLOAD_STEP_METADATA_PATH)) { instance = UPLOAD_STEP_METADATA_OPERATION_ENTRY; } else if (op.getPath().contains(UPLOAD_STEP_ACCESSCONDITIONS_OPERATION_ENTRY)) { instance = stepConf.getType() + "." + UPLOAD_STEP_ACCESSCONDITIONS_OPERATION_ENTRY; + } else if (op.getPath().contains(PRIMARY_FLAG_ENTRY)) { + instance = PRIMARY_FLAG_ENTRY; } else { instance = UPLOAD_STEP_REMOVE_OPERATION_ENTRY; } @@ -87,9 +95,11 @@ public class UploadStep extends AbstractProcessingStep instance = stepConf.getType() + "." + UPLOAD_STEP_ACCESSCONDITIONS_OPERATION_ENTRY; } else if (op.getPath().contains(UPLOAD_STEP_METADATA_PATH)) { instance = UPLOAD_STEP_METADATA_OPERATION_ENTRY; + } else if (op.getPath().contains(PRIMARY_FLAG_ENTRY)) { + instance = PRIMARY_FLAG_ENTRY; } } - if (instance == null) { + if (StringUtils.isBlank(instance)) { throw new UnprocessableEntityException("The path " + op.getPath() + " is not supported by the operation " + op.getOp()); } diff --git a/dspace-server-webapp/src/main/resources/spring/spring-dspace-core-services.xml b/dspace-server-webapp/src/main/resources/spring/spring-dspace-core-services.xml index bb56393d0b..7238e39967 100644 --- a/dspace-server-webapp/src/main/resources/spring/spring-dspace-core-services.xml +++ b/dspace-server-webapp/src/main/resources/spring/spring-dspace-core-services.xml @@ -20,18 +20,15 @@ - + - + - +
    @@ -39,90 +36,80 @@ - + - + - + - + - + + + +
    - + - + - + - + - + - + + + + - + - + - + - + - + @@ -130,6 +117,9 @@ + + +
    From 2d40aafd47d6127ba1f917d1505a834d2aea3925 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Thu, 19 Oct 2023 23:36:04 +0200 Subject: [PATCH 237/412] [CST-12042] added tests for patch operations of primary bitstream --- .../rest/WorkspaceItemRestRepositoryIT.java | 445 +++++++++++++++++- 1 file changed, 444 insertions(+), 1 deletion(-) 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 8b2f3f093a..5e84ab4f74 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 @@ -8601,6 +8601,449 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration .content("/api/submission/workspaceitems/" + workspaceItem.getID()) .contentType(textUriContentType)) .andExpect(status().isCreated()); - } + + @Test + public void patchAddPrimaryTest() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + InputStream pdf = getClass().getResourceAsStream("simple-article.pdf"); + + WorkspaceItem witem = WorkspaceItemBuilder.createWorkspaceItem(context, col1) + .withTitle("TITLE test") + .withIssueDate("2023-10-18") + .withFulltext("simple-article.pdf", + "/local/path/simple-article.pdf", pdf) + .withSubject("testEntry") + .build(); + + context.restoreAuthSystemState(); + + AtomicReference idRef = new AtomicReference(); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.upload.primary", nullValue())) + .andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), + "$.sections.upload.files[0].uuid"))); + + List addPrimaryOps = new ArrayList(); + Map primaryUUID = new HashMap(); + primaryUUID.put("primary", idRef.get()); + addPrimaryOps.add(new AddOperation("/sections/upload/primary", primaryUUID)); + + String patchBody = getPatchContent(addPrimaryOps); + getClient(tokenAdmin).perform(patch("/api/submission/workspaceitems/" + witem.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()); + + getClient(tokenAdmin).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.upload.primary", is(idRef.get()))); + } + + @Test + public void patchAddPrimaryUpdateAlredySettedOneTest() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + InputStream pdf = getClass().getResourceAsStream("simple-article.pdf"); + InputStream pdf2 = getClass().getResourceAsStream("bibtex-test.bib"); + + WorkspaceItem witem = WorkspaceItemBuilder.createWorkspaceItem(context, col1) + .withTitle("TITLE test") + .withIssueDate("2023-10-18") + .withFulltext("simple-article.pdf", + "/local/path/simple-article.pdf", pdf) + .withFulltext("bibtex-test.bib", + "/local/path/bibtex-test.bib", pdf2) + .withSubject("testEntry") + .build(); + + context.restoreAuthSystemState(); + + AtomicReference idFirstPdf = new AtomicReference(); + AtomicReference idSecondPdf = new AtomicReference(); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.upload.primary", nullValue())) + .andDo(result -> idFirstPdf.set(read(result.getResponse().getContentAsString(), + "$.sections.upload.files[0].uuid"))) + .andDo(result -> idSecondPdf.set(read(result.getResponse().getContentAsString(), + "$.sections.upload.files[1].uuid"))); + + List addPrimaryOps = new ArrayList(); + Map primaryUUID = new HashMap(); + primaryUUID.put("primary", idFirstPdf.get()); + addPrimaryOps.add(new AddOperation("/sections/upload/primary", primaryUUID)); + + String patchBody = getPatchContent(addPrimaryOps); + getClient(tokenAdmin).perform(patch("/api/submission/workspaceitems/" + witem.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()); + + getClient(tokenAdmin).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.upload.primary", is(idFirstPdf.get()))); + + List addPrimaryOps2 = new ArrayList(); + Map primaryUUID2 = new HashMap(); + primaryUUID2.put("primary", idSecondPdf.get()); + addPrimaryOps2.add(new AddOperation("/sections/upload/primary", primaryUUID2)); + + getClient(tokenAdmin).perform(patch("/api/submission/workspaceitems/" + witem.getID()) + .content(getPatchContent(addPrimaryOps2)) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()); + + getClient(tokenAdmin).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.upload.primary", is(idSecondPdf.get()))); + } + + @Test + public void patchAddPrimaryUUIDofNotExistingBitstreamTest() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + InputStream pdf = getClass().getResourceAsStream("simple-article.pdf"); + + WorkspaceItem witem = WorkspaceItemBuilder.createWorkspaceItem(context, col1) + .withTitle("TITLE test") + .withIssueDate("2023-10-18") + .withFulltext("simple-article.pdf", + "/local/path/simple-article.pdf", pdf) + .withSubject("testEntry") + .build(); + + context.restoreAuthSystemState(); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.upload.primary", nullValue())); + + List addPrimaryOps = new ArrayList(); + Map primaryUUID = new HashMap(); + primaryUUID.put("primary", UUID.randomUUID().toString()); + addPrimaryOps.add(new AddOperation("/sections/upload/primary", primaryUUID)); + + String patchBody = getPatchContent(addPrimaryOps); + getClient(tokenAdmin).perform(patch("/api/submission/workspaceitems/" + witem.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isUnprocessableEntity()); + + getClient(tokenAdmin).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.upload.primary", nullValue())); + } + + @Test + public void patchAddPrimaryWrongUUIDTest() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + InputStream pdf = getClass().getResourceAsStream("simple-article.pdf"); + + WorkspaceItem witem = WorkspaceItemBuilder.createWorkspaceItem(context, col1) + .withTitle("TITLE test") + .withIssueDate("2023-10-18") + .withFulltext("simple-article.pdf", + "/local/path/simple-article.pdf", pdf) + .withSubject("testEntry") + .build(); + + context.restoreAuthSystemState(); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.upload.primary", nullValue())); + + List addPrimaryOps = new ArrayList(); + Map primaryUUID = new HashMap(); + primaryUUID.put("primary", "wrong-uuid"); + addPrimaryOps.add(new AddOperation("/sections/upload/primary", primaryUUID)); + + String patchBody = getPatchContent(addPrimaryOps); + getClient(tokenAdmin).perform(patch("/api/submission/workspaceitems/" + witem.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isUnprocessableEntity()); + + getClient(tokenAdmin).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.upload.primary", nullValue())); + } + + @Test + public void patchRemovePrimaryTest() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + InputStream pdf = getClass().getResourceAsStream("simple-article.pdf"); + + WorkspaceItem witem = WorkspaceItemBuilder.createWorkspaceItem(context, col1) + .withTitle("TITLE test") + .withIssueDate("2023-10-18") + .withFulltext("simple-article.pdf", + "/local/path/simple-article.pdf", pdf) + .withSubject("testEntry") + .build(); + + context.restoreAuthSystemState(); + + AtomicReference idRef = new AtomicReference(); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.upload.primary", nullValue())) + .andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), + "$.sections.upload.files[0].uuid"))); + + List addOperations = new ArrayList(); + Map primaryUUID = new HashMap(); + primaryUUID.put("primary", idRef.get()); + addOperations.add(new AddOperation("/sections/upload/primary", primaryUUID)); + + getClient(tokenAdmin).perform(patch("/api/submission/workspaceitems/" + witem.getID()) + .content(getPatchContent(addOperations)) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()); + + getClient(tokenAdmin).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.upload.primary", is(idRef.get()))); + + List removeOps = new ArrayList(); + removeOps.add(new RemoveOperation("/sections/upload/primary")); + + getClient(tokenAdmin).perform(patch("/api/submission/workspaceitems/" + witem.getID()) + .content(getPatchContent(removeOps)) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()); + + getClient(tokenAdmin).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.upload.primary", nullValue())); + } + + @Test + public void patchReplacePrimaryTest() throws Exception { + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + InputStream pdf = getClass().getResourceAsStream("simple-article.pdf"); + InputStream pdf2 = getClass().getResourceAsStream("bibtex-test.bib"); + + WorkspaceItem witem = WorkspaceItemBuilder.createWorkspaceItem(context, col1) + .withTitle("TITLE test") + .withIssueDate("2023-10-18") + .withFulltext("simple-article.pdf", + "/local/path/simple-article.pdf", pdf) + .withFulltext("bibtex-test.bib", + "/local/path/bibtex-test.bib", pdf2) + .withSubject("testEntry") + .build(); + + context.restoreAuthSystemState(); + + AtomicReference idFirstPdf = new AtomicReference(); + AtomicReference idSecondPdf = new AtomicReference(); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.upload.primary", nullValue())) + .andDo(result -> idFirstPdf.set(read(result.getResponse().getContentAsString(), + "$.sections.upload.files[0].uuid"))) + .andDo(result -> idSecondPdf.set(read(result.getResponse().getContentAsString(), + "$.sections.upload.files[1].uuid"))); + + List addOperations = new ArrayList(); + Map primaryUUID = new HashMap(); + primaryUUID.put("primary", idFirstPdf.get()); + addOperations.add(new AddOperation("/sections/upload/primary", primaryUUID)); + + getClient(tokenAdmin).perform(patch("/api/submission/workspaceitems/" + witem.getID()) + .content(getPatchContent(addOperations)) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()); + + getClient(tokenAdmin).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.upload.primary", is(idFirstPdf.get()))); + + List replaceOperations = new ArrayList(); + Map primaryUUID2 = new HashMap(); + primaryUUID2.put("primary", idSecondPdf.get()); + replaceOperations.add(new ReplaceOperation("/sections/upload/primary", primaryUUID2)); + + getClient(tokenAdmin).perform(patch("/api/submission/workspaceitems/" + witem.getID()) + .content(getPatchContent(replaceOperations)) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()); + + getClient(tokenAdmin).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.upload.primary", is(idSecondPdf.get()))); + } + + @Test + public void patchReplacePrimaryWhenPrimariIsUnsetTest() throws Exception { + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + InputStream pdf = getClass().getResourceAsStream("simple-article.pdf"); + InputStream pdf2 = getClass().getResourceAsStream("bibtex-test.bib"); + + WorkspaceItem witem = WorkspaceItemBuilder.createWorkspaceItem(context, col1) + .withTitle("TITLE test") + .withIssueDate("2023-10-18") + .withFulltext("simple-article.pdf", + "/local/path/simple-article.pdf", pdf) + .withSubject("testEntry") + .build(); + + context.restoreAuthSystemState(); + + AtomicReference idPdf = new AtomicReference(); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.upload.primary", nullValue())) + .andDo(result -> idPdf.set(read(result.getResponse().getContentAsString(), + "$.sections.upload.files[0].uuid"))); + + List replaceOperations = new ArrayList(); + Map primaryUUID2 = new HashMap(); + primaryUUID2.put("primary", idPdf.get()); + replaceOperations.add(new ReplaceOperation("/sections/upload/primary", primaryUUID2)); + + getClient(tokenAdmin).perform(patch("/api/submission/workspaceitems/" + witem.getID()) + .content(getPatchContent(replaceOperations)) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isUnprocessableEntity()); + + getClient(tokenAdmin).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.upload.primary", nullValue())); + } + + @Test + public void patchReplaceProvidingWrongPrimaryTest() throws Exception { + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + InputStream pdf = getClass().getResourceAsStream("simple-article.pdf"); + + WorkspaceItem witem = WorkspaceItemBuilder.createWorkspaceItem(context, col1) + .withTitle("TITLE test") + .withIssueDate("2023-10-18") + .withFulltext("simple-article.pdf", + "/local/path/simple-article.pdf", pdf) + .build(); + + context.restoreAuthSystemState(); + + AtomicReference idFirstPdf = new AtomicReference(); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.upload.primary", nullValue())) + .andDo(result -> idFirstPdf.set(read(result.getResponse().getContentAsString(), + "$.sections.upload.files[0].uuid"))); + + List addOperations = new ArrayList(); + Map primaryUUID = new HashMap(); + primaryUUID.put("primary", idFirstPdf.get()); + addOperations.add(new AddOperation("/sections/upload/primary", primaryUUID)); + + getClient(tokenAdmin).perform(patch("/api/submission/workspaceitems/" + witem.getID()) + .content(getPatchContent(addOperations)) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()); + + getClient(tokenAdmin).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.upload.primary", is(idFirstPdf.get()))); + + List replaceOperations = new ArrayList(); + Map primaryUUID2 = new HashMap(); + primaryUUID2.put("primary", UUID.randomUUID().toString()); + replaceOperations.add(new ReplaceOperation("/sections/upload/primary", primaryUUID2)); + + getClient(tokenAdmin).perform(patch("/api/submission/workspaceitems/" + witem.getID()) + .content(getPatchContent(replaceOperations)) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isUnprocessableEntity()); + + getClient(tokenAdmin).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.upload.primary", is(idFirstPdf.get()))); + } + } 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 238/412] 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 546cbf01f3..547be787e4 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -769,7 +769,7 @@ org.json json - 20230227 + 20231013 From 3b81727d63cc812e1122b1249b6d4fe4ae005730 Mon Sep 17 00:00:00 2001 From: Mykhaylo Boychuk Date: Fri, 20 Oct 2023 17:01:01 +0200 Subject: [PATCH 239/412] [CST-12042] fix parsing of patch value --- .../app/rest/model/PrimaryBitstreamDTO.java | 27 ------------------- .../PrimaryBitstreamAddPatchOperation.java | 24 +++++++---------- .../PrimaryBitstreamRemovePatchOperation.java | 11 ++++---- ...PrimaryBitstreamReplacePatchOperation.java | 24 +++++++---------- 4 files changed, 23 insertions(+), 63 deletions(-) delete mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/model/PrimaryBitstreamDTO.java diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/PrimaryBitstreamDTO.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/PrimaryBitstreamDTO.java deleted file mode 100644 index d0a64ee217..0000000000 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/PrimaryBitstreamDTO.java +++ /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/ - */ -package org.dspace.app.rest.model; - -import java.util.UUID; - -/** - * @author Mykhaylo Boychuk (mykhaylo.boychuk@4science.com) - */ -public class PrimaryBitstreamDTO { - - private UUID primary; - - public UUID getPrimary() { - return primary; - } - - public void setPrimary(UUID primary) { - this.primary = primary; - } - -} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/PrimaryBitstreamAddPatchOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/PrimaryBitstreamAddPatchOperation.java index 6f32ec979d..0e23349d76 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/PrimaryBitstreamAddPatchOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/PrimaryBitstreamAddPatchOperation.java @@ -15,8 +15,6 @@ import java.util.UUID; import javax.servlet.http.HttpServletRequest; import org.dspace.app.rest.exception.UnprocessableEntityException; -import org.dspace.app.rest.model.PrimaryBitstreamDTO; -import org.dspace.app.rest.model.patch.LateObjectEvaluator; import org.dspace.content.Bitstream; import org.dspace.content.Bundle; import org.dspace.content.InProgressSubmission; @@ -24,14 +22,13 @@ import org.dspace.content.Item; import org.dspace.content.service.ItemService; import org.dspace.core.Context; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.rest.webmvc.json.patch.PatchException; /** * Submission "add" operation to set primary bitstream. * * @author Mykhaylo Boychuk (mykhaylo.boychuk@4science.com) */ -public class PrimaryBitstreamAddPatchOperation extends AddPatchOperation { +public class PrimaryBitstreamAddPatchOperation extends AddPatchOperation { @Autowired private ItemService itemService; @@ -69,26 +66,23 @@ public class PrimaryBitstreamAddPatchOperation extends AddPatchOperation getArrayClassForEvaluation() { - return PrimaryBitstreamDTO[].class; + protected Class getArrayClassForEvaluation() { + return null; } @Override - protected Class getClassForEvaluation() { - return PrimaryBitstreamDTO.class; + protected Class getClassForEvaluation() { + return null; } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/PrimaryBitstreamRemovePatchOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/PrimaryBitstreamRemovePatchOperation.java index eb204a2b08..57b688898a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/PrimaryBitstreamRemovePatchOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/PrimaryBitstreamRemovePatchOperation.java @@ -12,7 +12,6 @@ import static org.dspace.core.Constants.CONTENT_BUNDLE_NAME; import java.util.List; import javax.servlet.http.HttpServletRequest; -import org.dspace.app.rest.model.PrimaryBitstreamDTO; import org.dspace.content.Bundle; import org.dspace.content.InProgressSubmission; import org.dspace.content.Item; @@ -25,7 +24,7 @@ import org.springframework.beans.factory.annotation.Autowired; * * @author Mykhaylo Boychuk (mykhaylo.boychuk@4science.com) */ -public class PrimaryBitstreamRemovePatchOperation extends RemovePatchOperation { +public class PrimaryBitstreamRemovePatchOperation extends RemovePatchOperation { @Autowired private ItemService itemService; @@ -39,13 +38,13 @@ public class PrimaryBitstreamRemovePatchOperation extends RemovePatchOperation

    getArrayClassForEvaluation() { - return PrimaryBitstreamDTO[].class; + protected Class getArrayClassForEvaluation() { + return null; } @Override - protected Class getClassForEvaluation() { - return PrimaryBitstreamDTO.class; + protected Class getClassForEvaluation() { + return null; } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/PrimaryBitstreamReplacePatchOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/PrimaryBitstreamReplacePatchOperation.java index beea2194c2..6572801242 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/PrimaryBitstreamReplacePatchOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/PrimaryBitstreamReplacePatchOperation.java @@ -15,8 +15,6 @@ import java.util.UUID; import javax.servlet.http.HttpServletRequest; import org.dspace.app.rest.exception.UnprocessableEntityException; -import org.dspace.app.rest.model.PrimaryBitstreamDTO; -import org.dspace.app.rest.model.patch.LateObjectEvaluator; import org.dspace.content.Bitstream; import org.dspace.content.Bundle; import org.dspace.content.InProgressSubmission; @@ -24,14 +22,13 @@ import org.dspace.content.Item; import org.dspace.content.service.ItemService; import org.dspace.core.Context; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.rest.webmvc.json.patch.PatchException; /** * Submission "replace" operation to replace primary bitstream. * * @author Mykhaylo Boychuk (mykhaylo.boychuk@4science.com) */ -public class PrimaryBitstreamReplacePatchOperation extends ReplacePatchOperation { +public class PrimaryBitstreamReplacePatchOperation extends ReplacePatchOperation { private final String EX_MESSAGE = "It is impossible to replace primary bitstrem if it wasn't set!"; @@ -69,26 +66,23 @@ public class PrimaryBitstreamReplacePatchOperation extends ReplacePatchOperation } private UUID parseValue(Object value) { - PrimaryBitstreamDTO primaryBitstreamDTO = null; + UUID primaryBitstreamUUID; try { - primaryBitstreamDTO = evaluateSingleObject((LateObjectEvaluator) value); - } catch (PatchException e) { + primaryBitstreamUUID = UUID.fromString((String) value); + } catch (Exception e) { throw new UnprocessableEntityException("The provided value is invalid!", e); } - if (Objects.isNull(primaryBitstreamDTO) || Objects.isNull(primaryBitstreamDTO.getPrimary())) { - throw new UnprocessableEntityException("The provided value is invalid!" + value); - } - return primaryBitstreamDTO.getPrimary(); + return primaryBitstreamUUID; } @Override - protected Class getArrayClassForEvaluation() { - return PrimaryBitstreamDTO[].class; + protected Class getArrayClassForEvaluation() { + return null; } @Override - protected Class getClassForEvaluation() { - return PrimaryBitstreamDTO.class; + protected Class getClassForEvaluation() { + return null; } } From c9850d8d8be0e8b75d85ea94cf2c92ffe5877f0e Mon Sep 17 00:00:00 2001 From: Mykhaylo Boychuk Date: Fri, 20 Oct 2023 17:24:13 +0200 Subject: [PATCH 240/412] [CST-12042] refactored tests --- .../rest/WorkspaceItemRestRepositoryIT.java | 52 ++++++------------- 1 file changed, 15 insertions(+), 37 deletions(-) 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 5e84ab4f74..416d9e953f 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 @@ -8637,9 +8637,7 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration "$.sections.upload.files[0].uuid"))); List addPrimaryOps = new ArrayList(); - Map primaryUUID = new HashMap(); - primaryUUID.put("primary", idRef.get()); - addPrimaryOps.add(new AddOperation("/sections/upload/primary", primaryUUID)); + addPrimaryOps.add(new AddOperation("/sections/upload/primary", idRef.get())); String patchBody = getPatchContent(addPrimaryOps); getClient(tokenAdmin).perform(patch("/api/submission/workspaceitems/" + witem.getID()) @@ -8692,9 +8690,7 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration "$.sections.upload.files[1].uuid"))); List addPrimaryOps = new ArrayList(); - Map primaryUUID = new HashMap(); - primaryUUID.put("primary", idFirstPdf.get()); - addPrimaryOps.add(new AddOperation("/sections/upload/primary", primaryUUID)); + addPrimaryOps.add(new AddOperation("/sections/upload/primary", idFirstPdf.get())); String patchBody = getPatchContent(addPrimaryOps); getClient(tokenAdmin).perform(patch("/api/submission/workspaceitems/" + witem.getID()) @@ -8707,9 +8703,7 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration .andExpect(jsonPath("$.sections.upload.primary", is(idFirstPdf.get()))); List addPrimaryOps2 = new ArrayList(); - Map primaryUUID2 = new HashMap(); - primaryUUID2.put("primary", idSecondPdf.get()); - addPrimaryOps2.add(new AddOperation("/sections/upload/primary", primaryUUID2)); + addPrimaryOps2.add(new AddOperation("/sections/upload/primary", idSecondPdf.get())); getClient(tokenAdmin).perform(patch("/api/submission/workspaceitems/" + witem.getID()) .content(getPatchContent(addPrimaryOps2)) @@ -8750,12 +8744,10 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration .andExpect(status().isOk()) .andExpect(jsonPath("$.sections.upload.primary", nullValue())); - List addPrimaryOps = new ArrayList(); - Map primaryUUID = new HashMap(); - primaryUUID.put("primary", UUID.randomUUID().toString()); - addPrimaryOps.add(new AddOperation("/sections/upload/primary", primaryUUID)); + List addOperations = new ArrayList(); + addOperations.add(new AddOperation("/sections/upload/primary", UUID.randomUUID())); - String patchBody = getPatchContent(addPrimaryOps); + String patchBody = getPatchContent(addOperations); getClient(tokenAdmin).perform(patch("/api/submission/workspaceitems/" + witem.getID()) .content(patchBody) .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) @@ -8795,12 +8787,10 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration .andExpect(status().isOk()) .andExpect(jsonPath("$.sections.upload.primary", nullValue())); - List addPrimaryOps = new ArrayList(); - Map primaryUUID = new HashMap(); - primaryUUID.put("primary", "wrong-uuid"); - addPrimaryOps.add(new AddOperation("/sections/upload/primary", primaryUUID)); + List addOperations = new ArrayList(); + addOperations.add(new AddOperation("/sections/upload/primary", "wrong-uuid")); - String patchBody = getPatchContent(addPrimaryOps); + String patchBody = getPatchContent(addOperations); getClient(tokenAdmin).perform(patch("/api/submission/workspaceitems/" + witem.getID()) .content(patchBody) .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) @@ -8845,9 +8835,7 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration "$.sections.upload.files[0].uuid"))); List addOperations = new ArrayList(); - Map primaryUUID = new HashMap(); - primaryUUID.put("primary", idRef.get()); - addOperations.add(new AddOperation("/sections/upload/primary", primaryUUID)); + addOperations.add(new AddOperation("/sections/upload/primary", idRef.get())); getClient(tokenAdmin).perform(patch("/api/submission/workspaceitems/" + witem.getID()) .content(getPatchContent(addOperations)) @@ -8910,9 +8898,7 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration "$.sections.upload.files[1].uuid"))); List addOperations = new ArrayList(); - Map primaryUUID = new HashMap(); - primaryUUID.put("primary", idFirstPdf.get()); - addOperations.add(new AddOperation("/sections/upload/primary", primaryUUID)); + addOperations.add(new AddOperation("/sections/upload/primary", idFirstPdf.get())); getClient(tokenAdmin).perform(patch("/api/submission/workspaceitems/" + witem.getID()) .content(getPatchContent(addOperations)) @@ -8924,9 +8910,7 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration .andExpect(jsonPath("$.sections.upload.primary", is(idFirstPdf.get()))); List replaceOperations = new ArrayList(); - Map primaryUUID2 = new HashMap(); - primaryUUID2.put("primary", idSecondPdf.get()); - replaceOperations.add(new ReplaceOperation("/sections/upload/primary", primaryUUID2)); + replaceOperations.add(new ReplaceOperation("/sections/upload/primary", idSecondPdf.get())); getClient(tokenAdmin).perform(patch("/api/submission/workspaceitems/" + witem.getID()) .content(getPatchContent(replaceOperations)) @@ -8972,9 +8956,7 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration "$.sections.upload.files[0].uuid"))); List replaceOperations = new ArrayList(); - Map primaryUUID2 = new HashMap(); - primaryUUID2.put("primary", idPdf.get()); - replaceOperations.add(new ReplaceOperation("/sections/upload/primary", primaryUUID2)); + replaceOperations.add(new ReplaceOperation("/sections/upload/primary", idPdf.get())); getClient(tokenAdmin).perform(patch("/api/submission/workspaceitems/" + witem.getID()) .content(getPatchContent(replaceOperations)) @@ -9018,9 +9000,7 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration "$.sections.upload.files[0].uuid"))); List addOperations = new ArrayList(); - Map primaryUUID = new HashMap(); - primaryUUID.put("primary", idFirstPdf.get()); - addOperations.add(new AddOperation("/sections/upload/primary", primaryUUID)); + addOperations.add(new AddOperation("/sections/upload/primary", idFirstPdf.get())); getClient(tokenAdmin).perform(patch("/api/submission/workspaceitems/" + witem.getID()) .content(getPatchContent(addOperations)) @@ -9032,9 +9012,7 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration .andExpect(jsonPath("$.sections.upload.primary", is(idFirstPdf.get()))); List replaceOperations = new ArrayList(); - Map primaryUUID2 = new HashMap(); - primaryUUID2.put("primary", UUID.randomUUID().toString()); - replaceOperations.add(new ReplaceOperation("/sections/upload/primary", primaryUUID2)); + replaceOperations.add(new ReplaceOperation("/sections/upload/primary", UUID.randomUUID())); getClient(tokenAdmin).perform(patch("/api/submission/workspaceitems/" + witem.getID()) .content(getPatchContent(replaceOperations)) From 534ee3a699937eedd11aa5cb54f97b081bcda621 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 20 Oct 2023 15:08:03 -0500 Subject: [PATCH 241/412] 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 f45dbee66f..6eb631cfa5 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 class RequestItemRepository } 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 242/412] 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 f689551f1a..6cf8336171 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.authorize.service.AuthorizeService; 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 243/412] 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 afd82db863..4dcba5c1d4 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 @@ public class LDAPAuthentication 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 class LDAPAuthentication /* * 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 244/412] Add a null check when assigning ldap groups Prevent NullReferenceException by checking if the group list is null Fixes #8920 --- .../authenticate/LDAPAuthentication.java | 99 ++++++++++++------- 1 file changed, 61 insertions(+), 38 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 afd82db863..aced16876d 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 class LDAPAuthentication 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,52 +725,75 @@ public class LDAPAuthentication 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()) { - - // 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 (group == null) { + cmp = StringUtils.containsIgnoreCase(dn, 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)); + assignGroup(context, groupmapIndex, dspaceGroupName); + } + } else { + // list of strings with dn from LDAP groups + // inner loop + Iterator groupIterator = group.iterator(); + while (groupIterator.hasNext()) { + + // 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)); + } + } + @Override public boolean isUsed(final Context context, final HttpServletRequest request) { if (request != null && From 1e82ca7998c45bd628cd84cefce9ae3f0a0ce046 Mon Sep 17 00:00:00 2001 From: Jens Vannerum Date: Fri, 27 Oct 2023 15:50:26 +0200 Subject: [PATCH 245/412] 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 fc438c234c..5dffe5fdfc 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 class AuthorizeServiceImpl implements AuthorizeService { 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 82b39dd2df..a068ee0cf9 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 class Context implements AutoCloseable { 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 246/412] 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 f62feba298..7b11d73834 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 @@ public class 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 247/412] 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 f206558d81..998431d92a 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 248/412] 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 921e4efcc7..30ef5f37fb 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 class BitstreamTest extends AbstractDSpaceObjectTest { 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 249/412] 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 30ef5f37fb..eb3de96d2f 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 class BitstreamTest extends AbstractDSpaceObjectTest { 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 250/412] 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 eb3de96d2f..e85a0fc7b7 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 class BitstreamTest extends AbstractDSpaceObjectTest { 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 251/412] 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 59cee28293..b900ebe88d 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 3ee7a4a868e5a2407aadb1298b0cb617ecd4c359 Mon Sep 17 00:00:00 2001 From: Mykhaylo Boychuk Date: Mon, 30 Oct 2023 23:10:59 +0100 Subject: [PATCH 252/412] [CST-12042] improved code --- .../PrimaryBitstreamAddPatchOperation.java | 27 +++++++++---------- ...PrimaryBitstreamReplacePatchOperation.java | 16 +++++------ .../app/rest/submit/step/UploadStep.java | 2 +- 3 files changed, 22 insertions(+), 23 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/PrimaryBitstreamAddPatchOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/PrimaryBitstreamAddPatchOperation.java index 0e23349d76..5653678a50 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/PrimaryBitstreamAddPatchOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/PrimaryBitstreamAddPatchOperation.java @@ -11,6 +11,7 @@ import static org.dspace.core.Constants.CONTENT_BUNDLE_NAME; import java.util.List; import java.util.Objects; +import java.util.Optional; import java.util.UUID; import javax.servlet.http.HttpServletRequest; @@ -39,27 +40,25 @@ public class PrimaryBitstreamAddPatchOperation extends AddPatchOperation Item item = source.getItem(); UUID primaryUUID = parseValue(value); List bundles = itemService.getBundles(item, CONTENT_BUNDLE_NAME); - Bundle currentPrimaryBundle = bundles.stream() - .filter(bundle -> Objects.nonNull(bundle.getPrimaryBitstream())) - .findFirst() - .orElse(null); + Optional currentPrimaryBundle = bundles.stream() + .filter(bundle -> Objects.nonNull(bundle.getPrimaryBitstream())) + .findFirst(); - Bitstream primaryBitstreamToAdd = null; + Optional primaryBitstreamToAdd = null; for (Bundle bundle : bundles) { - primaryBitstreamToAdd = bundle.getBitstreams().stream() - .filter(b -> b.getID().equals(primaryUUID)) - .findFirst() - .orElse(null); - if (Objects.nonNull(primaryBitstreamToAdd)) { - if (Objects.nonNull(currentPrimaryBundle)) { - currentPrimaryBundle.setPrimaryBitstreamID(null); + primaryBitstreamToAdd = bundle.getBitstreams().stream() + .filter(b -> b.getID().equals(primaryUUID)) + .findFirst(); + if (primaryBitstreamToAdd.isPresent()) { + if (currentPrimaryBundle.isPresent()) { + currentPrimaryBundle.get().setPrimaryBitstreamID(null); } - bundle.setPrimaryBitstreamID(primaryBitstreamToAdd); + bundle.setPrimaryBitstreamID(primaryBitstreamToAdd.get()); break; } } - if (Objects.isNull(primaryBitstreamToAdd)) { + if (primaryBitstreamToAdd.isEmpty()) { throw new UnprocessableEntityException("The provided uuid: " + primaryUUID + " of bitstream to set as primary doesn't match any bitstream!"); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/PrimaryBitstreamReplacePatchOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/PrimaryBitstreamReplacePatchOperation.java index 6572801242..abd59101f4 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/PrimaryBitstreamReplacePatchOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/PrimaryBitstreamReplacePatchOperation.java @@ -11,6 +11,7 @@ import static org.dspace.core.Constants.CONTENT_BUNDLE_NAME; import java.util.List; import java.util.Objects; +import java.util.Optional; import java.util.UUID; import javax.servlet.http.HttpServletRequest; @@ -46,20 +47,19 @@ public class PrimaryBitstreamReplacePatchOperation extends ReplacePatchOperation .findFirst() .orElseThrow(() -> new UnprocessableEntityException(EX_MESSAGE)); - Bitstream primaryBitstream = null; + Optional primaryBitstream = null; for (Bundle bundle : bundles) { - primaryBitstream = bundle.getBitstreams().stream() - .filter(b -> b.getID().equals(primaryUUID)) - .findFirst() - .orElse(null); - if (Objects.nonNull(primaryBitstream)) { + primaryBitstream = bundle.getBitstreams().stream() + .filter(b -> b.getID().equals(primaryUUID)) + .findFirst(); + if (primaryBitstream.isPresent()) { currentPrimaryBundle.setPrimaryBitstreamID(null); - bundle.setPrimaryBitstreamID(primaryBitstream); + bundle.setPrimaryBitstreamID(primaryBitstream.get()); break; } } - if (Objects.isNull(primaryBitstream)) { + if (primaryBitstream.isEmpty()) { throw new UnprocessableEntityException("The provided uuid: " + primaryUUID + " of bitstream to set as primary doesn't match any bitstream!"); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/UploadStep.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/UploadStep.java index 57913160d3..1dea6bbeeb 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/UploadStep.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/UploadStep.java @@ -73,7 +73,7 @@ public class UploadStep extends AbstractProcessingStep public void doPatchProcessing(Context context, HttpServletRequest currentRequest, InProgressSubmission source, Operation op, SubmissionStepConfig stepConf) throws Exception { - String instance = StringUtils.EMPTY; + String instance = null; if ("remove".equals(op.getOp())) { if (op.getPath().contains(UPLOAD_STEP_METADATA_PATH)) { instance = UPLOAD_STEP_METADATA_OPERATION_ENTRY; 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 253/412] 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 c97d224657..7a0bae1582 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 254/412] 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 7a0bae1582..9dd2f54a43 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 255/412] 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 2d0574a630..5f17051dbb 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 class EPersonServiceImpl extends DSpaceObjectServiceImpl impleme @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 51ab89ef7e..573103f86a 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 interface EPersonDAO extends DSpaceObjectDAO, DSpaceObjectLegacy 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 50547a5007..14b44d77c0 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 class EPersonDAOImpl extends AbstractHibernateDSODAO implements } @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 class EPersonDAOImpl extends AbstractHibernateDSODAO implements 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 47be942e97..b60247ef54 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 interface EPersonService extends DSpaceObjectService, DSpaceObje 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 b1cdc401f2..1ce278893d 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 javax.servlet.http.HttpServletRequest; 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 @@ import org.springframework.stereotype.Component; public class GroupEPersonLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { + @Autowired + EPersonService epersonService; + @Autowired GroupService groupService; @@ -45,7 +52,11 @@ public class GroupEPersonLinkRepository extends AbstractDSpaceRestRepository 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 256/412] 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 14b44d77c0..bd68a7f399 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 class EPersonDAOImpl extends AbstractHibernateDSODAO implements } @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 class EPersonDAOImpl extends AbstractHibernateDSODAO implements 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 257/412] 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 fda8b15eff..4d68652e24 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 class GroupRestRepositoryIT extends AbstractControllerIntegrationTest { } + // 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 258/412] 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 6cb534146b..67655e0e0a 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 @@ public class Group extends DSpaceObject implements DSpaceObjectLegacySupport { } /** - * 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 @@ public class Group extends DSpaceObject implements DSpaceObjectLegacySupport { } /** - * 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 607e57af0b..4fdd1a3ba3 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 class GroupServiceImpl extends DSpaceObjectServiceImpl implements 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 2cc77129f0..fd56fe9bd1 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 @@ public interface GroupDAO extends DSpaceObjectDAO, DSpaceObjectLegacySupp */ 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 edc2ab749b..f071a1bc75 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 class GroupDAOImpl extends AbstractHibernateDSODAO implements Grou 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 b60247ef54..5b10ea539b 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 interface EPersonService extends DSpaceObjectService, DSpaceObje /** * 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 8979bcc445..634fd0aca2 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 interface GroupService extends DSpaceObjectService, DSpaceObjectLe */ 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 ee9c883f1b..7666fcfe54 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 @@ package org.dspace.eperson; 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 java.util.List; 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 class GroupTest extends AbstractUnitTest { 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 37cf9083b3..564e941d45 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 class GroupGroupLinkRepository extends AbstractDSpaceRestRepository 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 4d68652e24..797657794a 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 class GroupRestRepositoryIT extends AbstractControllerIntegrationTest { .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 259/412] 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 b98db57356..07f0fa4cd5 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.io.IOException; 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 class EPersonTest extends AbstractUnitTest { 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 @@ public class EPersonTest extends AbstractUnitTest { 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 260/412] 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 07f0fa4cd5..fb62edec09 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 @@ package org.dspace.eperson; 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 java.util.Set; 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 class EPersonTest extends AbstractUnitTest { // 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 class EPersonTest extends AbstractUnitTest { 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 7666fcfe54..a056c8061e 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.Arrays; 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 class GroupTest extends AbstractUnitTest { // 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 261/412] 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 | 62 +++++++++++-------- .../java/org/dspace/eperson/GroupTest.java | 60 +++++++++++++----- 4 files changed, 111 insertions(+), 44 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 4fdd1a3ba3..faf7b2b52a 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 class GroupServiceImpl extends DSpaceObjectServiceImpl implements 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 634fd0aca2..ef3949149f 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 fb62edec09..6c162c30d1 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 class EPersonTest extends AbstractUnitTest { 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))); + Group group2 = null; + EPerson eperson4 = null; - // Add another group with duplicate EPerson - Group group2 = createGroup("anotherGroup"); - groupService.addMember(context, group2, eperson1); - groupService.update(context, group2); + 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))); - // Verify countByGroups is still 3 (existing person should not be counted twice) - assertEquals(3, ePersonService.countByGroups(context, Set.of(group, group2))); + // Add another group with duplicate EPerson + group2 = createGroup("anotherGroup"); + groupService.addMember(context, group2, eperson1); + groupService.update(context, 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))); + // Verify countByGroups is still 3 (existing person should not be counted twice) + assertEquals(3, 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(); + // 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 a056c8061e..0eaacb6194 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 class GroupTest extends AbstractUnitTest { } } + @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 class GroupTest extends AbstractUnitTest { @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 class GroupTest extends AbstractUnitTest { 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 262/412] 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 5f17051dbb..ce117282de 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 class EPersonServiceImpl extends DSpaceObjectServiceImpl impleme 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 faf7b2b52a..d5d7ebcec1 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 class GroupServiceImpl extends DSpaceObjectServiceImpl implements 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 class GroupServiceImpl extends DSpaceObjectServiceImpl implements } } 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 class GroupServiceImpl extends DSpaceObjectServiceImpl implements 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 263/412] 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 d5d7ebcec1..20d29734cb 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 class GroupServiceImpl extends DSpaceObjectServiceImpl implements 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 class GroupServiceImpl extends DSpaceObjectServiceImpl implements } } 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 class GroupServiceImpl extends DSpaceObjectServiceImpl implements 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 264/412] 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 f071a1bc75..ad9c7b54fd 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 class GroupDAOImpl extends AbstractHibernateDSODAO implements Grou @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 class GroupDAOImpl extends AbstractHibernateDSODAO implements Grou } 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 265/412] 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 657e02b58d..c27c3d3d12 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 266/412] Remove line breaks from default.license because they are being rendered in the frontend --- dspace/config/default.license | 32 +++++++------------------------- 1 file changed, 7 insertions(+), 25 deletions(-) diff --git a/dspace/config/default.license b/dspace/config/default.license index 0b5b3cb4b8..390e978668 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. +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 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 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. +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 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. +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. +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 267/412] 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 a115c8aa2f..80d8ab2df4 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. 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.services.factory.DSpaceServicesFactory; 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 class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest @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 class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest //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 class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest .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 class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest @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 class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest .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 class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest //** 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 class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest ))) //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 class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest //** 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 class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest // 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 class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest 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 class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest // 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 class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest 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 class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest 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 class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest 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 class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest ))) //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 class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest 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 class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest ))) //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 class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest ))) //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 class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest ))) //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 class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest ))) //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 class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest 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 class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest ))) //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 class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest 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 class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest 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 class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest 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 class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest //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 class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest //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 class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest ))) //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 class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest 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 class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest 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 class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest ))) //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 class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest 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 class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest )))) //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 class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest 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 class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest ))) //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 class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest 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 class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest 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 class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest ))) //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 class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest 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 class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest ))) //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 class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest 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 class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest ))) //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 class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest 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 class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest ))) //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 class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest 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 class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest 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 class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest 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 class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest 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 class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest ))) //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 class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest 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 class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest ))) //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 class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest 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 class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest ))) //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 class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest 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 class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest ))) //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 class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest 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 class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest ))) //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 class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest 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 class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest ))) //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 class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest @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 class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest @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 class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest @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 class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest .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 class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest 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 class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest //** 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 class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest ))) //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 class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest .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 class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest .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 class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest .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 class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest .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 class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest .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 class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest // 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 class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest .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 class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest .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 class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest .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 class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest // 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 class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest .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 class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest .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 268/412] 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 20d29734cb..c2f2ea68bd 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 class GroupServiceImpl extends DSpaceObjectServiceImpl implements // 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 class GroupServiceImpl extends DSpaceObjectServiceImpl implements 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 573103f86a..9e78e758f9 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 interface EPersonDAO extends DSpaceObjectDAO, DSpaceObjectLegacy 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 269/412] 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 400b5ecb87..5e9a615560 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.Options; 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 class GenerateSitemaps { "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 class GenerateSitemaps { } /* - * 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 class GenerateSitemaps { 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 class GenerateSitemaps { 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 61027c5550..381d079ca6 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 270/412] 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 c2f2ea68bd..b8d8c75d0f 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 class GroupServiceImpl extends DSpaceObjectServiceImpl implements } @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 class GroupServiceImpl extends DSpaceObjectServiceImpl implements } @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 class GroupServiceImpl extends DSpaceObjectServiceImpl implements 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 fd56fe9bd1..9742e1611e 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 @@ public interface GroupDAO extends DSpaceObjectDAO, DSpaceObjectLegacySupp */ 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 ad9c7b54fd..6aea9ecd8d 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 class GroupDAOImpl extends AbstractHibernateDSODAO implements Grou 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 class GroupDAOImpl extends AbstractHibernateDSODAO implements Grou 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 ef3949149f..0be2f47a61 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 interface GroupService extends DSpaceObjectService, DSpaceObjectLe 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 0eaacb6194..fddcabe4b0 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 class GroupTest extends AbstractUnitTest { } } + @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 103abdcae6..9eb92d8e6f 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 class GroupRestRepository extends DSpaceObjectRestRepository 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 797657794a..4300c98758 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 class GroupRestRepositoryIT extends AbstractControllerIntegrationTest { .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 271/412] 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 | 96 +++++-- .../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, 435 insertions(+), 84 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 e6535f0941..e9c6b95b7f 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 @@ public abstract class AbstractHibernateDSODAO extends Ab if (CollectionUtils.isNotEmpty(metadataFields) || StringUtils.isNotBlank(additionalWhere)) { //Add the where query on metadata query.append(" WHERE "); + // Group the 'OR' clauses below in outer parentheses, e.g. "WHERE (clause1 OR clause2 OR clause3)". + // Grouping these 'OR' clauses allows for later code to append 'AND' clauses without unexpected behaviors + query.append("("); for (int i = 0; i < metadataFields.size(); i++) { MetadataField metadataField = metadataFields.get(i); if (StringUtils.isNotBlank(operator)) { - query.append(" ("); query.append("lower(STR(" + metadataField.toString()).append(".value)) ").append(operator) .append(" lower(:queryParam)"); - query.append(")"); if (i < metadataFields.size() - 1) { query.append(" OR "); } @@ -102,6 +103,7 @@ public abstract class AbstractHibernateDSODAO extends Ab } query.append(additionalWhere); } + query.append(")"); } } 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 ce117282de..66fe6562ea 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/EPersonServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/EPersonServiceImpl.java @@ -184,32 +184,98 @@ public class EPersonServiceImpl extends DSpaceObjectServiceImpl impleme @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)); - if (person != null) { - ePerson.add(person); - } - return ePerson; - } catch (IllegalArgumentException e) { + 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; } - return ePersonDAO.search(context, query, Arrays.asList(firstNameField, lastNameField), - Arrays.asList(firstNameField, lastNameField), offset, limit); + 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) { + ePersons.add(person); + } } + return ePersons; } @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; + 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.searchResultCount(context, query, Arrays.asList(firstNameField, lastNameField)); + } else { + // Search by UUID + EPerson person = find(context, uuid); + if (person != null) { + result = 1; + } } - return ePersonDAO.searchResultCount(context, query, Arrays.asList(firstNameField, lastNameField)); + return result; + } + + @Override + 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 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 9e78e758f9..f7543570df 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 bd68a7f399..4d64dd967f 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 class EPersonDAOImpl extends AbstractHibernateDSODAO implements 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 class EPersonDAOImpl extends AbstractHibernateDSODAO implements 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 class EPersonDAOImpl extends AbstractHibernateDSODAO implements 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 class EPersonDAOImpl extends AbstractHibernateDSODAO implements 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 5b10ea539b..2afec161a6 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 interface EPersonService extends DSpaceObjectService, DSpaceObje * * @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 interface EPersonService extends DSpaceObjectService, DSpaceObje 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 6c162c30d1..3780afcf63 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 static org.junit.Assert.fail; 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 class EPersonTest extends AbstractUnitTest { */ /** - * 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 @@ public class EPersonTest extends AbstractUnitTest { 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 @@ public class EPersonTest extends AbstractUnitTest { 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 272/412] 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 062f7b7a94..bd42b74206 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.authorize.service.ValidatePasswordService; 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 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 9eb92d8e6f..a3b525387c 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 class GroupRestRepository extends DSpaceObjectRestRepository Date: Tue, 17 Oct 2023 16:27:51 -0500 Subject: [PATCH 273/412] 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 4d64dd967f..87d6c5869b 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 @@ public class EPersonDAOImpl extends AbstractHibernateDSODAO implements // 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 274/412] 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 cbcf970547..7df95aeeb5 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 static org.springframework.test.web.servlet.result.MockMvcResultMatchers. 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 class SitemapRestControllerIT extends AbstractControllerIntegrationTest { 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 class SitemapRestControllerIT extends AbstractControllerIntegrationTest { .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 class SitemapRestControllerIT extends AbstractControllerIntegrationTest { .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 275/412] 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 5e9a615560..90962d12aa 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.File; 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.collections4.CollectionUtils; 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.Context; 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 class GenerateSitemaps { } 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); + 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 (Community comm : comms) { - String url = uiURLStem + "communities/" + comm.getID(); + 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); + int files = html.finish(); + log.info(LogHelper.getHeader(c, "write_sitemap", + "type=html,num_files=" + files + ",communities=" + + commsCount + ",collections=" + collsCount + + ",items=" + itemsCount)); } + if (makeSitemapOrg) { - sitemapsOrg.addURL(url, null); + int files = sitemapsOrg.finish(); + log.info(LogHelper.getHeader(c, "write_sitemap", + "type=html,num_files=" + files + ",communities=" + + commsCount + ",collections=" + collsCount + + ",items=" + itemsCount)); } - - c.uncacheEntity(comm); + } catch (SearchServiceException e) { + throw new RuntimeException(e); + } finally { + c.abort(); } - - 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()); - } - - c.uncacheEntity(i); - - itemCount++; - } - - 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 0cf2aa50af..cd3797e3e3 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 @@ public class SolrServiceImpl implements SearchService, IndexingService { // 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 7df95aeeb5..175fb34e6c 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 class SitemapRestControllerIT extends AbstractControllerIntegrationTest { .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 276/412] 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 4febcd5594..daea935f53 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 @@ public class SubmissionFormConverter implements DSpaceConverter Date: Wed, 8 Nov 2023 15:41:22 +0200 Subject: [PATCH 277/412] [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 d6d77fe7f0..0e051625aa 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 class BitstreamDAOImpl extends AbstractHibernateDSODAO impleme @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 278/412] 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 2822d3624e..08045325b8 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.Bitstream; 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 class BitstreamBuilder extends AbstractDSpaceObjectBuilder { 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 @@ public class BitstreamBuilder extends AbstractDSpaceObjectBuilder { 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 class BitstreamBuilder extends AbstractDSpaceObjectBuilder { } + 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 b4d1f785d4..d17db108ba 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 class IIIFControllerIT extends AbstractControllerIntegrationTest { .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 8f565590ea291f4c7b70eafde866b52499ad7b70 Mon Sep 17 00:00:00 2001 From: Vincenzo Mecca Date: Fri, 10 Nov 2023 16:04:23 +0100 Subject: [PATCH 279/412] [DURACOM-204][#9192] Makes forgot-password link removable --- .../org/dspace/app/util/AuthorizeUtil.java | 31 +++- .../impl/EPersonForgotPasswordFeature.java | 58 ++++++ .../RegistrationRestRepository.java | 3 + .../EPersonForgotPasswordFeatureIT.java | 167 ++++++++++++++++++ .../modules/authentication-password.cfg | 5 +- 5 files changed, 259 insertions(+), 5 deletions(-) create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/EPersonForgotPasswordFeature.java create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/EPersonForgotPasswordFeatureIT.java diff --git a/dspace-api/src/main/java/org/dspace/app/util/AuthorizeUtil.java b/dspace-api/src/main/java/org/dspace/app/util/AuthorizeUtil.java index efd813d29b..b498b69395 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/AuthorizeUtil.java +++ b/dspace-api/src/main/java/org/dspace/app/util/AuthorizeUtil.java @@ -628,12 +628,23 @@ public class AuthorizeUtil { // actually expected to be returning true. // For example the LDAP canSelfRegister will return true due to auto-register, while that // does not imply a new user can register explicitly - return AuthenticateServiceFactory.getInstance().getAuthenticationService() - .allowSetPassword(context, request, null); + return authorizePasswordChange(context, request); } return false; } + /** + * This method will return a boolean indicating whether the current user is allowed to reset the password + * or not + * + * @return A boolean indicating whether the current user can reset its password or not + * @throws SQLException If something goes wrong + */ + public static boolean authorizeForgotPassword() { + return DSpaceServicesFactory.getInstance().getConfigurationService() + .getBooleanProperty("user.forgot-password", true); + } + /** * This method will return a boolean indicating whether it's allowed to update the password for the EPerson * with the given email and canLogin property @@ -647,8 +658,7 @@ public class AuthorizeUtil { if (eperson != null && eperson.canLogIn()) { HttpServletRequest request = new DSpace().getRequestService().getCurrentRequest() .getHttpServletRequest(); - return AuthenticateServiceFactory.getInstance().getAuthenticationService() - .allowSetPassword(context, request, null); + return authorizePasswordChange(context, request); } } catch (SQLException e) { log.error("Something went wrong trying to retrieve EPerson for email: " + email, e); @@ -656,6 +666,19 @@ public class AuthorizeUtil { return false; } + /** + * Checks if the current configuration has at least one password based authentication method + * + * @param context Dspace Context + * @param request Current Request + * @return True if the password change is enabled + * @throws SQLException + */ + protected static boolean authorizePasswordChange(Context context, HttpServletRequest request) throws SQLException { + return AuthenticateServiceFactory.getInstance().getAuthenticationService() + .allowSetPassword(context, request, null); + } + /** * This method checks if the community Admin can manage accounts * diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/EPersonForgotPasswordFeature.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/EPersonForgotPasswordFeature.java new file mode 100644 index 0000000000..c6e6b55526 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/EPersonForgotPasswordFeature.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.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.EPersonRest; +import org.dspace.app.rest.model.SiteRest; +import org.dspace.app.util.AuthorizeUtil; +import org.dspace.core.Context; +import org.springframework.stereotype.Component; + +/** + * Checks if the user provided is allowed to request a password reset. + * If none user specified, checks if the current context is allowed to set the password. + * + * @author Vincenzo Mecca (vins01-4science - vincenzo.mecca at 4science.com) + **/ +@Component +@AuthorizationFeatureDocumentation(name = EPersonForgotPasswordFeature.NAME, + description = "It can be used to check password reset for an eperson") +public class EPersonForgotPasswordFeature implements AuthorizationFeature { + + public static final String NAME = "epersonForgotPassword"; + + @Override + public boolean isAuthorized(Context context, BaseObjectRest object) throws SQLException { + boolean isEperson = object instanceof EPersonRest; + boolean isSite = object instanceof SiteRest; + if (!isEperson && !isSite) { + return false; + } + if (!AuthorizeUtil.authorizeForgotPassword()) { + return false; + } + if (isEperson) { + return AuthorizeUtil.authorizeUpdatePassword(context, ((EPersonRest) object).getEmail()); + } + return true; + } + + @Override + public String[] getSupportedTypes() { + return new String[] { + SiteRest.CATEGORY + "." + SiteRest.NAME, + EPersonRest.CATEGORY + "." + EPersonRest.NAME + }; + } + +} 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 be28170d8a..3d183fd341 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 @@ -127,6 +127,9 @@ public class RegistrationRestRepository extends DSpaceRestRepository Date: Fri, 10 Nov 2023 16:40:11 +0100 Subject: [PATCH 280/412] [DURACOM-204][#9192] Removes unused import --- .../app/rest/authorization/EPersonForgotPasswordFeatureIT.java | 1 - 1 file changed, 1 deletion(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/EPersonForgotPasswordFeatureIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/EPersonForgotPasswordFeatureIT.java index 79456c7a7a..f7c0f3ba0e 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/EPersonForgotPasswordFeatureIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/EPersonForgotPasswordFeatureIT.java @@ -14,7 +14,6 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import org.dspace.app.rest.authorization.impl.EPersonForgotPasswordFeature; -import org.dspace.app.rest.authorization.impl.EPersonRegistrationFeature; import org.dspace.app.rest.converter.EPersonConverter; import org.dspace.app.rest.converter.SiteConverter; import org.dspace.app.rest.model.EPersonRest; 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 281/412] 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 6343ef4fe1..38692c73a6 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.Collection; 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 class DCInputsReader { 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 class DCInputsReader { 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 f2bc4f0be0..34ba9e8c45 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.DCInputSet; 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.core.service.PluginService; 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 final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService 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 final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService // 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 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService } @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 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService */ private void autoRegisterChoiceAuthorityFromInputReader() { try { - List submissionConfigs = itemSubmissionConfigReader + List submissionConfigs = submissionConfigService .getAllSubmissionConfigs(Integer.MAX_VALUE, 0); DCInputsReader dcInputsReader = new DCInputsReader(); @@ -490,10 +491,11 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService 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 a9fd24e947..94e5ca57a0 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 @@ package org.dspace.content.authority.service; 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 interface ChoiceAuthorityService { /** * 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 0000000000..a593fe8ae0 --- /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 0000000000..6020f13b46 --- /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 0000000000..19f0508597 --- /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 0000000000..c4b111a38f --- /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 0000000000..a72bcc2c3b --- /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 be4d6a12da..cb1f828b93 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.ArrayList; 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 class SubmissionConfigTest extends AbstractUnitTest { // 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 ca2d11c68d..f84d17fc7a 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.apache.logging.log4j.Logger; 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.orcid.service.OrcidTokenService; 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 abstract class AbstractBuilder { 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 abstract class AbstractBuilder { 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 fa1d145011..a5431d9000 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.projection.Projection; 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 bf683be8a4..0391cbce7a 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 { - 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 class SubmissionDefinitionRestRepository extends DSpaceRestRepository 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 class SubmissionDefinitionRestRepository 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 class SubmissionPanelRestRepository extends DSpaceRestRepository 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 ee8dc12e73..de39ff69fb 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.WorkflowItemRest; 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.core.Context; 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 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 76de36dde6..e3dbd566a8 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.WorkflowItemRestRepository; 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.license.service.CreativeCommonsService; 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 class SubmissionService { 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 class SubmissionService { 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 cf1e0c7c76..006c22dae1 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.matcher.SubmissionFormFieldMatcher; 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 class SubmissionFormsControllerIT extends AbstractControllerIntegrationTe ; } - 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 381d079ca6..d38ffd6433 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 cef906adc8..acfa0efe6d 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 3ede01647c..a13b1bb867 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 282/412] 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 7dde330b27..71b088ff16 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 class CrossRefImportMetadataSourceServiceImpl extends AbstractImportMetad 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 class CrossRefImportMetadataSourceServiceImpl extends AbstractImportMetad 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 class CrossRefImportMetadataSourceServiceImpl extends AbstractImportMetad 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 class CrossRefImportMetadataSourceServiceImpl extends AbstractImportMetad 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 283/412] 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 a913f2504a..c303561434 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 class ContentGenerator implements SubscriptionGenerator 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 class ContentGenerator implements SubscriptionGenerator } 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 a330c59537..9b8c91e559 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 284/412] 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 72524709ec..31c22692f0 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 285/412] 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 99c9efe019..d6913078e4 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 9e6dcc0b23..13bb0d2278 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 f1ae184fd5..c538d324cd 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 109835d14d..857f22755e 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 9b61af72d1..f16e81c9fd 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 286/412] 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 c538d324cd..8be8ac13fe 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 dd633def28..bef894d79b 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 a55b323339..6f72ab0585 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 287/412] 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 8be8ac13fe..2adceaa4d3 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 0000000000..46bdab3b68 --- /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 288/412] 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 2adceaa4d3..dd44e92f04 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 289/412] 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 dd44e92f04..338c7371f6 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 12f95f78462508c5a7f4ea20d0fe2454b19c43d2 Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Thu, 30 Nov 2023 14:07:05 +0100 Subject: [PATCH 290/412] 108915: Refactored all LinkRepositories to use the plural model name instead of the singular --- .../org/dspace/app/rest/EntityTypeLabelRestController.java | 2 +- .../main/java/org/dspace/app/rest/GroupRestController.java | 2 +- .../org/dspace/app/rest/SubmissionCCLicenseUrlRepository.java | 4 ++-- .../app/rest/WorkflowDefinitionCollectionsLinkRepository.java | 2 +- .../app/rest/WorkflowDefinitionStepsLinkRepository.java | 2 +- .../dspace/app/rest/WorkflowStepActionsLinkRepository.java | 2 +- .../process/SubmissionCCLicenseUrlResourceHalLinkFactory.java | 4 ++-- .../org/dspace/app/rest/model/AuthorizationFeatureRest.java | 1 + .../java/org/dspace/app/rest/model/AuthorizationRest.java | 1 + .../java/org/dspace/app/rest/model/BitstreamFormatRest.java | 1 + .../main/java/org/dspace/app/rest/model/BrowseIndexRest.java | 1 + .../org/dspace/app/rest/model/BulkAccessConditionRest.java | 4 ++-- .../java/org/dspace/app/rest/model/DiscoveryResultsRest.java | 1 + .../src/main/java/org/dspace/app/rest/model/EPersonRest.java | 1 + .../main/java/org/dspace/app/rest/model/EntityTypeRest.java | 2 +- .../src/main/java/org/dspace/app/rest/model/FeedbackRest.java | 3 ++- .../src/main/java/org/dspace/app/rest/model/GroupRest.java | 3 +-- .../main/java/org/dspace/app/rest/model/IdentifierRest.java | 1 + .../java/org/dspace/app/rest/model/MetadataFieldRest.java | 2 +- .../java/org/dspace/app/rest/model/MetadataSchemaRest.java | 1 + .../main/java/org/dspace/app/rest/model/OrcidHistoryRest.java | 1 + .../src/main/java/org/dspace/app/rest/model/PropertyRest.java | 1 + .../main/java/org/dspace/app/rest/model/RegistrationRest.java | 2 +- .../main/java/org/dspace/app/rest/model/RelationshipRest.java | 1 + .../java/org/dspace/app/rest/model/RelationshipTypeRest.java | 1 + .../java/org/dspace/app/rest/model/ResearcherProfileRest.java | 3 ++- .../main/java/org/dspace/app/rest/model/SearchEventRest.java | 1 + .../src/main/java/org/dspace/app/rest/model/SiteRest.java | 1 + .../org/dspace/app/rest/model/SubmissionAccessOptionRest.java | 4 ++-- .../org/dspace/app/rest/model/SubmissionCCLicenseRest.java | 2 +- .../org/dspace/app/rest/model/SubmissionCCLicenseUrlRest.java | 2 +- .../org/dspace/app/rest/model/SubmissionDefinitionRest.java | 1 + .../java/org/dspace/app/rest/model/SubmissionFormRest.java | 1 + .../java/org/dspace/app/rest/model/SubmissionSectionRest.java | 1 + .../java/org/dspace/app/rest/model/SubmissionUploadRest.java | 1 + .../main/java/org/dspace/app/rest/model/SubscriptionRest.java | 2 +- .../java/org/dspace/app/rest/model/SupervisionOrderRest.java | 1 + .../java/org/dspace/app/rest/model/SystemWideAlertRest.java | 1 + .../main/java/org/dspace/app/rest/model/TemplateItemRest.java | 1 + .../main/java/org/dspace/app/rest/model/UsageReportRest.java | 1 + .../java/org/dspace/app/rest/model/VersionHistoryRest.java | 1 + .../src/main/java/org/dspace/app/rest/model/VersionRest.java | 1 + .../main/java/org/dspace/app/rest/model/ViewEventRest.java | 1 + .../main/java/org/dspace/app/rest/model/VocabularyRest.java | 1 + .../java/org/dspace/app/rest/model/WorkflowActionRest.java | 2 +- .../org/dspace/app/rest/model/WorkflowDefinitionRest.java | 2 +- .../main/java/org/dspace/app/rest/model/WorkflowItemRest.java | 1 + .../main/java/org/dspace/app/rest/model/WorkflowStepRest.java | 2 +- .../java/org/dspace/app/rest/model/WorkspaceItemRest.java | 1 + .../rest/repository/AuthorizationEpersonLinkRepository.java | 2 +- .../rest/repository/AuthorizationFeatureLinkRepository.java | 2 +- .../rest/repository/AuthorizationObjectLinkRepository.java | 2 +- .../app/rest/repository/BitstreamBundleLinkRepository.java | 2 +- .../app/rest/repository/BitstreamFormatLinkRepository.java | 2 +- .../app/rest/repository/BitstreamThumbnailLinkRepository.java | 2 +- .../dspace/app/rest/repository/BrowseEntryLinkRepository.java | 2 +- .../dspace/app/rest/repository/BrowseItemLinkRepository.java | 2 +- .../app/rest/repository/BundleBitstreamLinkRepository.java | 2 +- .../dspace/app/rest/repository/BundleItemLinkRepository.java | 2 +- .../rest/repository/BundlePrimaryBitstreamLinkRepository.java | 2 +- .../org/dspace/app/rest/repository/BundleRestRepository.java | 2 +- .../app/rest/repository/ClaimedTaskStepLinkRepository.java | 2 +- .../rest/repository/CollectionAdminGroupLinkRepository.java | 2 +- .../CollectionBitstreamReadGroupLinkRepository.java | 2 +- .../repository/CollectionItemReadGroupLinkRepository.java | 2 +- .../app/rest/repository/CollectionLicenseLinkRepository.java | 2 +- .../app/rest/repository/CollectionLogoLinkRepository.java | 2 +- .../rest/repository/CollectionMappedItemLinkRepository.java | 2 +- .../repository/CollectionParentCommunityLinkRepository.java | 2 +- .../repository/CollectionSubmitterGroupLinkRepository.java | 2 +- .../rest/repository/CommunityAdminGroupLinkRepository.java | 2 +- .../rest/repository/CommunityCollectionLinkRepository.java | 2 +- .../app/rest/repository/CommunityLogoLinkRepository.java | 2 +- .../repository/CommunityParentCommunityLinkRepository.java | 2 +- .../rest/repository/CommunitySubcommunityLinkRepository.java | 2 +- .../app/rest/repository/EPersonGroupLinkRepository.java | 2 +- .../rest/repository/EntityTypeRelationshipLinkRepository.java | 4 ++-- .../repository/ExternalSourceEntityTypeLinkRepository.java | 4 ++-- .../app/rest/repository/GroupEPersonLinkRepository.java | 2 +- .../dspace/app/rest/repository/GroupGroupLinkRepository.java | 2 +- .../app/rest/repository/GroupParentObjectLinkRepository.java | 2 +- .../app/rest/repository/ItemAccessStatusLinkRepository.java | 2 +- .../dspace/app/rest/repository/ItemBundleLinkRepository.java | 2 +- .../app/rest/repository/ItemIdentifierLinkRepository.java | 2 +- .../rest/repository/ItemMappedCollectionLinkRepository.java | 2 +- .../rest/repository/ItemOwningCollectionLinkRepository.java | 2 +- .../app/rest/repository/ItemRelationshipLinkRepository.java | 2 +- .../app/rest/repository/ItemTemplateItemOfLinkRepository.java | 2 +- .../app/rest/repository/ItemThumbnailLinkRepository.java | 2 +- .../dspace/app/rest/repository/ItemVersionLinkRepository.java | 2 +- .../app/rest/repository/PoolTaskStepLinkRepository.java | 2 +- .../app/rest/repository/ProcessFileTypesLinkRepository.java | 2 +- .../app/rest/repository/ProcessFilesLinkRepository.java | 2 +- .../app/rest/repository/ProcessOutputLinkRepository.java | 4 ++-- .../RelationshipTypeRelationshipLinkRepository.java | 2 +- .../repository/ResearcherProfileEPersonLinkRepository.java | 3 ++- .../rest/repository/ResearcherProfileItemLinkRepository.java | 2 +- .../repository/SubscriptionDSpaceObjectLinkRepository.java | 4 ++-- .../rest/repository/SubscriptionEPersonLinkRepository.java | 4 ++-- .../app/rest/repository/SubscriptionRestRepository.java | 4 ++-- .../repository/VersionHistoryDraftVersionLinkRepository.java | 4 ++-- .../app/rest/repository/VersionHistoryLinkRepository.java | 2 +- .../dspace/app/rest/repository/VersionItemLinkRepository.java | 2 +- .../dspace/app/rest/repository/VersionsLinkRepository.java | 2 +- .../VocabularyEntryDetailsChildrenLinkRepository.java | 3 ++- .../VocabularyEntryDetailsParentLinkRepository.java | 3 ++- .../app/rest/repository/VocabularyEntryLinkRepository.java | 2 +- .../app/rest/repository/WorkflowItemStepLinkRepository.java | 2 +- .../WorkspaceItemSupervisionOrdersLinkRepository.java | 3 ++- .../org/dspace/app/rest/WorkflowActionRestRepositoryIT.java | 2 +- .../dspace/app/rest/WorkflowDefinitionRestRepositoryIT.java | 2 +- .../org/dspace/app/rest/WorkflowStepRestRepositoryIT.java | 2 +- .../org/dspace/app/rest/matcher/WorkflowActionMatcher.java | 2 +- .../dspace/app/rest/matcher/WorkflowDefinitionMatcher.java | 2 +- .../java/org/dspace/app/rest/matcher/WorkflowStepMatcher.java | 2 +- .../test/java/org/dspace/app/rest/model/MockObjectRest.java | 1 + .../repository/MockObjectNeverEmbedChildLinkRepository.java | 2 +- .../MockObjectOptionallyEmbedChildLinkRepository.java | 2 +- 118 files changed, 135 insertions(+), 101 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/EntityTypeLabelRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/EntityTypeLabelRestController.java index 0729754172..9880f21ab9 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/EntityTypeLabelRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/EntityTypeLabelRestController.java @@ -40,7 +40,7 @@ import org.springframework.web.bind.annotation.RestController; * @author Maria Verdonck (Atmire) on 2019-12-13 */ @RestController -@RequestMapping("/api/" + EntityTypeRest.CATEGORY + "/" + EntityTypeRest.NAME_PLURAL) +@RequestMapping("/api/" + EntityTypeRest.CATEGORY + "/" + EntityTypeRest.PLURAL_NAME) public class EntityTypeLabelRestController { protected final EntityTypeService entityTypeService = ContentServiceFactory.getInstance().getEntityTypeService(); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/GroupRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/GroupRestController.java index 522d69fbe8..f644863e03 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/GroupRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/GroupRestController.java @@ -47,7 +47,7 @@ import org.springframework.web.bind.annotation.RestController; * This will be the entry point for the api/eperson/groups endpoint with additional paths to it */ @RestController -@RequestMapping("/api/" + GroupRest.CATEGORY + "/" + GroupRest.GROUPS) +@RequestMapping("/api/" + GroupRest.CATEGORY + "/" + GroupRest.PLURAL_NAME) public class GroupRestController { @Autowired diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/SubmissionCCLicenseUrlRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/SubmissionCCLicenseUrlRepository.java index c32d551cbe..7e30413d8d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/SubmissionCCLicenseUrlRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/SubmissionCCLicenseUrlRepository.java @@ -133,8 +133,8 @@ public class SubmissionCCLicenseUrlRepository extends DSpaceRestRepository())); + SubmissionCCLicenseUrlRest.CATEGORY, SubmissionCCLicenseUrlRest.PLURAL_NAME, "rightsByQuestions", null, + null, null, null, new LinkedMultiValueMap<>())); for (String key : parameterMap.keySet()) { uriComponentsBuilder.queryParam(key, parameterMap.get(key)); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AuthorizationFeatureRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AuthorizationFeatureRest.java index cf602e2fb3..1eae763524 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AuthorizationFeatureRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AuthorizationFeatureRest.java @@ -21,6 +21,7 @@ import org.dspace.app.rest.RestResourceController; */ public class AuthorizationFeatureRest extends BaseObjectRest { public static final String NAME = "feature"; + public static final String PLURAL_NAME = "features"; public static final String CATEGORY = RestAddressableModel.AUTHORIZATION; private String description; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AuthorizationRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AuthorizationRest.java index 95f2888313..300f45c111 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AuthorizationRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AuthorizationRest.java @@ -24,6 +24,7 @@ import org.dspace.app.rest.RestResourceController; }) public class AuthorizationRest extends BaseObjectRest { public static final String NAME = "authorization"; + public static final String PLURAL_NAME = "authorizations"; public static final String CATEGORY = RestAddressableModel.AUTHORIZATION; public static final String EPERSON = "eperson"; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BitstreamFormatRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BitstreamFormatRest.java index 1b02a1d89a..96d7ebf3d6 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BitstreamFormatRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BitstreamFormatRest.java @@ -20,6 +20,7 @@ import org.dspace.app.rest.RestResourceController; */ public class BitstreamFormatRest extends BaseObjectRest { public static final String NAME = "bitstreamformat"; + public static final String PLURAL_NAME = "bitstreamformats"; public static final String CATEGORY = RestAddressableModel.CORE; 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 f7978f00fd..4b5517643f 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 @@ -33,6 +33,7 @@ public class BrowseIndexRest extends BaseObjectRest { private static final long serialVersionUID = -4870333170249999559L; public static final String NAME = "browse"; + public static final String PLURAL_NAME = "browses"; public static final String CATEGORY = RestAddressableModel.DISCOVER; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BulkAccessConditionRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BulkAccessConditionRest.java index 97d35117d1..32d38173e7 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BulkAccessConditionRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BulkAccessConditionRest.java @@ -23,7 +23,7 @@ public class BulkAccessConditionRest extends BaseObjectRest { private static final long serialVersionUID = -7708437586052984082L; public static final String NAME = "bulkaccessconditionoption"; - public static final String PLURAL = "bulkaccessconditionoptions"; + public static final String PLURAL_NAME = "bulkaccessconditionoptions"; public static final String CATEGORY = RestAddressableModel.CONFIGURATION; private String id; @@ -81,4 +81,4 @@ public class BulkAccessConditionRest extends BaseObjectRest { return RestResourceController.class; } -} \ No newline at end of file +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/DiscoveryResultsRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/DiscoveryResultsRest.java index bf1d513a81..a6db2e58a1 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/DiscoveryResultsRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/DiscoveryResultsRest.java @@ -21,6 +21,7 @@ public abstract class DiscoveryResultsRest extends BaseObjectRest { @JsonIgnore public static final String NAME = "discover"; + public static final String PLURAL_NAME = "discovers"; public static final String CATEGORY = RestModel.DISCOVER; private String scope; private String query; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/EPersonRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/EPersonRest.java index 7b4c683322..6fb211b830 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/EPersonRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/EPersonRest.java @@ -27,6 +27,7 @@ import org.dspace.app.rest.RestResourceController; }) public class EPersonRest extends DSpaceObjectRest { public static final String NAME = "eperson"; + public static final String PLURAL_NAME = "epersons"; public static final String CATEGORY = RestAddressableModel.EPERSON; public static final String GROUPS = "groups"; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/EntityTypeRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/EntityTypeRest.java index f777b3a29c..7600ad8383 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/EntityTypeRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/EntityTypeRest.java @@ -25,7 +25,7 @@ public class EntityTypeRest extends BaseObjectRest { private static final long serialVersionUID = 8166078961459192770L; public static final String NAME = "entitytype"; - public static final String NAME_PLURAL = "entitytypes"; + public static final String PLURAL_NAME = "entitytypes"; public static final String CATEGORY = "core"; public static final String RELATION_SHIP_TYPES = "relationshiptypes"; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/FeedbackRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/FeedbackRest.java index 00f1e92c87..9a7fee568e 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/FeedbackRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/FeedbackRest.java @@ -19,6 +19,7 @@ public class FeedbackRest extends BaseObjectRest { private static final long serialVersionUID = 1L; public static final String NAME = "feedback"; + public static final String PLURAL_NAME = "feedbacks"; public static final String CATEGORY = RestAddressableModel.TOOLS; private String page; @@ -72,4 +73,4 @@ public class FeedbackRest extends BaseObjectRest { return RestResourceController.class; } -} \ No newline at end of file +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/GroupRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/GroupRest.java index 3f60b2d61f..a50aa63847 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/GroupRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/GroupRest.java @@ -33,9 +33,8 @@ import org.dspace.app.rest.RestResourceController; }) public class GroupRest extends DSpaceObjectRest { public static final String NAME = "group"; + public static final String PLURAL_NAME = "groups"; public static final String CATEGORY = RestAddressableModel.EPERSON; - - public static final String GROUPS = "groups"; public static final String SUBGROUPS = "subgroups"; public static final String EPERSONS = "epersons"; public static final String OBJECT = "object"; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/IdentifierRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/IdentifierRest.java index 6cfb147ea3..68a12f3166 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/IdentifierRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/IdentifierRest.java @@ -21,6 +21,7 @@ public class IdentifierRest extends BaseObjectRest implements RestModel // Set names used in component wiring public static final String NAME = "identifier"; public static final String PLURAL_NAME = "identifiers"; + public static final String CATEGORY = "pid"; private String value; private String identifierType; private String identifierStatus; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/MetadataFieldRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/MetadataFieldRest.java index 4524f82a68..2e17f98644 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/MetadataFieldRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/MetadataFieldRest.java @@ -18,7 +18,7 @@ import org.dspace.app.rest.RestResourceController; */ public class MetadataFieldRest extends BaseObjectRest { public static final String NAME = "metadatafield"; - public static final String NAME_PLURAL = "metadatafields"; + public static final String PLURAL_NAME = "metadatafields"; public static final String CATEGORY = RestAddressableModel.CORE; @JsonIgnore diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/MetadataSchemaRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/MetadataSchemaRest.java index 655d4c86d8..fd4ada6651 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/MetadataSchemaRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/MetadataSchemaRest.java @@ -17,6 +17,7 @@ import org.dspace.app.rest.RestResourceController; */ public class MetadataSchemaRest extends BaseObjectRest { public static final String NAME = "metadataschema"; + public static final String PLURAL_NAME = "metadataschemas"; public static final String CATEGORY = RestAddressableModel.CORE; private String prefix; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/OrcidHistoryRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/OrcidHistoryRest.java index 02e0f47062..a793006a23 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/OrcidHistoryRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/OrcidHistoryRest.java @@ -25,6 +25,7 @@ public class OrcidHistoryRest extends BaseObjectRest { public static final String CATEGORY = RestModel.EPERSON; public static final String NAME = "orcidhistory"; + public static final String PLURAL_NAME = "orcidhistories"; private UUID profileItemId; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/PropertyRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/PropertyRest.java index 365a679019..d51f0f6fcc 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/PropertyRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/PropertyRest.java @@ -18,6 +18,7 @@ import org.dspace.app.rest.RestResourceController; */ public class PropertyRest extends BaseObjectRest { public static final String NAME = "property"; + public static final String PLURAL_NAME = "properties"; public static final String CATEGORY = RestAddressableModel.CONFIGURATION; public String getName() { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RegistrationRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RegistrationRest.java index e8397f8ca7..9e525470e3 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RegistrationRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RegistrationRest.java @@ -21,7 +21,7 @@ import org.dspace.app.rest.RestResourceController; public class RegistrationRest extends RestAddressableModel { public static final String NAME = "registration"; - public static final String NAME_PLURAL = "registrations"; + public static final String PLURAL_NAME = "registrations"; public static final String CATEGORY = EPERSON; private String email; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RelationshipRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RelationshipRest.java index dd35a0726e..a4ea162829 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RelationshipRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RelationshipRest.java @@ -26,6 +26,7 @@ import org.dspace.app.rest.RestResourceController; }) public class RelationshipRest extends BaseObjectRest { public static final String NAME = "relationship"; + public static final String PLURAL_NAME = "relationships"; public static final String CATEGORY = "core"; public static final String RELATIONSHIP_TYPE = "relationshipType"; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RelationshipTypeRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RelationshipTypeRest.java index e3943643a5..6bfc75621d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RelationshipTypeRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RelationshipTypeRest.java @@ -18,6 +18,7 @@ import org.dspace.app.rest.RestResourceController; public class RelationshipTypeRest extends BaseObjectRest { public static final String NAME = "relationshiptype"; + public static final String PLURAL_NAME = "relationshiptypes"; public static final String CATEGORY = "core"; private String leftwardType; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ResearcherProfileRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ResearcherProfileRest.java index 4224cfeeb9..d6a717a922 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ResearcherProfileRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ResearcherProfileRest.java @@ -28,6 +28,7 @@ public class ResearcherProfileRest extends BaseObjectRest { private static final long serialVersionUID = 1L; public static final String CATEGORY = RestModel.EPERSON; public static final String NAME = "profile"; + public static final String PLURAL_NAME = "profiles"; public static final String ITEM = "item"; public static final String EPERSON = "eperson"; @@ -129,4 +130,4 @@ public class ResearcherProfileRest extends BaseObjectRest { } -} \ No newline at end of file +} 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 46827711f2..4206162638 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 @@ -19,6 +19,7 @@ import org.dspace.app.rest.RestResourceController; public class SearchEventRest extends BaseObjectRest { public static final String NAME = "searchevent"; + public static final String PLURAL_NAME = "searchevents"; public static final String CATEGORY = RestAddressableModel.STATISTICS; private String query; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SiteRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SiteRest.java index 533ad47df0..c45c1004f9 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SiteRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SiteRest.java @@ -17,6 +17,7 @@ import org.apache.commons.lang3.builder.HashCodeBuilder; */ public class SiteRest extends DSpaceObjectRest { public static final String NAME = "site"; + public static final String PLURAL_NAME = "sites"; public static final String CATEGORY = RestAddressableModel.CORE; @Override diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionAccessOptionRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionAccessOptionRest.java index 08f4c82f43..407a39fca5 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionAccessOptionRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionAccessOptionRest.java @@ -23,7 +23,7 @@ public class SubmissionAccessOptionRest extends BaseObjectRest { private static final long serialVersionUID = -7708437586052984082L; public static final String NAME = "submissionaccessoption"; - public static final String PLURAL = "submissionaccessoptions"; + public static final String PLURAL_NAME = "submissionaccessoptions"; public static final String CATEGORY = RestAddressableModel.CONFIGURATION; private String id; @@ -76,4 +76,4 @@ public class SubmissionAccessOptionRest extends BaseObjectRest { return RestResourceController.class; } -} \ No newline at end of file +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionCCLicenseRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionCCLicenseRest.java index 23589d5a46..57315cd1a5 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionCCLicenseRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionCCLicenseRest.java @@ -20,7 +20,7 @@ import org.dspace.app.rest.RestResourceController; */ public class SubmissionCCLicenseRest extends BaseObjectRest { public static final String NAME = "submissioncclicense"; - public static final String PLURAL = "submissioncclicenses"; + public static final String PLURAL_NAME = "submissioncclicenses"; public static final String CATEGORY = RestAddressableModel.CONFIGURATION; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionCCLicenseUrlRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionCCLicenseUrlRest.java index 77263ba317..870fe02972 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionCCLicenseUrlRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionCCLicenseUrlRest.java @@ -17,7 +17,7 @@ import org.dspace.app.rest.RestResourceController; */ public class SubmissionCCLicenseUrlRest extends BaseObjectRest { public static final String NAME = "submissioncclicenseUrl"; - public static final String PLURAL = "submissioncclicenseUrls"; + public static final String PLURAL_NAME = "submissioncclicenseUrls"; public static final String CATEGORY = RestAddressableModel.CONFIGURATION; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionDefinitionRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionDefinitionRest.java index fb440345c2..1316e3b91b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionDefinitionRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionDefinitionRest.java @@ -20,6 +20,7 @@ import org.dspace.app.rest.RestResourceController; */ public class SubmissionDefinitionRest extends BaseObjectRest { public static final String NAME = "submissiondefinition"; + public static final String PLURAL_NAME = "submissiondefinitions"; public static final String CATEGORY = RestAddressableModel.CONFIGURATION; private String name; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionFormRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionFormRest.java index 8dededdabb..fdc6b53e23 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionFormRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionFormRest.java @@ -18,6 +18,7 @@ import org.dspace.app.rest.RestResourceController; */ public class SubmissionFormRest extends BaseObjectRest { public static final String NAME = "submissionform"; + public static final String PLURAL_NAME = "submissionforms"; public static final String NAME_LINK_ON_PANEL = RestAddressableModel.CONFIGURATION; public static final String CATEGORY = RestAddressableModel.CONFIGURATION; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionSectionRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionSectionRest.java index 55f7fc035a..5a827554c5 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionSectionRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionSectionRest.java @@ -23,6 +23,7 @@ import org.dspace.app.rest.RestResourceController; public class SubmissionSectionRest extends BaseObjectRest { public static final String NAME = "submissionsection"; + public static final String PLURAL_NAME = "submissionsections"; public static final String ATTRIBUTE_NAME = "sections"; private String header; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionUploadRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionUploadRest.java index 4d8504fa0b..4d0cedc9fe 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionUploadRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionUploadRest.java @@ -21,6 +21,7 @@ import org.dspace.app.rest.RestResourceController; public class SubmissionUploadRest extends BaseObjectRest { public static final String NAME = "submissionupload"; + public static final String PLURAL_NAME = "submissionuploads"; public static final String NAME_LINK_ON_PANEL = RestAddressableModel.CONFIGURATION; public static final String CATEGORY = RestAddressableModel.CONFIGURATION; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubscriptionRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubscriptionRest.java index 78a81c38b1..dd94cc6051 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubscriptionRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubscriptionRest.java @@ -21,7 +21,7 @@ public class SubscriptionRest extends BaseObjectRest { private static final long serialVersionUID = 1L; public static final String NAME = "subscription"; - public static final String NAME_PLURAL = "subscriptions"; + public static final String PLURAL_NAME = "subscriptions"; public static final String CATEGORY = "core"; public static final String DSPACE_OBJECT = "resource"; public static final String EPERSON = "eperson"; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SupervisionOrderRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SupervisionOrderRest.java index e114fdeb39..75a391c9e4 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SupervisionOrderRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SupervisionOrderRest.java @@ -19,6 +19,7 @@ import org.dspace.supervision.SupervisionOrder; public class SupervisionOrderRest extends BaseObjectRest { public static final String NAME = "supervisionorder"; + public static final String PLURAL_NAME = "supervisionorders"; public static final String CATEGORY = RestAddressableModel.CORE; private Integer id; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SystemWideAlertRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SystemWideAlertRest.java index 995ec8e934..1fde31d45a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SystemWideAlertRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SystemWideAlertRest.java @@ -19,6 +19,7 @@ import org.dspace.app.rest.RestResourceController; */ public class SystemWideAlertRest extends BaseObjectRest { public static final String NAME = "systemwidealert"; + public static final String PLURAL_NAME = "systemwidealerts"; public static final String CATEGORY = RestAddressableModel.SYSTEM; public String getCategory() { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/TemplateItemRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/TemplateItemRest.java index cc6b11d12a..48aa30ba16 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/TemplateItemRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/TemplateItemRest.java @@ -21,6 +21,7 @@ public class TemplateItemRest extends BaseObjectRest { private UUID uuid; public static final String NAME = "itemtemplate"; + public static final String PLURAL_NAME = "itemtemplates"; public static final String CATEGORY = RestAddressableModel.CORE; @JsonIgnore private CollectionRest templateItemOf; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/UsageReportRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/UsageReportRest.java index a59535fb94..1be1a35263 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/UsageReportRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/UsageReportRest.java @@ -20,6 +20,7 @@ import org.dspace.app.rest.RestResourceController; */ public class UsageReportRest extends BaseObjectRest { public static final String NAME = "usagereport"; + public static final String PLURAL_NAME = "usagereports"; public static final String CATEGORY = RestModel.STATISTICS; @JsonProperty(value = "report-type") diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VersionHistoryRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VersionHistoryRest.java index e93e131aad..cb6294dd74 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VersionHistoryRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VersionHistoryRest.java @@ -32,6 +32,7 @@ public class VersionHistoryRest extends BaseObjectRest { private Boolean draftVersion; public static final String NAME = "versionhistory"; + public static final String PLURAL_NAME = "versionhistories"; public static final String CATEGORY = RestAddressableModel.VERSIONING; public static final String VERSIONS = "versions"; public static final String DRAFT_VERSION = "draftVersion"; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VersionRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VersionRest.java index abdc64295f..1ad9806779 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VersionRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VersionRest.java @@ -28,6 +28,7 @@ import org.dspace.app.rest.RestResourceController; public class VersionRest extends BaseObjectRest { public static final String NAME = "version"; + public static final String PLURAL_NAME = "versions"; public static final String CATEGORY = RestAddressableModel.VERSIONING; public static final String VERSION_HISTORY = "versionhistory"; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ViewEventRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ViewEventRest.java index 897a3f86ae..50163339ce 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ViewEventRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ViewEventRest.java @@ -19,6 +19,7 @@ import org.dspace.app.rest.RestResourceController; public class ViewEventRest extends BaseObjectRest { public static final String NAME = "viewevent"; + public static final String PLURAL_NAME = "viewevents"; public static final String CATEGORY = RestAddressableModel.STATISTICS; private UUID targetId; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyRest.java index cc848b945b..b4a62ae383 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyRest.java @@ -22,6 +22,7 @@ import org.dspace.app.rest.RestResourceController; public class VocabularyRest extends BaseObjectRest { public static final String NAME = "vocabulary"; + public static final String PLURAL_NAME = "vocabularies"; public static final String CATEGORY = RestAddressableModel.SUBMISSION; public static final String ENTRIES = "entries"; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowActionRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowActionRest.java index 07a2c36cff..cdfcfa1ee4 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowActionRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowActionRest.java @@ -23,7 +23,7 @@ public class WorkflowActionRest extends BaseObjectRest { public static final String CATEGORY = "config"; public static final String NAME = "workflowaction"; - public static final String NAME_PLURAL = "workflowactions"; + public static final String PLURAL_NAME = "workflowactions"; private List options; private List advancedOptions; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowDefinitionRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowDefinitionRest.java index 7c2de7071b..c31ca5be5b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowDefinitionRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowDefinitionRest.java @@ -31,7 +31,7 @@ public class WorkflowDefinitionRest extends BaseObjectRest { public static final String CATEGORY = "config"; public static final String NAME = "workflowdefinition"; - public static final String NAME_PLURAL = "workflowdefinitions"; + public static final String PLURAL_NAME = "workflowdefinitions"; public static final String COLLECTIONS_MAPPED_TO = "collections"; public static final String STEPS = "steps"; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowItemRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowItemRest.java index 8f580f4414..226ee0eece 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowItemRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowItemRest.java @@ -22,6 +22,7 @@ import org.dspace.app.rest.RestResourceController; }) public class WorkflowItemRest extends AInprogressSubmissionRest { public static final String NAME = "workflowitem"; + public static final String PLURAL_NAME = "workflowitems"; public static final String CATEGORY = RestAddressableModel.WORKFLOW; public static final String STEP = "step"; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowStepRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowStepRest.java index 648cffbca8..3a0491b95c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowStepRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowStepRest.java @@ -27,7 +27,7 @@ public class WorkflowStepRest extends BaseObjectRest { public static final String CATEGORY = "config"; public static final String NAME = "workflowstep"; - public static final String NAME_PLURAL = "workflowsteps"; + public static final String PLURAL_NAME = "workflowsteps"; public static final String ACTIONS = "workflowactions"; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkspaceItemRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkspaceItemRest.java index 57a5ab5c7f..688f4c7496 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkspaceItemRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkspaceItemRest.java @@ -22,6 +22,7 @@ import org.dspace.app.rest.RestResourceController; }) public class WorkspaceItemRest extends AInprogressSubmissionRest { public static final String NAME = "workspaceitem"; + public static final String PLURAL_NAME = "workspaceitems"; public static final String CATEGORY = RestAddressableModel.SUBMISSION; public static final String SUPERVISION_ORDERS = "supervisionOrders"; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AuthorizationEpersonLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AuthorizationEpersonLinkRepository.java index 5ffbd95a77..dad973b0f8 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AuthorizationEpersonLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AuthorizationEpersonLinkRepository.java @@ -25,7 +25,7 @@ import org.springframework.stereotype.Component; /** * Link repository for "eperson" subresource of an individual authorization. */ -@Component(AuthorizationRest.CATEGORY + "." + AuthorizationRest.NAME + "." + AuthorizationRest.EPERSON) +@Component(AuthorizationRest.CATEGORY + "." + AuthorizationRest.PLURAL_NAME + "." + AuthorizationRest.EPERSON) public class AuthorizationEpersonLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AuthorizationFeatureLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AuthorizationFeatureLinkRepository.java index cbfa848df3..6bf8b22367 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AuthorizationFeatureLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AuthorizationFeatureLinkRepository.java @@ -24,7 +24,7 @@ import org.springframework.stereotype.Component; /** * Link repository for "feature" subresource of an individual authorization. */ -@Component(AuthorizationRest.CATEGORY + "." + AuthorizationRest.NAME + "." + AuthorizationRest.FEATURE) +@Component(AuthorizationRest.CATEGORY + "." + AuthorizationRest.PLURAL_NAME + "." + AuthorizationRest.FEATURE) public class AuthorizationFeatureLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AuthorizationObjectLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AuthorizationObjectLinkRepository.java index f010b28fa5..5bf7c1b9b6 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AuthorizationObjectLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AuthorizationObjectLinkRepository.java @@ -24,7 +24,7 @@ import org.springframework.stereotype.Component; /** * Link repository for "object" subresource of an individual authorization. */ -@Component(AuthorizationRest.CATEGORY + "." + AuthorizationRest.NAME + "." + AuthorizationRest.OBJECT) +@Component(AuthorizationRest.CATEGORY + "." + AuthorizationRest.PLURAL_NAME + "." + AuthorizationRest.OBJECT) public class AuthorizationObjectLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BitstreamBundleLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BitstreamBundleLinkRepository.java index bcef1ef33d..17c26174d3 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BitstreamBundleLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BitstreamBundleLinkRepository.java @@ -27,7 +27,7 @@ import org.springframework.stereotype.Component; /** * Link repository for "bundle" subresource of an individual bitstream. */ -@Component(BitstreamRest.CATEGORY + "." + BitstreamRest.NAME + "." + BitstreamRest.BUNDLE) +@Component(BitstreamRest.CATEGORY + "." + BitstreamRest.PLURAL_NAME + "." + BitstreamRest.BUNDLE) public class BitstreamBundleLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BitstreamFormatLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BitstreamFormatLinkRepository.java index 74454161a0..491eb2c348 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BitstreamFormatLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BitstreamFormatLinkRepository.java @@ -27,7 +27,7 @@ import org.springframework.stereotype.Component; /** * Link repository for "format" subresource of an individual bitstream. */ -@Component(BitstreamRest.CATEGORY + "." + BitstreamRest.NAME + "." + BitstreamRest.FORMAT) +@Component(BitstreamRest.CATEGORY + "." + BitstreamRest.PLURAL_NAME + "." + BitstreamRest.FORMAT) public class BitstreamFormatLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BitstreamThumbnailLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BitstreamThumbnailLinkRepository.java index afe864a866..57d5f107e0 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BitstreamThumbnailLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BitstreamThumbnailLinkRepository.java @@ -27,7 +27,7 @@ import org.springframework.stereotype.Component; /** * Link repository for the thumbnail Bitstream of a Bitstream */ -@Component(BitstreamRest.CATEGORY + "." + BitstreamRest.NAME + "." + BitstreamRest.THUMBNAIL) +@Component(BitstreamRest.CATEGORY + "." + BitstreamRest.PLURAL_NAME + "." + BitstreamRest.THUMBNAIL) public class BitstreamThumbnailLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { @Autowired BitstreamService bitstreamService; 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 f608595c3d..cc1fcc9d5d 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 @@ import org.springframework.stereotype.Component; * * @author Andrea Bollini (andrea.bollini at 4science.it) */ -@Component(BrowseIndexRest.CATEGORY + "." + BrowseIndexRest.NAME + "." + BrowseIndexRest.LINK_ENTRIES) +@Component(BrowseIndexRest.CATEGORY + "." + BrowseIndexRest.PLURAL_NAME + "." + BrowseIndexRest.LINK_ENTRIES) public class BrowseEntryLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { 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 baa79bc80a..6dcf65fd50 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 @@ import org.springframework.stereotype.Component; * * @author Andrea Bollini (andrea.bollini at 4science.it) */ -@Component(BrowseIndexRest.CATEGORY + "." + BrowseIndexRest.NAME + "." + BrowseIndexRest.LINK_ITEMS) +@Component(BrowseIndexRest.CATEGORY + "." + BrowseIndexRest.PLURAL_NAME + "." + BrowseIndexRest.LINK_ITEMS) public class BrowseItemLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BundleBitstreamLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BundleBitstreamLinkRepository.java index b0a4488e03..393b490520 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BundleBitstreamLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BundleBitstreamLinkRepository.java @@ -29,7 +29,7 @@ import org.springframework.stereotype.Component; /** * Link repository for "bitstreams" subresource of an individual bundle. */ -@Component(BundleRest.CATEGORY + "." + BundleRest.NAME + "." + BundleRest.BITSTREAMS) +@Component(BundleRest.CATEGORY + "." + BundleRest.PLURAL_NAME + "." + BundleRest.BITSTREAMS) public class BundleBitstreamLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BundleItemLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BundleItemLinkRepository.java index 4df81d5054..53dedddfbf 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BundleItemLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BundleItemLinkRepository.java @@ -28,7 +28,7 @@ import org.springframework.stereotype.Component; /** * Link repository for "item" subresource of an individual bundle. */ -@Component(BundleRest.CATEGORY + "." + BundleRest.NAME + "." + BundleRest.ITEM) +@Component(BundleRest.CATEGORY + "." + BundleRest.PLURAL_NAME + "." + BundleRest.ITEM) public class BundleItemLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { 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 3d11379cd3..f419b0b8aa 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 @@ -30,7 +30,7 @@ import org.springframework.stereotype.Component; /** * Link repository for "primaryBitstream" subresource of an individual bundle. */ -@Component(BundleRest.CATEGORY + "." + BundleRest.NAME + "." + BundleRest.PRIMARY_BITSTREAM) +@Component(BundleRest.CATEGORY + "." + BundleRest.PLURAL_NAME + "." + BundleRest.PRIMARY_BITSTREAM) public class BundlePrimaryBitstreamLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BundleRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BundleRestRepository.java index f750743db6..e12594ddc1 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BundleRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BundleRestRepository.java @@ -49,7 +49,7 @@ import org.springframework.stereotype.Component; * @author Jelle Pelgrims (jelle.pelgrims at atmire.com) */ -@Component(BundleRest.CATEGORY + "." + BundleRest.NAME) +@Component(BundleRest.CATEGORY + "." + BundleRest.PLURAL_NAME) public class BundleRestRepository extends DSpaceObjectRestRepository { private static final Logger log = LogManager.getLogger(); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ClaimedTaskStepLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ClaimedTaskStepLinkRepository.java index 9ee277171e..3093a2ac0c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ClaimedTaskStepLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ClaimedTaskStepLinkRepository.java @@ -26,7 +26,7 @@ import org.springframework.stereotype.Component; /** * Link repository for the Steps subresources for an individual ClaimedTask */ -@Component(ClaimedTaskRest.CATEGORY + "." + ClaimedTaskRest.NAME + "." + ClaimedTaskRest.STEP) +@Component(ClaimedTaskRest.CATEGORY + "." + ClaimedTaskRest.PLURAL_NAME + "." + ClaimedTaskRest.STEP) public class ClaimedTaskStepLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { @Autowired diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionAdminGroupLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionAdminGroupLinkRepository.java index b239dc0457..0ce8aa82e3 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionAdminGroupLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionAdminGroupLinkRepository.java @@ -32,7 +32,7 @@ import org.springframework.stereotype.Component; * Link repository for "admingroup" subresource of an individual collection. * */ -@Component(CollectionRest.CATEGORY + "." + CollectionRest.NAME + "." + CollectionRest.ADMIN_GROUP) +@Component(CollectionRest.CATEGORY + "." + CollectionRest.PLURAL_NAME + "." + CollectionRest.ADMIN_GROUP) public class CollectionAdminGroupLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { @Autowired diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionBitstreamReadGroupLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionBitstreamReadGroupLinkRepository.java index c1b322a490..55cacf3e33 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionBitstreamReadGroupLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionBitstreamReadGroupLinkRepository.java @@ -35,7 +35,7 @@ import org.springframework.stereotype.Component; * Link repository for "BitstreamReadGroup" subresource of an individual collection. * */ -@Component(CollectionRest.CATEGORY + "." + CollectionRest.NAME + "." + CollectionRest.BITSTREAM_READ_GROUP) +@Component(CollectionRest.CATEGORY + "." + CollectionRest.PLURAL_NAME + "." + CollectionRest.BITSTREAM_READ_GROUP) public class CollectionBitstreamReadGroupLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionItemReadGroupLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionItemReadGroupLinkRepository.java index 77acb8e359..a8bf3615cf 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionItemReadGroupLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionItemReadGroupLinkRepository.java @@ -35,7 +35,7 @@ import org.springframework.stereotype.Component; * Link repository for "ItemReadGroup" subresource of an individual collection. * */ -@Component(CollectionRest.CATEGORY + "." + CollectionRest.NAME + "." + CollectionRest.ITEM_READ_GROUP) +@Component(CollectionRest.CATEGORY + "." + CollectionRest.PLURAL_NAME + "." + CollectionRest.ITEM_READ_GROUP) public class CollectionItemReadGroupLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { @Autowired diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionLicenseLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionLicenseLinkRepository.java index 4aceea599c..dcb9705dbc 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionLicenseLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionLicenseLinkRepository.java @@ -31,7 +31,7 @@ import org.springframework.stereotype.Component; * * @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.it) */ -@Component(CollectionRest.CATEGORY + "." + CollectionRest.NAME + "." + CollectionRest.LICENSE) +@Component(CollectionRest.CATEGORY + "." + CollectionRest.PLURAL_NAME + "." + CollectionRest.LICENSE) public class CollectionLicenseLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionLogoLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionLogoLinkRepository.java index 94bf99fc1f..cf2360dc05 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionLogoLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionLogoLinkRepository.java @@ -27,7 +27,7 @@ import org.springframework.stereotype.Component; /** * Link repository for "logo" subresource of an individual collection. */ -@Component(CollectionRest.CATEGORY + "." + CollectionRest.NAME + "." + CollectionRest.LOGO) +@Component(CollectionRest.CATEGORY + "." + CollectionRest.PLURAL_NAME + "." + CollectionRest.LOGO) public class CollectionLogoLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionMappedItemLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionMappedItemLinkRepository.java index 1118d0bd7b..02795b312e 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionMappedItemLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionMappedItemLinkRepository.java @@ -32,7 +32,7 @@ import org.springframework.stereotype.Component; /** * Link repository for "mappedItems" subresource of an individual collection. */ -@Component(CollectionRest.CATEGORY + "." + CollectionRest.NAME + "." + CollectionRest.MAPPED_ITEMS) +@Component(CollectionRest.CATEGORY + "." + CollectionRest.PLURAL_NAME + "." + CollectionRest.MAPPED_ITEMS) public class CollectionMappedItemLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionParentCommunityLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionParentCommunityLinkRepository.java index fd21397b36..62a6f4e41d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionParentCommunityLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionParentCommunityLinkRepository.java @@ -28,7 +28,7 @@ import org.springframework.stereotype.Component; /** * LinkRepository for the ParentCommunity object for a Collection */ -@Component(CollectionRest.CATEGORY + "." + CollectionRest.NAME + "." + CollectionRest.PARENT_COMMUNITY) +@Component(CollectionRest.CATEGORY + "." + CollectionRest.PLURAL_NAME + "." + CollectionRest.PARENT_COMMUNITY) public class CollectionParentCommunityLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionSubmitterGroupLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionSubmitterGroupLinkRepository.java index 4e6d901387..58645479fa 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionSubmitterGroupLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionSubmitterGroupLinkRepository.java @@ -32,7 +32,7 @@ import org.springframework.stereotype.Component; * Link repository for "submittergroup" subresource of an individual collection. * */ -@Component(CollectionRest.CATEGORY + "." + CollectionRest.NAME + "." + CollectionRest.SUBMITTERS_GROUP) +@Component(CollectionRest.CATEGORY + "." + CollectionRest.PLURAL_NAME + "." + CollectionRest.SUBMITTERS_GROUP) public class CollectionSubmitterGroupLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { @Autowired diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CommunityAdminGroupLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CommunityAdminGroupLinkRepository.java index b2ca20a7bc..a355058408 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CommunityAdminGroupLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CommunityAdminGroupLinkRepository.java @@ -32,7 +32,7 @@ import org.springframework.stereotype.Component; * Link repository for "admingroup" subresource of an individual community. * */ -@Component(CommunityRest.CATEGORY + "." + CommunityRest.NAME + "." + CommunityRest.ADMIN_GROUP) +@Component(CommunityRest.CATEGORY + "." + CommunityRest.PLURAL_NAME + "." + CommunityRest.ADMIN_GROUP) public class CommunityAdminGroupLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { @Autowired 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 3c728d8c31..f8e8891832 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 @@ -40,7 +40,7 @@ import org.springframework.stereotype.Component; /** * Link repository for "collections" subresource of an individual community. */ -@Component(CommunityRest.CATEGORY + "." + CommunityRest.NAME + "." + CommunityRest.COLLECTIONS) +@Component(CommunityRest.CATEGORY + "." + CommunityRest.PLURAL_NAME + "." + CommunityRest.COLLECTIONS) public class CommunityCollectionLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CommunityLogoLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CommunityLogoLinkRepository.java index e3892462a3..0765e2efa3 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CommunityLogoLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CommunityLogoLinkRepository.java @@ -27,7 +27,7 @@ import org.springframework.stereotype.Component; /** * Link repository for "logo" subresource of an individual community. */ -@Component(CommunityRest.CATEGORY + "." + CommunityRest.NAME + "." + CommunityRest.LOGO) +@Component(CommunityRest.CATEGORY + "." + CommunityRest.PLURAL_NAME + "." + CommunityRest.LOGO) public class CommunityLogoLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CommunityParentCommunityLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CommunityParentCommunityLinkRepository.java index ab23f3afed..be58f7d267 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CommunityParentCommunityLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CommunityParentCommunityLinkRepository.java @@ -26,7 +26,7 @@ import org.springframework.stereotype.Component; /** * LinkRepository for the ParentCommunity object for a Community */ -@Component(CommunityRest.CATEGORY + "." + CommunityRest.NAME + "." + CommunityRest.PARENT_COMMUNITY) +@Component(CommunityRest.CATEGORY + "." + CommunityRest.PLURAL_NAME + "." + CommunityRest.PARENT_COMMUNITY) public class CommunityParentCommunityLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { 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 135d964f3f..a577e97f98 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 @@ -38,7 +38,7 @@ import org.springframework.stereotype.Component; /** * Link repository for "subcommunities" subresource of an individual community. */ -@Component(CommunityRest.CATEGORY + "." + CommunityRest.NAME + "." + CommunityRest.SUBCOMMUNITIES) +@Component(CommunityRest.CATEGORY + "." + CommunityRest.PLURAL_NAME + "." + CommunityRest.SUBCOMMUNITIES) public class CommunitySubcommunityLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EPersonGroupLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EPersonGroupLinkRepository.java index 0aeda20678..af38584c1f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EPersonGroupLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EPersonGroupLinkRepository.java @@ -29,7 +29,7 @@ import org.springframework.stereotype.Component; /** * Link repository for the direct "groups" subresource of an individual eperson. */ -@Component(EPersonRest.CATEGORY + "." + EPersonRest.NAME + "." + EPersonRest.GROUPS) +@Component(EPersonRest.CATEGORY + "." + EPersonRest.PLURAL_NAME + "." + EPersonRest.GROUPS) public class EPersonGroupLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EntityTypeRelationshipLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EntityTypeRelationshipLinkRepository.java index 3d71ddd9bb..cea27f5830 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EntityTypeRelationshipLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EntityTypeRelationshipLinkRepository.java @@ -31,7 +31,7 @@ import org.springframework.stereotype.Component; * * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.it) */ -@Component(EntityTypeRest.CATEGORY + "." + EntityTypeRest.NAME + "." + EntityTypeRest.RELATION_SHIP_TYPES) +@Component(EntityTypeRest.CATEGORY + "." + EntityTypeRest.PLURAL_NAME + "." + EntityTypeRest.RELATION_SHIP_TYPES) public class EntityTypeRelationshipLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { @@ -70,4 +70,4 @@ public class EntityTypeRelationshipLinkRepository extends AbstractDSpaceRestRepo } } -} \ No newline at end of file +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ExternalSourceEntityTypeLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ExternalSourceEntityTypeLinkRepository.java index 4ed39c1893..21e87b44d0 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ExternalSourceEntityTypeLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ExternalSourceEntityTypeLinkRepository.java @@ -34,7 +34,7 @@ import org.springframework.stereotype.Component; * * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.it) */ -@Component(ExternalSourceRest.CATEGORY + "." + ExternalSourceRest.NAME + "." + ExternalSourceRest.ENTITY_TYPES) +@Component(ExternalSourceRest.CATEGORY + "." + ExternalSourceRest.PLURAL_NAME + "." + ExternalSourceRest.ENTITY_TYPES) public class ExternalSourceEntityTypeLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { @@ -70,4 +70,4 @@ public class ExternalSourceEntityTypeLinkRepository extends AbstractDSpaceRestRe return converter.toRestPage(entityTypes, pageable, total, utils.obtainProjection()); } -} \ No newline at end of file +} 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 b1cdc401f2..e1de5acc20 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 @@ -27,7 +27,7 @@ import org.springframework.stereotype.Component; /** * Link repository for "epersons" subresource of an individual group. */ -@Component(GroupRest.CATEGORY + "." + GroupRest.NAME + "." + GroupRest.EPERSONS) +@Component(GroupRest.CATEGORY + "." + GroupRest.PLURAL_NAME + "." + GroupRest.EPERSONS) public class GroupEPersonLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { 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 37cf9083b3..95b0a66abd 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 @@ -27,7 +27,7 @@ import org.springframework.stereotype.Component; /** * Link repository for "groups" subresource of an individual group. */ -@Component(GroupRest.CATEGORY + "." + GroupRest.NAME + "." + GroupRest.SUBGROUPS) +@Component(GroupRest.CATEGORY + "." + GroupRest.PLURAL_NAME + "." + GroupRest.SUBGROUPS) public class GroupGroupLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/GroupParentObjectLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/GroupParentObjectLinkRepository.java index 3d7cdf8f80..8ce43e4af8 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/GroupParentObjectLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/GroupParentObjectLinkRepository.java @@ -29,7 +29,7 @@ import org.springframework.stereotype.Component; /** * Link repository for the parent object of a group. */ -@Component(GroupRest.CATEGORY + "." + GroupRest.NAME + "." + GroupRest.OBJECT) +@Component(GroupRest.CATEGORY + "." + GroupRest.PLURAL_NAME + "." + GroupRest.OBJECT) public class GroupParentObjectLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemAccessStatusLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemAccessStatusLinkRepository.java index b2660f51e0..83e720a413 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemAccessStatusLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemAccessStatusLinkRepository.java @@ -29,7 +29,7 @@ import org.springframework.stereotype.Component; /** * Link repository for calculating the access status of an Item */ -@Component(ItemRest.CATEGORY + "." + ItemRest.NAME + "." + ItemRest.ACCESS_STATUS) +@Component(ItemRest.CATEGORY + "." + ItemRest.PLURAL_NAME + "." + ItemRest.ACCESS_STATUS) public class ItemAccessStatusLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemBundleLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemBundleLinkRepository.java index d7525c881a..19efe5e842 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemBundleLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemBundleLinkRepository.java @@ -28,7 +28,7 @@ import org.springframework.stereotype.Component; /** * Link repository for "bundles" subresource of an individual item. */ -@Component(ItemRest.CATEGORY + "." + ItemRest.NAME + "." + ItemRest.BUNDLES) +@Component(ItemRest.CATEGORY + "." + ItemRest.PLURAL_NAME + "." + ItemRest.BUNDLES) public class ItemBundleLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemIdentifierLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemIdentifierLinkRepository.java index 0714b7329b..2cc7a62174 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemIdentifierLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemIdentifierLinkRepository.java @@ -39,7 +39,7 @@ import org.springframework.stereotype.Component; /** * Link repository for the identifier of an Item */ -@Component(ItemRest.CATEGORY + "." + ItemRest.NAME + "." + ItemRest.IDENTIFIERS) +@Component(ItemRest.CATEGORY + "." + ItemRest.PLURAL_NAME + "." + ItemRest.IDENTIFIERS) public class ItemIdentifierLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { @Autowired ItemService itemService; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemMappedCollectionLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemMappedCollectionLinkRepository.java index c632cd9d61..382b1a3b93 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemMappedCollectionLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemMappedCollectionLinkRepository.java @@ -31,7 +31,7 @@ import org.springframework.stereotype.Component; /** * Link repository for "mappedCollections" subresource of an individual item. */ -@Component(ItemRest.CATEGORY + "." + ItemRest.NAME + "." + ItemRest.MAPPED_COLLECTIONS) +@Component(ItemRest.CATEGORY + "." + ItemRest.PLURAL_NAME + "." + ItemRest.MAPPED_COLLECTIONS) public class ItemMappedCollectionLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemOwningCollectionLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemOwningCollectionLinkRepository.java index a7ceed900d..22c82c4d3d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemOwningCollectionLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemOwningCollectionLinkRepository.java @@ -27,7 +27,7 @@ import org.springframework.stereotype.Component; /** * Link repository for "owningCollection" subresource of an individual item. */ -@Component(ItemRest.CATEGORY + "." + ItemRest.NAME + "." + ItemRest.OWNING_COLLECTION) +@Component(ItemRest.CATEGORY + "." + ItemRest.PLURAL_NAME + "." + ItemRest.OWNING_COLLECTION) public class ItemOwningCollectionLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemRelationshipLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemRelationshipLinkRepository.java index 4a282ee466..c5f63ab5a8 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemRelationshipLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemRelationshipLinkRepository.java @@ -31,7 +31,7 @@ import org.springframework.stereotype.Component; /** * Link repository for "relationships" subresource of an individual item. */ -@Component(ItemRest.CATEGORY + "." + ItemRest.NAME + "." + ItemRest.RELATIONSHIPS) +@Component(ItemRest.CATEGORY + "." + ItemRest.PLURAL_NAME + "." + ItemRest.RELATIONSHIPS) public class ItemRelationshipLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemTemplateItemOfLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemTemplateItemOfLinkRepository.java index 63f25bc668..fb7db2b38d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemTemplateItemOfLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemTemplateItemOfLinkRepository.java @@ -27,7 +27,7 @@ import org.springframework.stereotype.Component; /** * Link repository for "templateItemOf" subresource of an individual item. */ -@Component(ItemRest.CATEGORY + "." + ItemRest.NAME + "." + ItemRest.TEMPLATE_ITEM_OF) +@Component(ItemRest.CATEGORY + "." + ItemRest.PLURAL_NAME + "." + ItemRest.TEMPLATE_ITEM_OF) public class ItemTemplateItemOfLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemThumbnailLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemThumbnailLinkRepository.java index 7391d410b6..7a42050eec 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemThumbnailLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemThumbnailLinkRepository.java @@ -29,7 +29,7 @@ import org.springframework.stereotype.Component; /** * Link repository for the thumbnail Bitstream of an Item */ -@Component(ItemRest.CATEGORY + "." + ItemRest.NAME + "." + ItemRest.THUMBNAIL) +@Component(ItemRest.CATEGORY + "." + ItemRest.PLURAL_NAME + "." + ItemRest.THUMBNAIL) public class ItemThumbnailLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { @Autowired ItemService itemService; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemVersionLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemVersionLinkRepository.java index 95bbddc665..5e489d5ebf 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemVersionLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemVersionLinkRepository.java @@ -29,7 +29,7 @@ import org.springframework.stereotype.Component; /** * This is the Repository that will take care of fetching the Version for a given Item */ -@Component(ItemRest.CATEGORY + "." + ItemRest.NAME + "." + ItemRest.VERSION) +@Component(ItemRest.CATEGORY + "." + ItemRest.PLURAL_NAME + "." + ItemRest.VERSION) public class ItemVersionLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/PoolTaskStepLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/PoolTaskStepLinkRepository.java index 6e7f4f84ac..9e1a41cb92 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/PoolTaskStepLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/PoolTaskStepLinkRepository.java @@ -26,7 +26,7 @@ import org.springframework.stereotype.Component; /** * Link repositoy for the Steps subresources of an individual PoolTask */ -@Component(PoolTaskRest.CATEGORY + "." + PoolTaskRest.NAME + "." + PoolTaskRest.STEP) +@Component(PoolTaskRest.CATEGORY + "." + PoolTaskRest.PLURAL_NAME + "." + PoolTaskRest.STEP) public class PoolTaskStepLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { @Autowired 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 16c8115b29..1727e720a8 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 @@ -30,7 +30,7 @@ import org.springframework.stereotype.Component; * It'll retrieve all the bitstreams for the given Process and return a {@link ProcessFileTypesRest} object that holds * a list of Strings where each String represents a unique fileType of the Bitstreams for that Process */ -@Component(ProcessRest.CATEGORY + "." + ProcessRest.NAME + "." + ProcessRest.FILE_TYPES) +@Component(ProcessRest.CATEGORY + "." + ProcessRest.PLURAL_NAME + "." + ProcessRest.FILE_TYPES) public class ProcessFileTypesLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { @Autowired 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 5d8251cf19..e09a47ad7c 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 @@ -29,7 +29,7 @@ import org.springframework.stereotype.Component; * {@link org.dspace.content.Bitstream} objects for the Process endpoints * */ -@Component(ProcessRest.CATEGORY + "." + ProcessRest.NAME + "." + ProcessRest.FILES) +@Component(ProcessRest.CATEGORY + "." + ProcessRest.PLURAL_NAME + "." + ProcessRest.FILES) public class ProcessFilesLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { private static final Logger log = LogManager.getLogger(); 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 f5b3edced2..632e7d7c94 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 @@ -29,7 +29,7 @@ import org.springframework.stereotype.Component; * This linkRepository will deal with calls to the /output endpoint of a given Process. * It'll retrieve the output for the given Process and return this as a {@link BitstreamRest} object */ -@Component(ProcessRest.CATEGORY + "." + ProcessRest.NAME + "." + ProcessRest.OUTPUT) +@Component(ProcessRest.CATEGORY + "." + ProcessRest.PLURAL_NAME + "." + ProcessRest.OUTPUT) public class ProcessOutputLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { @Autowired @@ -68,4 +68,4 @@ public class ProcessOutputLinkRepository extends AbstractDSpaceRestRepository im } return converter.toRest(bitstream, projection); } -} \ No newline at end of file +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RelationshipTypeRelationshipLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RelationshipTypeRelationshipLinkRepository.java index a6c31d0c16..46cf6ac307 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RelationshipTypeRelationshipLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RelationshipTypeRelationshipLinkRepository.java @@ -27,7 +27,7 @@ import org.springframework.stereotype.Component; /** * Link repository for "relationshipType" subresource of an individual Relationship. */ -@Component(RelationshipRest.CATEGORY + "." + RelationshipRest.NAME + "." + RelationshipRest.RELATIONSHIP_TYPE) +@Component(RelationshipRest.CATEGORY + "." + RelationshipRest.PLURAL_NAME + "." + RelationshipRest.RELATIONSHIP_TYPE) public class RelationshipTypeRelationshipLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ResearcherProfileEPersonLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ResearcherProfileEPersonLinkRepository.java index 92bbf6996d..58bd9798a3 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ResearcherProfileEPersonLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ResearcherProfileEPersonLinkRepository.java @@ -34,7 +34,8 @@ import org.springframework.stereotype.Component; * @author Luca Giamminonni (luca.giamminonni at 4science.it) * */ -@Component(ResearcherProfileRest.CATEGORY + "." + ResearcherProfileRest.NAME + "." + ResearcherProfileRest.EPERSON) +@Component(ResearcherProfileRest.CATEGORY + "." + ResearcherProfileRest.PLURAL_NAME + "." + + ResearcherProfileRest.EPERSON) public class ResearcherProfileEPersonLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { @Autowired diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ResearcherProfileItemLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ResearcherProfileItemLinkRepository.java index 5f212b966f..7aaedc05a2 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ResearcherProfileItemLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ResearcherProfileItemLinkRepository.java @@ -31,7 +31,7 @@ import org.springframework.stereotype.Component; * @author Luca Giamminonni (luca.giamminonni at 4science.it) * */ -@Component(ResearcherProfileRest.CATEGORY + "." + ResearcherProfileRest.NAME + "." + ResearcherProfileRest.ITEM) +@Component(ResearcherProfileRest.CATEGORY + "." + ResearcherProfileRest.PLURAL_NAME + "." + ResearcherProfileRest.ITEM) public class ResearcherProfileItemLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { @Autowired diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionDSpaceObjectLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionDSpaceObjectLinkRepository.java index 95c4714e9c..f35b6230e5 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionDSpaceObjectLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionDSpaceObjectLinkRepository.java @@ -26,7 +26,7 @@ import org.springframework.stereotype.Component; /** * Link repository for "DSpaceObject" of subscription */ -@Component(SubscriptionRest.CATEGORY + "." + SubscriptionRest.NAME + "." + SubscriptionRest.DSPACE_OBJECT) +@Component(SubscriptionRest.CATEGORY + "." + SubscriptionRest.PLURAL_NAME + "." + SubscriptionRest.DSPACE_OBJECT) public class SubscriptionDSpaceObjectLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { @Autowired @@ -46,4 +46,4 @@ public class SubscriptionDSpaceObjectLinkRepository extends AbstractDSpaceRestRe } } -} \ No newline at end of file +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionEPersonLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionEPersonLinkRepository.java index dcf612e52d..96d07ee988 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionEPersonLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionEPersonLinkRepository.java @@ -26,7 +26,7 @@ import org.springframework.stereotype.Component; /** * Link repository for "eperson" of subscription */ -@Component(SubscriptionRest.CATEGORY + "." + SubscriptionRest.NAME + "." + SubscriptionRest.EPERSON) +@Component(SubscriptionRest.CATEGORY + "." + SubscriptionRest.PLURAL_NAME + "." + SubscriptionRest.EPERSON) public class SubscriptionEPersonLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { @Autowired @@ -46,4 +46,4 @@ public class SubscriptionEPersonLinkRepository extends AbstractDSpaceRestReposit } } -} \ No newline at end of file +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java index ce1bcff11f..e6ee87b368 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java @@ -278,7 +278,7 @@ public class SubscriptionRestRepository extends DSpaceRestRepository { public static final String CATEGORY = "test"; public static final String NAME = "testobject"; + public static final String PLURAL_NAME = "testobjects"; public static final String O_CHILDREN = "optionallyEmbeddedChildren"; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/repository/MockObjectNeverEmbedChildLinkRepository.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/repository/MockObjectNeverEmbedChildLinkRepository.java index 7d82938ae8..1442eac66c 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/repository/MockObjectNeverEmbedChildLinkRepository.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/repository/MockObjectNeverEmbedChildLinkRepository.java @@ -13,7 +13,7 @@ import org.springframework.stereotype.Component; /** * Link repository used by {@link MockObjectRest} to test that never-embedded subresources work correctly. */ -@Component(MockObjectRest.CATEGORY + "." + MockObjectRest.NAME + "." + MockObjectRest.N_CHILDREN) +@Component(MockObjectRest.CATEGORY + "." + MockObjectRest.PLURAL_NAME + "." + MockObjectRest.N_CHILDREN) public class MockObjectNeverEmbedChildLinkRepository extends AbstractMockObjectChildLinkRepository { @Override public boolean isEmbeddableRelation(Object data, String name) { diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/repository/MockObjectOptionallyEmbedChildLinkRepository.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/repository/MockObjectOptionallyEmbedChildLinkRepository.java index e75351a78e..a81eceb5b2 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/repository/MockObjectOptionallyEmbedChildLinkRepository.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/repository/MockObjectOptionallyEmbedChildLinkRepository.java @@ -13,6 +13,6 @@ import org.springframework.stereotype.Component; /** * Link repository used by {@link MockObjectRest} to test that optionally-embedded subresources work correctly. */ -@Component(MockObjectRest.CATEGORY + "." + MockObjectRest.NAME + "." + MockObjectRest.O_CHILDREN) +@Component(MockObjectRest.CATEGORY + "." + MockObjectRest.PLURAL_NAME + "." + MockObjectRest.O_CHILDREN) public class MockObjectOptionallyEmbedChildLinkRepository extends AbstractMockObjectChildLinkRepository { } From ad8809f38769152dad639279d43a52f13fe6dc8d Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Thu, 30 Nov 2023 16:22:37 +0100 Subject: [PATCH 291/412] 108915: Moved the plural/singular logic to the Rest classes --- .../org/dspace/app/rest/model/AccessStatusRest.java | 8 +++++--- .../app/rest/model/AuthenticationTokenRest.java | 6 ++++++ .../java/org/dspace/app/rest/model/AuthnRest.java | 9 +++++++++ .../app/rest/model/AuthorizationFeatureRest.java | 5 +++++ .../dspace/app/rest/model/AuthorizationRest.java | 8 ++++++++ .../dspace/app/rest/model/BitstreamFormatRest.java | 5 +++++ .../org/dspace/app/rest/model/BitstreamRest.java | 5 +++++ .../org/dspace/app/rest/model/BrowseEntryRest.java | 6 ++++++ .../org/dspace/app/rest/model/BrowseIndexRest.java | 5 +++++ .../app/rest/model/BulkAccessConditionRest.java | 5 +++++ .../java/org/dspace/app/rest/model/BundleRest.java | 5 +++++ .../org/dspace/app/rest/model/ClaimedTaskRest.java | 5 +++++ .../org/dspace/app/rest/model/CollectionRest.java | 5 +++++ .../org/dspace/app/rest/model/CommunityRest.java | 5 +++++ .../dspace/app/rest/model/DiscoveryResultsRest.java | 10 +++++++++- .../java/org/dspace/app/rest/model/EPersonRest.java | 5 +++++ .../org/dspace/app/rest/model/EntityTypeRest.java | 7 ++++++- .../app/rest/model/ExternalSourceEntryRest.java | 5 +++++ .../dspace/app/rest/model/ExternalSourceRest.java | 5 +++++ .../app/rest/model/FacetConfigurationRest.java | 9 +++++++++ .../org/dspace/app/rest/model/FeedbackRest.java | 5 +++++ .../java/org/dspace/app/rest/model/GroupRest.java | 5 +++++ .../app/rest/model/HarvestedCollectionRest.java | 8 +++++++- .../app/rest/model/HarvesterMetadataRest.java | 11 ++++++++++- .../org/dspace/app/rest/model/IdentifierRest.java | 2 +- .../org/dspace/app/rest/model/IdentifiersRest.java | 7 +++++++ .../java/org/dspace/app/rest/model/ItemRest.java | 5 +++++ .../java/org/dspace/app/rest/model/LicenseRest.java | 7 +++++++ .../dspace/app/rest/model/MetadataFieldRest.java | 5 +++++ .../dspace/app/rest/model/MetadataSchemaRest.java | 5 +++++ .../org/dspace/app/rest/model/OrcidHistoryRest.java | 5 +++++ .../org/dspace/app/rest/model/OrcidQueueRest.java | 5 +++++ .../org/dspace/app/rest/model/PoolTaskRest.java | 5 +++++ .../dspace/app/rest/model/ProcessFileTypesRest.java | 5 +++++ .../java/org/dspace/app/rest/model/ProcessRest.java | 5 +++++ .../org/dspace/app/rest/model/PropertyRest.java | 5 +++++ .../org/dspace/app/rest/model/RegistrationRest.java | 5 +++++ .../org/dspace/app/rest/model/RelationshipRest.java | 7 ++++++- .../dspace/app/rest/model/RelationshipTypeRest.java | 7 ++++++- .../org/dspace/app/rest/model/RequestItemRest.java | 9 ++++----- .../app/rest/model/ResearcherProfileRest.java | 5 +++++ .../dspace/app/rest/model/ResourcePolicyRest.java | 5 +++++ .../java/org/dspace/app/rest/model/RestModel.java | 6 ++---- .../java/org/dspace/app/rest/model/RootRest.java | 9 +++++++++ .../java/org/dspace/app/rest/model/ScriptRest.java | 5 +++++ .../app/rest/model/SearchConfigurationRest.java | 9 +++++++++ .../org/dspace/app/rest/model/SearchEventRest.java | 5 +++++ .../dspace/app/rest/model/SearchFacetEntryRest.java | 9 +++++++++ .../dspace/app/rest/model/SearchFacetValueRest.java | 9 +++++++++ .../app/rest/model/SearchResultEntryRest.java | 9 +++++++++ .../dspace/app/rest/model/SearchSupportRest.java | 9 +++++++++ .../java/org/dspace/app/rest/model/SiteRest.java | 5 +++++ .../app/rest/model/StatisticsSupportRest.java | 6 ++++++ .../app/rest/model/SubmissionAccessOptionRest.java | 5 +++++ .../app/rest/model/SubmissionCCLicenseRest.java | 5 +++++ .../app/rest/model/SubmissionCCLicenseUrlRest.java | 5 +++++ .../app/rest/model/SubmissionDefinitionRest.java | 5 +++++ .../dspace/app/rest/model/SubmissionFormRest.java | 5 +++++ .../app/rest/model/SubmissionSectionRest.java | 5 +++++ .../dspace/app/rest/model/SubmissionUploadRest.java | 5 +++++ .../org/dspace/app/rest/model/SubscriptionRest.java | 7 ++++++- .../dspace/app/rest/model/SupervisionOrderRest.java | 5 +++++ .../dspace/app/rest/model/SystemWideAlertRest.java | 5 +++++ .../org/dspace/app/rest/model/TemplateItemRest.java | 5 +++++ .../app/rest/model/UsageReportPointCityRest.java | 6 ++++++ .../app/rest/model/UsageReportPointCountryRest.java | 6 ++++++ .../app/rest/model/UsageReportPointDateRest.java | 6 ++++++ .../model/UsageReportPointDsoTotalVisitsRest.java | 7 +++++++ .../dspace/app/rest/model/UsageReportPointRest.java | 13 ++++++++++++- .../org/dspace/app/rest/model/UsageReportRest.java | 5 +++++ .../dspace/app/rest/model/VersionHistoryRest.java | 5 +++++ .../java/org/dspace/app/rest/model/VersionRest.java | 5 +++++ .../org/dspace/app/rest/model/ViewEventRest.java | 5 +++++ .../app/rest/model/VocabularyEntryDetailsRest.java | 5 +++++ .../dspace/app/rest/model/VocabularyEntryRest.java | 6 ++++++ .../org/dspace/app/rest/model/VocabularyRest.java | 5 +++++ .../dspace/app/rest/model/WorkflowActionRest.java | 7 ++++++- .../app/rest/model/WorkflowDefinitionRest.java | 7 ++++++- .../org/dspace/app/rest/model/WorkflowItemRest.java | 5 +++++ .../org/dspace/app/rest/model/WorkflowStepRest.java | 7 ++++++- .../dspace/app/rest/model/WorkspaceItemRest.java | 5 +++++ .../app/rest/signposting/model/LinksetRest.java | 5 +++++ .../app/rest/signposting/model/TypedLinkRest.java | 5 +++++ .../app/rest/converter/ConverterServiceIT.java | 5 +++++ .../org/dspace/app/rest/model/MockObjectRest.java | 5 +++++ 85 files changed, 490 insertions(+), 24 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AccessStatusRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AccessStatusRest.java index c7dc2d1198..85993f9a92 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AccessStatusRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AccessStatusRest.java @@ -7,7 +7,6 @@ */ package org.dspace.app.rest.model; -import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty.Access; @@ -16,6 +15,7 @@ import com.fasterxml.jackson.annotation.JsonProperty.Access; */ public class AccessStatusRest implements RestModel { public static final String NAME = "accessStatus"; + public static final String PLURAL_NAME = NAME; String status; @@ -25,10 +25,12 @@ public class AccessStatusRest implements RestModel { return NAME; } + /** + * The plural name is the same as the singular name + */ @Override - @JsonIgnore public String getTypePlural() { - return getType(); + return PLURAL_NAME; } public AccessStatusRest() { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AuthenticationTokenRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AuthenticationTokenRest.java index 0599e09565..6fbeeb219a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AuthenticationTokenRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AuthenticationTokenRest.java @@ -15,6 +15,7 @@ import org.dspace.app.rest.RestResourceController; */ public class AuthenticationTokenRest extends RestAddressableModel { public static final String NAME = "shortlivedtoken"; + public static final String PLURAL_NAME = "shortlivedtokens"; public static final String CATEGORY = RestAddressableModel.AUTHENTICATION; private String token; @@ -34,6 +35,11 @@ public class AuthenticationTokenRest extends RestAddressableModel { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + public String getToken() { return token; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AuthnRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AuthnRest.java index fade90fe4d..8c873ccc12 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AuthnRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AuthnRest.java @@ -18,6 +18,7 @@ import org.dspace.app.rest.AuthenticationRestController; public class AuthnRest extends BaseObjectRest { public static final String NAME = "authn"; + public static final String PLURAL_NAME = NAME; public static final String CATEGORY = RestAddressableModel.AUTHENTICATION; public String getCategory() { @@ -28,6 +29,14 @@ public class AuthnRest extends BaseObjectRest { return NAME; } + /** + * The plural name is the same as the singular name + */ + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + public Class getController() { return AuthenticationRestController.class; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AuthorizationFeatureRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AuthorizationFeatureRest.java index 1eae763524..775b10d619 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AuthorizationFeatureRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AuthorizationFeatureRest.java @@ -35,6 +35,11 @@ public class AuthorizationFeatureRest extends BaseObjectRest { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + @Override public String getCategory() { return CATEGORY; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AuthorizationRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AuthorizationRest.java index 300f45c111..fa463a7c39 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AuthorizationRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AuthorizationRest.java @@ -37,6 +37,14 @@ public class AuthorizationRest extends BaseObjectRest { return NAME; } + /** + * The plural name is the same as the singular name + */ + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + @Override public String getCategory() { return CATEGORY; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BitstreamFormatRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BitstreamFormatRest.java index 96d7ebf3d6..486eb02cad 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BitstreamFormatRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BitstreamFormatRest.java @@ -97,6 +97,11 @@ public class BitstreamFormatRest extends BaseObjectRest { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + @Override @JsonIgnore public Class getController() { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BitstreamRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BitstreamRest.java index 8e9efc2680..d2c2268b3f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BitstreamRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BitstreamRest.java @@ -88,4 +88,9 @@ public class BitstreamRest extends DSpaceObjectRest { public String getType() { return NAME; } + + @Override + public String getTypePlural() { + return PLURAL_NAME; + } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BrowseEntryRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BrowseEntryRest.java index 6a569bd4c9..57f20d7c31 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BrowseEntryRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BrowseEntryRest.java @@ -17,6 +17,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore; public class BrowseEntryRest implements RestModel { private static final long serialVersionUID = -3415049466402327251L; public static final String NAME = "browseEntry"; + public static final String PLURAL_NAME = "browseEntries"; private String authority; private String value; private String valueLang; @@ -69,4 +70,9 @@ public class BrowseEntryRest implements RestModel { public String getType() { return NAME; } + + @Override + public String getTypePlural() { + return PLURAL_NAME; + } } 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 4b5517643f..a3c0b37ba5 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 @@ -80,6 +80,11 @@ public class BrowseIndexRest extends BaseObjectRest { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + public List getMetadataList() { return metadataList; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BulkAccessConditionRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BulkAccessConditionRest.java index 32d38173e7..18ed4e71aa 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BulkAccessConditionRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BulkAccessConditionRest.java @@ -69,6 +69,11 @@ public class BulkAccessConditionRest extends BaseObjectRest { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + @Override public String getCategory() { return CATEGORY; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BundleRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BundleRest.java index dd4a80d488..1ec9f448dd 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BundleRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BundleRest.java @@ -52,4 +52,9 @@ public class BundleRest extends DSpaceObjectRest { public String getType() { return NAME; } + + @Override + public String getTypePlural() { + return PLURAL_NAME; + } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ClaimedTaskRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ClaimedTaskRest.java index dd97fef44d..0973fac987 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ClaimedTaskRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ClaimedTaskRest.java @@ -47,6 +47,11 @@ public class ClaimedTaskRest extends BaseObjectRest { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + @Override public Class getController() { return RestResourceController.class; 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 3f5ae3bb34..f00bb88395 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 @@ -75,6 +75,11 @@ public class CollectionRest extends DSpaceObjectRest { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + private int archivedItemsCount; public int getArchivedItemsCount() { 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 86dc4b2c39..0004e0b91c 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 @@ -59,6 +59,11 @@ public class CommunityRest extends DSpaceObjectRest { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + private int archivedItemsCount; public int getArchivedItemsCount() { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/DiscoveryResultsRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/DiscoveryResultsRest.java index a6db2e58a1..e63c94edc3 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/DiscoveryResultsRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/DiscoveryResultsRest.java @@ -21,7 +21,7 @@ public abstract class DiscoveryResultsRest extends BaseObjectRest { @JsonIgnore public static final String NAME = "discover"; - public static final String PLURAL_NAME = "discovers"; + public static final String PLURAL_NAME = NAME; public static final String CATEGORY = RestModel.DISCOVER; private String scope; private String query; @@ -41,6 +41,14 @@ public abstract class DiscoveryResultsRest extends BaseObjectRest { return NAME; } + /** + * The plural name is the same as the singular name + */ + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + public Class getController() { return DiscoveryRestController.class; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/EPersonRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/EPersonRest.java index 6fb211b830..c06ed0e3fe 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/EPersonRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/EPersonRest.java @@ -53,6 +53,11 @@ public class EPersonRest extends DSpaceObjectRest { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + public String getNetid() { return netid; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/EntityTypeRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/EntityTypeRest.java index 7600ad8383..9d4a729ded 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/EntityTypeRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/EntityTypeRest.java @@ -26,7 +26,7 @@ public class EntityTypeRest extends BaseObjectRest { public static final String NAME = "entitytype"; public static final String PLURAL_NAME = "entitytypes"; - public static final String CATEGORY = "core"; + public static final String CATEGORY = RestModel.CORE; public static final String RELATION_SHIP_TYPES = "relationshiptypes"; public String getCategory() { @@ -41,6 +41,11 @@ public class EntityTypeRest extends BaseObjectRest { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + private String label; public String getLabel() { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ExternalSourceEntryRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ExternalSourceEntryRest.java index 06af7e2227..34253d538a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ExternalSourceEntryRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ExternalSourceEntryRest.java @@ -33,6 +33,11 @@ public class ExternalSourceEntryRest extends RestAddressableModel { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + private String id; private String display; private String value; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ExternalSourceRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ExternalSourceRest.java index 639c2cf72e..58402954e8 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ExternalSourceRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ExternalSourceRest.java @@ -42,6 +42,11 @@ public class ExternalSourceRest extends BaseObjectRest { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + private String id; private String name; private boolean hierarchical; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/FacetConfigurationRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/FacetConfigurationRest.java index 4b8244eba2..06068b34e0 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/FacetConfigurationRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/FacetConfigurationRest.java @@ -21,6 +21,7 @@ import org.dspace.app.rest.DiscoveryRestController; public class FacetConfigurationRest extends BaseObjectRest { public static final String NAME = "discover"; + public static final String PLURAL_NAME = NAME; public static final String CATEGORY = RestModel.DISCOVER; private String scope; @@ -38,6 +39,14 @@ public class FacetConfigurationRest extends BaseObjectRest { return NAME; } + /** + * The plural name is the same as the singular name + */ + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + public Class getController() { return DiscoveryRestController.class; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/FeedbackRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/FeedbackRest.java index 9a7fee568e..434f5755e8 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/FeedbackRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/FeedbackRest.java @@ -62,6 +62,11 @@ public class FeedbackRest extends BaseObjectRest { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + @Override public String getCategory() { return CATEGORY; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/GroupRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/GroupRest.java index a50aa63847..7d56af2e72 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/GroupRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/GroupRest.java @@ -53,6 +53,11 @@ public class GroupRest extends DSpaceObjectRest { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + public String getName() { return name; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/HarvestedCollectionRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/HarvestedCollectionRest.java index a4a0aac013..3cbd98b9d4 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/HarvestedCollectionRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/HarvestedCollectionRest.java @@ -21,7 +21,8 @@ import com.fasterxml.jackson.annotation.JsonProperty; public class HarvestedCollectionRest extends BaseObjectRest { public static final String NAME = "collections"; - public static final String CATEGORY = "core"; + public static final String PLURAL_NAME = NAME; + public static final String CATEGORY = RestModel.CORE; @JsonProperty("harvest_type") private HarvestTypeEnum harvestType; @@ -70,6 +71,11 @@ public class HarvestedCollectionRest extends BaseObjectRest { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + @JsonIgnore public CollectionRest getCollectionRest() { return this.collectionRest; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/HarvesterMetadataRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/HarvesterMetadataRest.java index a32e901d74..f130f4a819 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/HarvesterMetadataRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/HarvesterMetadataRest.java @@ -21,8 +21,9 @@ import org.dspace.app.rest.HarvesterMetadataController; */ public class HarvesterMetadataRest extends BaseObjectRest { - public static final String CATEGORY = "config"; + public static final String CATEGORY = RestModel.CONFIGURATION; public static final String NAME = "harvesterMetadata"; + public static final String PLURAL_NAME = NAME; private List> configs; @@ -42,6 +43,14 @@ public class HarvesterMetadataRest extends BaseObjectRest { return NAME; } + /** + * The plural name is the same as the singular name + */ + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + public Class getController() { return HarvesterMetadataController.class; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/IdentifierRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/IdentifierRest.java index 68a12f3166..cf16184acc 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/IdentifierRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/IdentifierRest.java @@ -21,7 +21,7 @@ public class IdentifierRest extends BaseObjectRest implements RestModel // Set names used in component wiring public static final String NAME = "identifier"; public static final String PLURAL_NAME = "identifiers"; - public static final String CATEGORY = "pid"; + public static final String CATEGORY = RestModel.PID; private String value; private String identifierType; private String identifierStatus; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/IdentifiersRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/IdentifiersRest.java index 169e40979c..5414ac10b6 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/IdentifiersRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/IdentifiersRest.java @@ -22,6 +22,8 @@ public class IdentifiersRest extends BaseObjectRest { // Set names used in component wiring public static final String NAME = "identifiers"; + public static final String PLURAL_NAME = "identifiers"; + private List identifiers; // Empty constructor @@ -37,6 +39,11 @@ public class IdentifiersRest extends BaseObjectRest { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + public List getIdentifiers() { return identifiers; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ItemRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ItemRest.java index 1254ef8f93..b2f540c0ac 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ItemRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ItemRest.java @@ -87,6 +87,11 @@ public class ItemRest extends DSpaceObjectRest { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + public boolean getInArchive() { return inArchive; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/LicenseRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/LicenseRest.java index 8f8ea51086..863335ee88 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/LicenseRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/LicenseRest.java @@ -14,6 +14,8 @@ package org.dspace.app.rest.model; */ public class LicenseRest implements RestModel { public static final String NAME = "license"; + public static final String PLURAL_NAME = "licenses"; + private boolean custom = false; private String text; @@ -37,4 +39,9 @@ public class LicenseRest implements RestModel { public String getType() { return NAME; } + + @Override + public String getTypePlural() { + return PLURAL_NAME; + } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/MetadataFieldRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/MetadataFieldRest.java index 2e17f98644..f73e51cb7e 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/MetadataFieldRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/MetadataFieldRest.java @@ -68,6 +68,11 @@ public class MetadataFieldRest extends BaseObjectRest { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + @Override public Class getController() { return RestResourceController.class; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/MetadataSchemaRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/MetadataSchemaRest.java index fd4ada6651..c762ccc65b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/MetadataSchemaRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/MetadataSchemaRest.java @@ -46,6 +46,11 @@ public class MetadataSchemaRest extends BaseObjectRest { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + @Override public Class getController() { return RestResourceController.class; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/OrcidHistoryRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/OrcidHistoryRest.java index a793006a23..2c4c7cbe60 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/OrcidHistoryRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/OrcidHistoryRest.java @@ -47,6 +47,11 @@ public class OrcidHistoryRest extends BaseObjectRest { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + @Override public String getCategory() { return CATEGORY; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/OrcidQueueRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/OrcidQueueRest.java index ba7c300628..13a0b474b6 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/OrcidQueueRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/OrcidQueueRest.java @@ -35,6 +35,11 @@ public class OrcidQueueRest extends BaseObjectRest { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + @Override public String getCategory() { return CATEGORY; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/PoolTaskRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/PoolTaskRest.java index c32c2c9578..0b66f0604b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/PoolTaskRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/PoolTaskRest.java @@ -50,6 +50,11 @@ public class PoolTaskRest extends BaseObjectRest { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + @Override public Class getController() { return RestResourceController.class; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ProcessFileTypesRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ProcessFileTypesRest.java index ecceea107e..eec9390388 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ProcessFileTypesRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ProcessFileTypesRest.java @@ -65,4 +65,9 @@ public class ProcessFileTypesRest extends BaseObjectRest { public String getType() { return NAME; } + + @Override + public String getTypePlural() { + return PLURAL_NAME; + } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ProcessRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ProcessRest.java index 8216e16171..8793d37f0d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ProcessRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ProcessRest.java @@ -55,6 +55,11 @@ public class ProcessRest extends BaseObjectRest { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + private String scriptName; private UUID userId; private Integer processId; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/PropertyRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/PropertyRest.java index d51f0f6fcc..c0e846fbb1 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/PropertyRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/PropertyRest.java @@ -60,4 +60,9 @@ public class PropertyRest extends BaseObjectRest { public String getType() { return NAME; } + + @Override + public String getTypePlural() { + return PLURAL_NAME; + } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RegistrationRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RegistrationRest.java index 9e525470e3..eb7c58a18c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RegistrationRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RegistrationRest.java @@ -74,4 +74,9 @@ public class RegistrationRest extends RestAddressableModel { public String getType() { return NAME; } + + @Override + public String getTypePlural() { + return PLURAL_NAME; + } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RelationshipRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RelationshipRest.java index a4ea162829..76a7a43486 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RelationshipRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RelationshipRest.java @@ -27,7 +27,7 @@ import org.dspace.app.rest.RestResourceController; public class RelationshipRest extends BaseObjectRest { public static final String NAME = "relationship"; public static final String PLURAL_NAME = "relationships"; - public static final String CATEGORY = "core"; + public static final String CATEGORY = RestModel.CORE; public static final String RELATIONSHIP_TYPE = "relationshipType"; @@ -48,6 +48,11 @@ public class RelationshipRest extends BaseObjectRest { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + public String getCategory() { return CATEGORY; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RelationshipTypeRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RelationshipTypeRest.java index 6bfc75621d..48a9cf499a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RelationshipTypeRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RelationshipTypeRest.java @@ -19,7 +19,7 @@ public class RelationshipTypeRest extends BaseObjectRest { public static final String NAME = "relationshiptype"; public static final String PLURAL_NAME = "relationshiptypes"; - public static final String CATEGORY = "core"; + public static final String CATEGORY = RestModel.CORE; private String leftwardType; private String rightwardType; @@ -36,6 +36,11 @@ public class RelationshipTypeRest extends BaseObjectRest { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + public String getCategory() { return CATEGORY; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RequestItemRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RequestItemRest.java index 616a2d631d..677405bda0 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RequestItemRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RequestItemRest.java @@ -21,11 +21,10 @@ import org.dspace.app.rest.RestResourceController; @LinksRest(links = { @LinkRest(name = "bitstream", method = "getUuid"), @LinkRest(name = "item", method = "getUuid") - }) -public class RequestItemRest - extends BaseObjectRest { +}) +public class RequestItemRest extends BaseObjectRest { public static final String NAME = "itemrequest"; - public static final String PLURAL_NAME = NAME + "s"; + public static final String PLURAL_NAME = "itemrequests"; public static final String CATEGORY = RestAddressableModel.TOOLS; @@ -230,6 +229,6 @@ public class RequestItemRest @Override public String getTypePlural() { - return super.getTypePlural(); + return PLURAL_NAME; } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ResearcherProfileRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ResearcherProfileRest.java index d6a717a922..13faa2e2bb 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ResearcherProfileRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ResearcherProfileRest.java @@ -70,6 +70,11 @@ public class ResearcherProfileRest extends BaseObjectRest { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + @Override public String getCategory() { return CATEGORY; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ResourcePolicyRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ResourcePolicyRest.java index a75c17b136..564782c251 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ResourcePolicyRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ResourcePolicyRest.java @@ -63,6 +63,11 @@ public class ResourcePolicyRest extends BaseObjectRest { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + @Override public String getCategory() { return CATEGORY; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RestModel.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RestModel.java index 5bc85a58b2..5775a4388e 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RestModel.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RestModel.java @@ -10,7 +10,6 @@ package org.dspace.app.rest.model; import java.io.Serializable; import com.fasterxml.jackson.annotation.JsonIgnore; -import org.atteo.evo.inflector.English; /** * A REST resource directly or indirectly (in a collection) exposed must have at @@ -34,11 +33,10 @@ public interface RestModel extends Serializable { public static final String VERSIONING = "versioning"; public static final String AUTHENTICATION = "authn"; public static final String TOOLS = "tools"; + public static final String PID = "pid"; public String getType(); @JsonIgnore - default public String getTypePlural() { - return English.plural(getType()); - } + String getTypePlural(); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RootRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RootRest.java index cef8965601..a4ca592cd5 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RootRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RootRest.java @@ -16,6 +16,7 @@ import org.dspace.app.rest.RootRestResourceController; */ public class RootRest extends RestAddressableModel { public static final String NAME = "root"; + public static final String PLURAL_NAME = NAME; public static final String CATEGORY = RestModel.ROOT; private String dspaceUI; private String dspaceName; @@ -30,6 +31,14 @@ public class RootRest extends RestAddressableModel { return NAME; } + /** + * The plural name is the same as the singular name + */ + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + public Class getController() { return RootRestResourceController.class; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ScriptRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ScriptRest.java index e9c0eb4203..58c68b37bd 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ScriptRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ScriptRest.java @@ -39,6 +39,11 @@ public class ScriptRest extends BaseObjectRest { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + public String getName() { return name; } 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 b25d827e75..f8a6e698d1 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 @@ -22,6 +22,7 @@ import org.dspace.discovery.configuration.DiscoverySearchFilter; public class SearchConfigurationRest extends BaseObjectRest { public static final String NAME = "discover"; + public static final String PLURAL_NAME = NAME; public static final String CATEGORY = RestModel.DISCOVER; @JsonIgnore private String scope; @@ -41,6 +42,14 @@ public class SearchConfigurationRest extends BaseObjectRest { return NAME; } + /** + * The plural name is the same as the singular name + */ + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + public Class getController() { return DiscoveryRestController.class; } 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 4206162638..d2a61ef8c0 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 @@ -44,6 +44,11 @@ public class SearchEventRest extends BaseObjectRest { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + public String getQuery() { return query; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SearchFacetEntryRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SearchFacetEntryRest.java index 513b4ad54a..aafa3a1108 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SearchFacetEntryRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SearchFacetEntryRest.java @@ -21,6 +21,7 @@ import org.dspace.discovery.configuration.DiscoverySearchFilterFacet; public class SearchFacetEntryRest extends RestAddressableModel { public static final String NAME = "discover"; + public static final String PLURAL_NAME = NAME; public static final String CATEGORY = RestModel.DISCOVER; private String name; @@ -54,6 +55,14 @@ public class SearchFacetEntryRest extends RestAddressableModel { return NAME; } + /** + * The plural name is the same as the singular name + */ + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + public Class getController() { return DiscoveryRestController.class; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SearchFacetValueRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SearchFacetValueRest.java index 3f5be08712..97860eff4c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SearchFacetValueRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SearchFacetValueRest.java @@ -16,6 +16,7 @@ import org.dspace.app.rest.DiscoveryRestController; public class SearchFacetValueRest extends RestAddressableModel { public static final String NAME = "discover"; + public static final String PLURAL_NAME = NAME; public static final String CATEGORY = RestModel.DISCOVER; private String label; @@ -36,6 +37,14 @@ public class SearchFacetValueRest extends RestAddressableModel { return NAME; } + /** + * The plural name is the same as the singular name + */ + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + public Class getController() { return DiscoveryRestController.class; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SearchResultEntryRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SearchResultEntryRest.java index ac1d94f5f5..ac4918833c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SearchResultEntryRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SearchResultEntryRest.java @@ -20,6 +20,7 @@ import org.dspace.app.rest.DiscoveryRestController; public class SearchResultEntryRest extends RestAddressableModel { public static final String NAME = "discover"; + public static final String PLURAL_NAME = NAME; public static final String CATEGORY = RestModel.DISCOVER; private Map> hitHighlights; @@ -35,6 +36,14 @@ public class SearchResultEntryRest extends RestAddressableModel { return NAME; } + /** + * The plural name is the same as the singular name + */ + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + @JsonIgnore public Class getController() { return DiscoveryRestController.class; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SearchSupportRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SearchSupportRest.java index 52c07ab13a..a0743b8cbb 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SearchSupportRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SearchSupportRest.java @@ -16,6 +16,7 @@ import org.dspace.app.rest.DiscoveryRestController; */ public class SearchSupportRest extends BaseObjectRest { public static final String NAME = "discover"; + public static final String PLURAL_NAME = NAME; public static final String CATEGORY = RestModel.DISCOVER; public String getCategory() { @@ -26,6 +27,14 @@ public class SearchSupportRest extends BaseObjectRest { return NAME; } + /** + * The plural name is the same as the singular name + */ + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + public Class getController() { return DiscoveryRestController.class; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SiteRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SiteRest.java index c45c1004f9..6217505edb 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SiteRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SiteRest.java @@ -30,6 +30,11 @@ public class SiteRest extends DSpaceObjectRest { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + @Override public boolean equals(Object object) { return (object instanceof SiteRest && diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/StatisticsSupportRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/StatisticsSupportRest.java index 55df71e8e6..e2d42c60a9 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/StatisticsSupportRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/StatisticsSupportRest.java @@ -12,6 +12,7 @@ import org.dspace.app.rest.StatisticsRestController; public class StatisticsSupportRest extends BaseObjectRest { public static final String NAME = "statistics"; + public static final String PLURAL_NAME = "statistics"; public static final String CATEGORY = RestModel.STATISTICS; public String getCategory() { @@ -25,4 +26,9 @@ public class StatisticsSupportRest extends BaseObjectRest { public String getType() { return NAME; } + + @Override + public String getTypePlural() { + return null; + } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionAccessOptionRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionAccessOptionRest.java index 407a39fca5..ad8fa68cdb 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionAccessOptionRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionAccessOptionRest.java @@ -64,6 +64,11 @@ public class SubmissionAccessOptionRest extends BaseObjectRest { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + @Override public String getCategory() { return CATEGORY; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionCCLicenseRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionCCLicenseRest.java index 57315cd1a5..7cc3c124d5 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionCCLicenseRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionCCLicenseRest.java @@ -66,6 +66,11 @@ public class SubmissionCCLicenseRest extends BaseObjectRest { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + @Override @JsonIgnore public Class getController() { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionCCLicenseUrlRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionCCLicenseUrlRest.java index 870fe02972..409cad5f52 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionCCLicenseUrlRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionCCLicenseUrlRest.java @@ -47,6 +47,11 @@ public class SubmissionCCLicenseUrlRest extends BaseObjectRest { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + @Override public String getCategory() { return SubmissionCCLicenseUrlRest.CATEGORY; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionDefinitionRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionDefinitionRest.java index 1316e3b91b..958701b5d6 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionDefinitionRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionDefinitionRest.java @@ -60,6 +60,11 @@ public class SubmissionDefinitionRest extends BaseObjectRest { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + public void setDefaultConf(boolean isDefault) { this.defaultConf = isDefault; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionFormRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionFormRest.java index fdc6b53e23..5ef3c87fb3 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionFormRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionFormRest.java @@ -62,6 +62,11 @@ public class SubmissionFormRest extends BaseObjectRest { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + @Override public Class getController() { return RestResourceController.class; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionSectionRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionSectionRest.java index 5a827554c5..9c5a1c8629 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionSectionRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionSectionRest.java @@ -52,6 +52,11 @@ public class SubmissionSectionRest extends BaseObjectRest { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + public ScopeEnum getScope() { return scope; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionUploadRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionUploadRest.java index 4d0cedc9fe..48d01a86d7 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionUploadRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionUploadRest.java @@ -54,6 +54,11 @@ public class SubmissionUploadRest extends BaseObjectRest { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + @Override public Class getController() { return RestResourceController.class; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubscriptionRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubscriptionRest.java index dd94cc6051..bec1be5fc0 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubscriptionRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubscriptionRest.java @@ -22,7 +22,7 @@ public class SubscriptionRest extends BaseObjectRest { public static final String NAME = "subscription"; public static final String PLURAL_NAME = "subscriptions"; - public static final String CATEGORY = "core"; + public static final String CATEGORY = RestModel.CORE; public static final String DSPACE_OBJECT = "resource"; public static final String EPERSON = "eperson"; @@ -45,6 +45,11 @@ public class SubscriptionRest extends BaseObjectRest { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + public void setSubscriptionType(String type) { this.subscriptionType = type; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SupervisionOrderRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SupervisionOrderRest.java index 75a391c9e4..f5e75f1751 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SupervisionOrderRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SupervisionOrderRest.java @@ -70,4 +70,9 @@ public class SupervisionOrderRest extends BaseObjectRest { public String getType() { return NAME; } + + @Override + public String getTypePlural() { + return PLURAL_NAME; + } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SystemWideAlertRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SystemWideAlertRest.java index 1fde31d45a..b0f3e9da4e 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SystemWideAlertRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SystemWideAlertRest.java @@ -35,6 +35,11 @@ public class SystemWideAlertRest extends BaseObjectRest { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + private Integer alertId; private String message; private String allowSessions; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/TemplateItemRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/TemplateItemRest.java index 48aa30ba16..d48c713d64 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/TemplateItemRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/TemplateItemRest.java @@ -68,6 +68,11 @@ public class TemplateItemRest extends BaseObjectRest { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + @Override public UUID getId() { return uuid; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/UsageReportPointCityRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/UsageReportPointCityRest.java index 369bcce4d1..0198aaf0cc 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/UsageReportPointCityRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/UsageReportPointCityRest.java @@ -15,12 +15,18 @@ package org.dspace.app.rest.model; */ public class UsageReportPointCityRest extends UsageReportPointRest { public static final String NAME = "city"; + public static final String PLURAL_NAME = "cities"; @Override public String getType() { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + @Override public void setId(String id) { super.id = id; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/UsageReportPointCountryRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/UsageReportPointCountryRest.java index 1d6723503e..fbfe9b9ee6 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/UsageReportPointCountryRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/UsageReportPointCountryRest.java @@ -19,6 +19,7 @@ import org.dspace.statistics.util.LocationUtils; */ public class UsageReportPointCountryRest extends UsageReportPointRest { public static final String NAME = "country"; + public static final String PLURAL_NAME = "countries"; @Override public void setLabel(String label) { @@ -36,4 +37,9 @@ public class UsageReportPointCountryRest extends UsageReportPointRest { public String getType() { return NAME; } + + @Override + public String getTypePlural() { + return PLURAL_NAME; + } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/UsageReportPointDateRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/UsageReportPointDateRest.java index e9b4ddea15..d3ccb0163d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/UsageReportPointDateRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/UsageReportPointDateRest.java @@ -15,12 +15,18 @@ package org.dspace.app.rest.model; */ public class UsageReportPointDateRest extends UsageReportPointRest { public static final String NAME = "date"; + public static final String PLURAL_NAME = "dates"; @Override public String getType() { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + @Override public void setId(String id) { super.id = id; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/UsageReportPointDsoTotalVisitsRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/UsageReportPointDsoTotalVisitsRest.java index fd8d334786..721d0b8dc2 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/UsageReportPointDsoTotalVisitsRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/UsageReportPointDsoTotalVisitsRest.java @@ -7,6 +7,8 @@ */ package org.dspace.app.rest.model; +import org.atteo.evo.inflector.English; + /** * This class serves as a REST representation of a TotalVisit data Point of a DSO's {@link UsageReportRest} from the * DSpace statistics @@ -25,6 +27,11 @@ public class UsageReportPointDsoTotalVisitsRest extends UsageReportPointRest { return this.type; } + @Override + public String getTypePlural() { + return English.plural(getType()); + } + /** * Sets the type of this {@link UsageReportPointRest} object, should be type of dso concerned (e.g. item, bitstream, ...) * diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/UsageReportPointRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/UsageReportPointRest.java index feb006486f..50a8fc46ba 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/UsageReportPointRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/UsageReportPointRest.java @@ -17,8 +17,9 @@ import org.dspace.app.rest.RestResourceController; * * @author Maria Verdonck (Atmire) on 08/06/2020 */ -public class UsageReportPointRest extends BaseObjectRest { +public abstract class UsageReportPointRest extends BaseObjectRest { public static final String NAME = "point"; + public static final String PLURAL_NAME = "points"; public static final String CATEGORY = RestModel.STATISTICS; protected String id; protected String label; @@ -54,6 +55,16 @@ public class UsageReportPointRest extends BaseObjectRest { return NAME; } + /** + * Returns the plural type of this {@link UsageReportPointRest} object + * + * @return Plural type of this {@link UsageReportPointRest} object + */ + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + /** * Returns the values of this {@link UsageReportPointRest} object, containing the amount of views * diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/UsageReportRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/UsageReportRest.java index 1be1a35263..a8b267daaf 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/UsageReportRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/UsageReportRest.java @@ -57,6 +57,11 @@ public class UsageReportRest extends BaseObjectRest { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + /** * Returns the report type of this UsageReport, options listed in * {@link org.dspace.app.rest.utils.UsageReportUtils}, e.g. diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VersionHistoryRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VersionHistoryRest.java index cb6294dd74..5aab7028a8 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VersionHistoryRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VersionHistoryRest.java @@ -52,6 +52,11 @@ public class VersionHistoryRest extends BaseObjectRest { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + /** * Generic getter for the id * @return the id value of this VersionHistoryRest diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VersionRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VersionRest.java index 1ad9806779..21bf82804d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VersionRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VersionRest.java @@ -138,4 +138,9 @@ public class VersionRest extends BaseObjectRest { public String getType() { return NAME; } + + @Override + public String getTypePlural() { + return PLURAL_NAME; + } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ViewEventRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ViewEventRest.java index 50163339ce..2806f7ed87 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ViewEventRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ViewEventRest.java @@ -68,4 +68,9 @@ public class ViewEventRest extends BaseObjectRest { public String getType() { return NAME; } + + @Override + public String getTypePlural() { + return PLURAL_NAME; + } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryDetailsRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryDetailsRest.java index 30e5eb71cb..e5869a8525 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryDetailsRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryDetailsRest.java @@ -82,6 +82,11 @@ public class VocabularyEntryDetailsRest extends BaseObjectRest { return VocabularyEntryDetailsRest.NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + @Override public Class getController() { return RestResourceController.class; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryRest.java index 713d4c5209..3977026efa 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryRest.java @@ -20,6 +20,7 @@ import com.fasterxml.jackson.annotation.JsonInclude.Include; */ public class VocabularyEntryRest implements RestModel { public static final String NAME = "vocabularyEntry"; + public static final String PLURAL_NAME = "vocabularyEntries"; @JsonInclude(Include.NON_NULL) private String authority; @@ -77,4 +78,9 @@ public class VocabularyEntryRest implements RestModel { public String getType() { return VocabularyEntryRest.NAME; } + + @Override + public String getTypePlural() { + return PLURAL_NAME; + } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyRest.java index b4a62ae383..f119059c2b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyRest.java @@ -76,6 +76,11 @@ public class VocabularyRest extends BaseObjectRest { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + @Override public Class getController() { return RestResourceController.class; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowActionRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowActionRest.java index cdfcfa1ee4..c240bee2fb 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowActionRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowActionRest.java @@ -21,7 +21,7 @@ import org.dspace.xmlworkflow.state.actions.ActionAdvancedInfo; */ public class WorkflowActionRest extends BaseObjectRest { - public static final String CATEGORY = "config"; + public static final String CATEGORY = RestModel.CONFIGURATION; public static final String NAME = "workflowaction"; public static final String PLURAL_NAME = "workflowactions"; @@ -44,6 +44,11 @@ public class WorkflowActionRest extends BaseObjectRest { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + public List getOptions() { return options; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowDefinitionRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowDefinitionRest.java index c31ca5be5b..0ec967d098 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowDefinitionRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowDefinitionRest.java @@ -29,7 +29,7 @@ import org.dspace.app.rest.RestResourceController; }) public class WorkflowDefinitionRest extends BaseObjectRest { - public static final String CATEGORY = "config"; + public static final String CATEGORY = RestModel.CONFIGURATION; public static final String NAME = "workflowdefinition"; public static final String PLURAL_NAME = "workflowdefinitions"; @@ -55,6 +55,11 @@ public class WorkflowDefinitionRest extends BaseObjectRest { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + @Override @JsonIgnore public String getId() { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowItemRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowItemRest.java index 226ee0eece..278d5de85d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowItemRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowItemRest.java @@ -37,6 +37,11 @@ public class WorkflowItemRest extends AInprogressSubmissionRest { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + @Override public Class getController() { return RestResourceController.class; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowStepRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowStepRest.java index 3a0491b95c..b3397721c1 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowStepRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowStepRest.java @@ -25,7 +25,7 @@ import org.dspace.app.rest.RestResourceController; }) public class WorkflowStepRest extends BaseObjectRest { - public static final String CATEGORY = "config"; + public static final String CATEGORY = RestModel.CONFIGURATION; public static final String NAME = "workflowstep"; public static final String PLURAL_NAME = "workflowsteps"; @@ -48,6 +48,11 @@ public class WorkflowStepRest extends BaseObjectRest { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + @JsonIgnore public List getWorkflowactions() { return workflowactions; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkspaceItemRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkspaceItemRest.java index 688f4c7496..f0ba0981da 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkspaceItemRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkspaceItemRest.java @@ -37,6 +37,11 @@ public class WorkspaceItemRest extends AInprogressSubmissionRest { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + @Override public Class getController() { return RestResourceController.class; 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 df80cd5c2d..d1123f50f3 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 @@ -49,6 +49,11 @@ public class LinksetRest extends RestAddressableModel { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + @Override public String getCategory() { return CATEGORY; 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 3ba09bf109..ce6668920a 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 @@ -62,6 +62,11 @@ public class TypedLinkRest extends RestAddressableModel { return type; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + @Override public String getCategory() { return CATEGORY; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/converter/ConverterServiceIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/converter/ConverterServiceIT.java index 685bd5dbfd..d9bcf60468 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/converter/ConverterServiceIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/converter/ConverterServiceIT.java @@ -296,6 +296,11 @@ public class ConverterServiceIT extends AbstractControllerIntegrationTest { public String getType() { return null; } + + @Override + public String getTypePlural() { + return null; + } } class MockHalResource extends HALResource { diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/model/MockObjectRest.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/model/MockObjectRest.java index 8586bf16bf..377705ad5f 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/model/MockObjectRest.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/model/MockObjectRest.java @@ -67,6 +67,11 @@ public class MockObjectRest extends BaseObjectRest { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + public String getValue() { return value; } From 4e598a833de3dabf065e91f5316b0d275bac0d2c Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Thu, 30 Nov 2023 16:23:19 +0100 Subject: [PATCH 292/412] 108915: Retrieve the link repositories using the plural model name --- .../src/main/java/org/dspace/app/rest/utils/Utils.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/Utils.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/Utils.java index ed6e26ed0f..76710d1c34 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/Utils.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/Utils.java @@ -343,11 +343,10 @@ public class Utils { * @return */ public LinkRestRepository getLinkResourceRepository(String apiCategory, String modelPlural, String rel) { - String model = makeSingular(modelPlural); try { - return applicationContext.getBean(apiCategory + "." + model + "." + rel, LinkRestRepository.class); + return applicationContext.getBean(apiCategory + "." + modelPlural + "." + rel, LinkRestRepository.class); } catch (NoSuchBeanDefinitionException e) { - throw new RepositoryNotFoundException(apiCategory, model); + throw new RepositoryNotFoundException(apiCategory, modelPlural); } } @@ -742,7 +741,7 @@ public class Utils { } Projection projection = resource.getContent().getProjection(); LinkRestRepository linkRepository = getLinkResourceRepository(resource.getContent().getCategory(), - resource.getContent().getType(), rel); + resource.getContent().getTypePlural(), rel); if (linkRepository.isEmbeddableRelation(resource.getContent(), rel)) { Method method = requireMethod(linkRepository.getClass(), linkRest.method()); Object contentId = getContentIdForLinkMethod(resource.getContent(), method); From 052766ad9d76b6c89cdb79159be53757bc617ce5 Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Fri, 1 Dec 2023 12:05:42 +0100 Subject: [PATCH 293/412] 108915: Refactored all regular repositories to use the plural model name instead of the singular --- .../app/rest/SubmissionCCLicenseUrlRepository.java | 2 +- .../app/rest/model/HarvestedCollectionRest.java | 4 ++-- .../AuthorizationFeatureRestRepository.java | 2 +- .../rest/repository/AuthorizationRestRepository.java | 2 +- .../repository/BitstreamFormatRestRepository.java | 2 +- .../app/rest/repository/BitstreamRestRepository.java | 2 +- .../rest/repository/BrowseIndexRestRepository.java | 2 +- .../BulkAccessConditionRestRepository.java | 4 ++-- .../rest/repository/ClaimedTaskRestRepository.java | 2 +- .../rest/repository/CollectionRestRepository.java | 2 +- .../app/rest/repository/CommunityRestRepository.java | 2 +- .../rest/repository/ConfigurationRestRepository.java | 2 +- .../app/rest/repository/DiscoveryRestRepository.java | 2 +- .../app/rest/repository/EPersonRestRepository.java | 2 +- .../rest/repository/EntityTypeRestRepository.java | 2 +- .../repository/ExternalSourceRestRepository.java | 2 +- .../app/rest/repository/FeedbackRestRepository.java | 4 ++-- .../app/rest/repository/GroupRestRepository.java | 2 +- .../HarvestedCollectionRestRepository.java | 2 +- .../rest/repository/IdentifierRestRepository.java | 12 ++++-------- .../app/rest/repository/ItemRestRepository.java | 2 +- .../rest/repository/MetadataFieldRestRepository.java | 2 +- .../repository/MetadataSchemaRestRepository.java | 2 +- .../rest/repository/OrcidHistoryRestRepository.java | 2 +- .../rest/repository/OrcidQueueRestRepository.java | 2 +- .../app/rest/repository/PoolTaskRestRepository.java | 2 +- .../app/rest/repository/ProcessRestRepository.java | 2 +- .../rest/repository/RegistrationRestRepository.java | 2 +- .../rest/repository/RelationshipRestRepository.java | 2 +- .../repository/RelationshipTypeRestRepository.java | 2 +- .../app/rest/repository/RequestItemRepository.java | 2 +- .../repository/ResearcherProfileRestRepository.java | 2 +- .../repository/ResourcePolicyRestRepository.java | 2 +- .../app/rest/repository/ScriptRestRepository.java | 2 +- .../rest/repository/SearchEventRestRepository.java | 2 +- .../app/rest/repository/SiteRestRepository.java | 2 +- .../rest/repository/StatisticsRestRepository.java | 2 +- .../SubmissionAccessOptionRestRepository.java | 4 ++-- .../SubmissionCCLicenseRestRepository.java | 2 +- .../SubmissionDefinitionRestRepository.java | 2 +- .../repository/SubmissionFormRestRepository.java | 2 +- .../repository/SubmissionPanelRestRepository.java | 2 +- .../repository/SubmissionUploadRestRepository.java | 2 +- .../rest/repository/SubscriptionRestRepository.java | 2 +- .../repository/SupervisionOrderRestRepository.java | 2 +- .../repository/SystemWideAlertRestRepository.java | 2 +- .../rest/repository/TemplateItemRestRepository.java | 2 +- .../repository/VersionHistoryRestRepository.java | 2 +- .../app/rest/repository/VersionRestRepository.java | 2 +- .../app/rest/repository/ViewEventRestRepository.java | 2 +- .../VocabularyEntryDetailsRestRepository.java | 2 +- .../rest/repository/VocabularyRestRepository.java | 2 +- .../repository/WorkflowActionRestRepository.java | 2 +- .../repository/WorkflowDefinitionRestRepository.java | 2 +- .../rest/repository/WorkflowItemRestRepository.java | 2 +- .../rest/repository/WorkflowStepRestRepository.java | 2 +- .../rest/repository/WorkspaceItemRestRepository.java | 2 +- .../rest/repository/MockObjectRestRepository.java | 2 +- 58 files changed, 65 insertions(+), 69 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/SubmissionCCLicenseUrlRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/SubmissionCCLicenseUrlRepository.java index 7e30413d8d..d65e569c80 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/SubmissionCCLicenseUrlRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/SubmissionCCLicenseUrlRepository.java @@ -38,7 +38,7 @@ import org.springframework.stereotype.Component; * It only supports a search method */ -@Component(SubmissionCCLicenseUrlRest.CATEGORY + "." + SubmissionCCLicenseUrlRest.NAME) +@Component(SubmissionCCLicenseUrlRest.CATEGORY + "." + SubmissionCCLicenseUrlRest.PLURAL_NAME) public class SubmissionCCLicenseUrlRepository extends DSpaceRestRepository implements InitializingBean { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/HarvestedCollectionRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/HarvestedCollectionRest.java index 3cbd98b9d4..ae3711b950 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/HarvestedCollectionRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/HarvestedCollectionRest.java @@ -20,8 +20,8 @@ import com.fasterxml.jackson.annotation.JsonProperty; */ public class HarvestedCollectionRest extends BaseObjectRest { - public static final String NAME = "collections"; - public static final String PLURAL_NAME = NAME; + public static final String NAME = "harvestedcollection"; + public static final String PLURAL_NAME = "harvestedcollections"; public static final String CATEGORY = RestModel.CORE; @JsonProperty("harvest_type") diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AuthorizationFeatureRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AuthorizationFeatureRestRepository.java index 62781fe8e8..e161b07ed0 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AuthorizationFeatureRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AuthorizationFeatureRestRepository.java @@ -28,7 +28,7 @@ import org.springframework.stereotype.Component; * * @author Andrea Bollini (andrea.bollini at 4science.it) */ -@Component(AuthorizationFeatureRest.CATEGORY + "." + AuthorizationFeatureRest.NAME) +@Component(AuthorizationFeatureRest.CATEGORY + "." + AuthorizationFeatureRest.PLURAL_NAME) public class AuthorizationFeatureRestRepository extends DSpaceRestRepository { @Autowired diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AuthorizationRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AuthorizationRestRepository.java index 1379528669..c5e0975730 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AuthorizationRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AuthorizationRestRepository.java @@ -49,7 +49,7 @@ import org.springframework.stereotype.Component; * @author Andrea Bollini (andrea.bollini at 4science.it) */ -@Component(AuthorizationRest.CATEGORY + "." + AuthorizationRest.NAME) +@Component(AuthorizationRest.CATEGORY + "." + AuthorizationRest.PLURAL_NAME) public class AuthorizationRestRepository extends DSpaceRestRepository { private static final Logger log = LoggerFactory.getLogger(AuthorizationRestRepository.class); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BitstreamFormatRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BitstreamFormatRestRepository.java index 49585ee9db..c4c1981f9d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BitstreamFormatRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BitstreamFormatRestRepository.java @@ -35,7 +35,7 @@ import org.springframework.stereotype.Component; * * @author Andrea Bollini (andrea.bollini at 4science.it) */ -@Component(BitstreamFormatRest.CATEGORY + "." + BitstreamFormatRest.NAME) +@Component(BitstreamFormatRest.CATEGORY + "." + BitstreamFormatRest.PLURAL_NAME) public class BitstreamFormatRestRepository extends DSpaceRestRepository { @Autowired 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 12e27dccac..57a9a03add 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 @@ -56,7 +56,7 @@ import org.springframework.stereotype.Component; * @author Andrea Bollini (andrea.bollini at 4science.it) */ -@Component(BitstreamRest.CATEGORY + "." + BitstreamRest.NAME) +@Component(BitstreamRest.CATEGORY + "." + BitstreamRest.PLURAL_NAME) public class BitstreamRestRepository extends DSpaceObjectRestRepository { private final BitstreamService bs; 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 6aedcee6c0..315ea8dbe2 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 @@ -32,7 +32,7 @@ import org.springframework.stereotype.Component; * * @author Andrea Bollini (andrea.bollini at 4science.it) */ -@Component(BrowseIndexRest.CATEGORY + "." + BrowseIndexRest.NAME) +@Component(BrowseIndexRest.CATEGORY + "." + BrowseIndexRest.PLURAL_NAME) public class BrowseIndexRestRepository extends DSpaceRestRepository { @Autowired 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 2bf25978ef..7d46031134 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 @@ -27,7 +27,7 @@ import org.springframework.stereotype.Component; * * @author Mohamed Eskander (mohamed.eskander at 4science.it) */ -@Component(BulkAccessConditionRest.CATEGORY + "." + BulkAccessConditionRest.NAME) +@Component(BulkAccessConditionRest.CATEGORY + "." + BulkAccessConditionRest.PLURAL_NAME) public class BulkAccessConditionRestRepository extends DSpaceRestRepository { @Autowired @@ -82,4 +82,4 @@ public class BulkAccessConditionRestRepository extends DSpaceRestRepository implements InitializingBean { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionRestRepository.java index ba3163a444..6fcae23453 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionRestRepository.java @@ -81,7 +81,7 @@ import org.springframework.web.multipart.MultipartFile; * @author Andrea Bollini (andrea.bollini at 4science.it) */ -@Component(CollectionRest.CATEGORY + "." + CollectionRest.NAME) +@Component(CollectionRest.CATEGORY + "." + CollectionRest.PLURAL_NAME) public class CollectionRestRepository extends DSpaceObjectRestRepository { public static Logger log = org.apache.logging.log4j.LogManager.getLogger(CollectionRestRepository.class); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CommunityRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CommunityRestRepository.java index 927b6a0b98..7d060319d9 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CommunityRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CommunityRestRepository.java @@ -61,7 +61,7 @@ import org.springframework.web.multipart.MultipartFile; * @author Andrea Bollini (andrea.bollini at 4science.it) */ -@Component(CommunityRest.CATEGORY + "." + CommunityRest.NAME) +@Component(CommunityRest.CATEGORY + "." + CommunityRest.PLURAL_NAME) public class CommunityRestRepository extends DSpaceObjectRestRepository { private static final Logger log = org.apache.logging.log4j.LogManager diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ConfigurationRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ConfigurationRestRepository.java index caadb9f6f3..b524ca776d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ConfigurationRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ConfigurationRestRepository.java @@ -24,7 +24,7 @@ import org.springframework.stereotype.Component; /** * This is the repository responsible of exposing configuration properties */ -@Component(PropertyRest.CATEGORY + "." + PropertyRest.NAME) +@Component(PropertyRest.CATEGORY + "." + PropertyRest.PLURAL_NAME) public class ConfigurationRestRepository extends DSpaceRestRepository { private ConfigurationService configurationService; 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 4b9b7d7644..fb2bff8e55 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 @@ -44,7 +44,7 @@ import org.springframework.stereotype.Component; * information lookup * that has to be done for the endpoint */ -@Component(SearchResultsRest.CATEGORY + "." + SearchResultsRest.NAME) +@Component(SearchResultsRest.CATEGORY + "." + SearchResultsRest.PLURAL_NAME) public class DiscoveryRestRepository extends AbstractDSpaceRestRepository { private static final Logger log = LogManager.getLogger(); 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 062f7b7a94..4657064dc4 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 @@ -58,7 +58,7 @@ import org.springframework.stereotype.Component; * @author Andrea Bollini (andrea.bollini at 4science.it) */ -@Component(EPersonRest.CATEGORY + "." + EPersonRest.NAME) +@Component(EPersonRest.CATEGORY + "." + EPersonRest.PLURAL_NAME) public class EPersonRestRepository extends DSpaceObjectRestRepository implements InitializingBean { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EntityTypeRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EntityTypeRestRepository.java index eec67b6fc9..4ad7aa092c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EntityTypeRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EntityTypeRestRepository.java @@ -31,7 +31,7 @@ import org.springframework.stereotype.Component; /** * This is the repository that is responsible to manage EntityType Rest objects */ -@Component(EntityTypeRest.CATEGORY + "." + EntityTypeRest.NAME) +@Component(EntityTypeRest.CATEGORY + "." + EntityTypeRest.PLURAL_NAME) public class EntityTypeRestRepository extends DSpaceRestRepository { @Autowired diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ExternalSourceRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ExternalSourceRestRepository.java index 7888603f19..736261cd26 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ExternalSourceRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ExternalSourceRestRepository.java @@ -31,7 +31,7 @@ import org.springframework.stereotype.Component; * This is the Repository that is responsible for the functionality and implementations coming from * {@link org.dspace.app.rest.ExternalSourcesRestController} */ -@Component(ExternalSourceRest.CATEGORY + "." + ExternalSourceRest.NAME) +@Component(ExternalSourceRest.CATEGORY + "." + ExternalSourceRest.PLURAL_NAME) public class ExternalSourceRestRepository extends DSpaceRestRepository { @Autowired diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/FeedbackRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/FeedbackRestRepository.java index 8fd28bbe94..aee8cf6005 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/FeedbackRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/FeedbackRestRepository.java @@ -33,7 +33,7 @@ import org.springframework.stereotype.Component; * * @author Mykhaylo Boychuk (mykhaylo.boychuk@4science.com) */ -@Component(FeedbackRest.CATEGORY + "." + FeedbackRest.NAME) +@Component(FeedbackRest.CATEGORY + "." + FeedbackRest.PLURAL_NAME) public class FeedbackRestRepository extends DSpaceRestRepository { @Autowired @@ -100,4 +100,4 @@ public class FeedbackRestRepository extends DSpaceRestRepository { @Autowired GroupService gs; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/HarvestedCollectionRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/HarvestedCollectionRestRepository.java index ccacc24418..d7f12a1813 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/HarvestedCollectionRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/HarvestedCollectionRestRepository.java @@ -35,7 +35,7 @@ import org.springframework.stereotype.Component; * * @author Jelle Pelgrims (jelle.pelgrims at atmire.com) */ -@Component(HarvestedCollectionRest.CATEGORY + "." + HarvestedCollectionRest.NAME) +@Component(HarvestedCollectionRest.CATEGORY + "." + HarvestedCollectionRest.PLURAL_NAME) public class HarvestedCollectionRestRepository extends AbstractDSpaceRestRepository { @Autowired 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 1be569d18e..75b19b8449 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 @@ -73,8 +73,8 @@ import org.springframework.web.bind.annotation.RestController; * @author Kim Shepherd */ @RestController -@RequestMapping("/api/" + IdentifierRestRepository.CATEGORY) -@Component(IdentifierRestRepository.CATEGORY + "." + IdentifierRestRepository.NAME) +@RequestMapping("/api/" + IdentifierRest.CATEGORY) +@Component(IdentifierRest.CATEGORY + "." + IdentifierRest.PLURAL_NAME) public class IdentifierRestRepository extends DSpaceRestRepository implements InitializingBean { @Autowired private DiscoverableEndpointsService discoverableEndpointsService; @@ -87,10 +87,6 @@ public class IdentifierRestRepository extends DSpaceRestRepository(results, pageable, results.size()); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemRestRepository.java index 16ce8629d1..a1659c58d3 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemRestRepository.java @@ -59,7 +59,7 @@ import org.springframework.stereotype.Component; * @author Andrea Bollini (andrea.bollini at 4science.it) */ -@Component(ItemRest.CATEGORY + "." + ItemRest.NAME) +@Component(ItemRest.CATEGORY + "." + ItemRest.PLURAL_NAME) public class ItemRestRepository extends DSpaceObjectRestRepository { private static final Logger log = LogManager.getLogger(ItemRestRepository.class); 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 5152f11902..a6e6bd34ed 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 @@ -55,7 +55,7 @@ import org.springframework.stereotype.Component; * * @author Andrea Bollini (andrea.bollini at 4science.it) */ -@Component(MetadataFieldRest.CATEGORY + "." + MetadataFieldRest.NAME) +@Component(MetadataFieldRest.CATEGORY + "." + MetadataFieldRest.PLURAL_NAME) public class MetadataFieldRestRepository extends DSpaceRestRepository { /** * log4j logger 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 d9c148b71c..c57d33b150 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 @@ -38,7 +38,7 @@ import org.springframework.stereotype.Component; * * @author Andrea Bollini (andrea.bollini at 4science.it) */ -@Component(MetadataSchemaRest.CATEGORY + "." + MetadataSchemaRest.NAME) +@Component(MetadataSchemaRest.CATEGORY + "." + MetadataSchemaRest.PLURAL_NAME) public class MetadataSchemaRestRepository extends DSpaceRestRepository { @Autowired diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/OrcidHistoryRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/OrcidHistoryRestRepository.java index 0c44baaf0d..e804654907 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/OrcidHistoryRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/OrcidHistoryRestRepository.java @@ -32,7 +32,7 @@ import org.springframework.stereotype.Component; * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.it) * */ -@Component(OrcidHistoryRest.CATEGORY + "." + OrcidHistoryRest.NAME) +@Component(OrcidHistoryRest.CATEGORY + "." + OrcidHistoryRest.PLURAL_NAME) @ConditionalOnProperty("orcid.synchronization-enabled") public class OrcidHistoryRestRepository extends DSpaceRestRepository { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/OrcidQueueRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/OrcidQueueRestRepository.java index 0a1614cded..9334103fb6 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/OrcidQueueRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/OrcidQueueRestRepository.java @@ -35,7 +35,7 @@ import org.springframework.stereotype.Component; * @author Luca Giamminonni (luca.giamminonni at 4science.it) * */ -@Component(OrcidQueueRest.CATEGORY + "." + OrcidQueueRest.NAME) +@Component(OrcidQueueRest.CATEGORY + "." + OrcidQueueRest.PLURAL_NAME) @ConditionalOnProperty("orcid.synchronization-enabled") public class OrcidQueueRestRepository extends DSpaceRestRepository { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/PoolTaskRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/PoolTaskRestRepository.java index da5253608e..18995b4a02 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/PoolTaskRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/PoolTaskRestRepository.java @@ -48,7 +48,7 @@ import org.springframework.stereotype.Component; * @author Andrea Bollini (andrea.bollini at 4science.it) */ -@Component(PoolTaskRest.CATEGORY + "." + PoolTaskRest.NAME) +@Component(PoolTaskRest.CATEGORY + "." + PoolTaskRest.PLURAL_NAME) public class PoolTaskRestRepository extends DSpaceRestRepository implements InitializingBean { 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 2479eeda97..0e5a7e4e93 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 @@ -47,7 +47,7 @@ import org.springframework.stereotype.Component; /** * The repository for the Process workload */ -@Component(ProcessRest.CATEGORY + "." + ProcessRest.NAME) +@Component(ProcessRest.CATEGORY + "." + ProcessRest.PLURAL_NAME) public class ProcessRestRepository extends DSpaceRestRepository { private static final Logger log = LogManager.getLogger(); 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 be28170d8a..6d99e94399 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 @@ -48,7 +48,7 @@ import org.springframework.stereotype.Component; /** * This is the repository that is responsible for managing Registration Rest objects */ -@Component(RegistrationRest.CATEGORY + "." + RegistrationRest.NAME) +@Component(RegistrationRest.CATEGORY + "." + RegistrationRest.PLURAL_NAME) public class RegistrationRestRepository extends DSpaceRestRepository { private static Logger log = LogManager.getLogger(RegistrationRestRepository.class); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RelationshipRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RelationshipRestRepository.java index 21f43ddacd..38b9b3545c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RelationshipRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RelationshipRestRepository.java @@ -50,7 +50,7 @@ import org.springframework.stereotype.Component; /** * This is the repository that is responsible to manage Relationship Rest objects */ -@Component(RelationshipRest.CATEGORY + "." + RelationshipRest.NAME) +@Component(RelationshipRest.CATEGORY + "." + RelationshipRest.PLURAL_NAME) public class RelationshipRestRepository extends DSpaceRestRepository { private static final Logger log = LogManager.getLogger(); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RelationshipTypeRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RelationshipTypeRestRepository.java index a4e65d75a3..31a1e09168 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RelationshipTypeRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RelationshipTypeRestRepository.java @@ -29,7 +29,7 @@ import org.springframework.stereotype.Component; /** * This is the repository that is responsible to manage RelationshipType Rest objects */ -@Component(RelationshipTypeRest.CATEGORY + "." + RelationshipTypeRest.NAME) +@Component(RelationshipTypeRest.CATEGORY + "." + RelationshipTypeRest.PLURAL_NAME) public class RelationshipTypeRestRepository extends DSpaceRestRepository { @Autowired 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 7c0694c52f..18ec1e6fe2 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 @@ -54,7 +54,7 @@ import org.springframework.stereotype.Component; * * @author Mark H. Wood */ -@Component(RequestItemRest.CATEGORY + '.' + RequestItemRest.NAME) +@Component(RequestItemRest.CATEGORY + '.' + RequestItemRest.PLURAL_NAME) public class RequestItemRepository extends DSpaceRestRepository { private static final Logger LOG = LogManager.getLogger(); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ResearcherProfileRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ResearcherProfileRestRepository.java index 37717f6268..a2fc2f01be 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ResearcherProfileRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ResearcherProfileRestRepository.java @@ -45,7 +45,7 @@ import org.springframework.stereotype.Component; * @author Luca Giamminonni (luca.giamminonni at 4science.it) * */ -@Component(ResearcherProfileRest.CATEGORY + "." + ResearcherProfileRest.NAME) +@Component(ResearcherProfileRest.CATEGORY + "." + ResearcherProfileRest.PLURAL_NAME) @ConditionalOnProperty(value = "researcher-profile.entity-type") public class ResearcherProfileRestRepository extends DSpaceRestRepository { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ResourcePolicyRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ResourcePolicyRestRepository.java index 0b77f96b9b..31b4333103 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ResourcePolicyRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ResourcePolicyRestRepository.java @@ -51,7 +51,7 @@ import org.springframework.stereotype.Component; * * @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.it) */ -@Component(ResourcePolicyRest.CATEGORY + "." + ResourcePolicyRest.NAME) +@Component(ResourcePolicyRest.CATEGORY + "." + ResourcePolicyRest.PLURAL_NAME) public class ResourcePolicyRestRepository extends DSpaceRestRepository implements InitializingBean { 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 1eea06a4ee..e26f91c889 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 @@ -46,7 +46,7 @@ import org.springframework.web.multipart.MultipartFile; /** * This is the REST repository dealing with the Script logic */ -@Component(ScriptRest.CATEGORY + "." + ScriptRest.NAME) +@Component(ScriptRest.CATEGORY + "." + ScriptRest.PLURAL_NAME) public class ScriptRestRepository extends DSpaceRestRepository { private static final Logger log = LogManager.getLogger(); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SearchEventRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SearchEventRestRepository.java index b55536d75e..9bbc2b4c26 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SearchEventRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SearchEventRestRepository.java @@ -25,7 +25,7 @@ import org.dspace.usage.UsageSearchEvent; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; -@Component(SearchEventRest.CATEGORY + "." + SearchEventRest.NAME) +@Component(SearchEventRest.CATEGORY + "." + SearchEventRest.PLURAL_NAME) public class SearchEventRestRepository extends AbstractDSpaceRestRepository { @Autowired diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SiteRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SiteRestRepository.java index bf13700078..d4ca14f6da 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SiteRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SiteRestRepository.java @@ -31,7 +31,7 @@ import org.springframework.stereotype.Component; * @author Andrea Bollini (andrea.bollini at 4science.it) */ -@Component(SiteRest.CATEGORY + "." + SiteRest.NAME) +@Component(SiteRest.CATEGORY + "." + SiteRest.PLURAL_NAME) public class SiteRestRepository extends DSpaceObjectRestRepository { private final SiteService sitesv; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/StatisticsRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/StatisticsRestRepository.java index 09588a6045..d1336e5d8a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/StatisticsRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/StatisticsRestRepository.java @@ -31,7 +31,7 @@ import org.springframework.data.rest.webmvc.ResourceNotFoundException; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Component; -@Component(StatisticsSupportRest.CATEGORY + "." + UsageReportRest.NAME) +@Component(StatisticsSupportRest.CATEGORY + "." + UsageReportRest.PLURAL_NAME) public class StatisticsRestRepository extends DSpaceRestRepository { @Autowired diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionAccessOptionRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionAccessOptionRestRepository.java index a4117a6176..85893dd161 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionAccessOptionRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionAccessOptionRestRepository.java @@ -25,7 +25,7 @@ import org.springframework.stereotype.Component; * * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.com) */ -@Component(SubmissionAccessOptionRest.CATEGORY + "." + SubmissionAccessOptionRest.NAME) +@Component(SubmissionAccessOptionRest.CATEGORY + "." + SubmissionAccessOptionRest.PLURAL_NAME) public class SubmissionAccessOptionRestRepository extends DSpaceRestRepository { @Autowired @@ -48,4 +48,4 @@ public class SubmissionAccessOptionRestRepository extends DSpaceRestRepository { @Autowired 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 b358b96847..2a4b28af52 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 @@ -31,7 +31,7 @@ import org.springframework.stereotype.Component; * * @author Andrea Bollini (andrea.bollini at 4science.it) */ -@Component(SubmissionDefinitionRest.CATEGORY + "." + SubmissionDefinitionRest.NAME) +@Component(SubmissionDefinitionRest.CATEGORY + "." + SubmissionDefinitionRest.PLURAL_NAME) public class SubmissionDefinitionRestRepository extends DSpaceRestRepository { private SubmissionConfigReader submissionConfigReader; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionFormRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionFormRestRepository.java index 76d680cf27..a5b9592aa0 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionFormRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionFormRestRepository.java @@ -28,7 +28,7 @@ import org.springframework.stereotype.Component; * * @author Andrea Bollini (andrea.bollini at 4science.it) */ -@Component(SubmissionFormRest.CATEGORY + "." + SubmissionFormRest.NAME) +@Component(SubmissionFormRest.CATEGORY + "." + SubmissionFormRest.PLURAL_NAME) public class SubmissionFormRestRepository extends DSpaceRestRepository { private Map inputReaders; private DCInputsReader defaultInputReader; 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 2046a816eb..2b8e1b60a0 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 @@ -27,7 +27,7 @@ import org.springframework.stereotype.Component; * * @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.it) */ -@Component(SubmissionDefinitionRest.CATEGORY + "." + SubmissionSectionRest.NAME) +@Component(SubmissionDefinitionRest.CATEGORY + "." + SubmissionSectionRest.PLURAL_NAME) public class SubmissionPanelRestRepository extends DSpaceRestRepository { private SubmissionConfigReader submissionConfigReader; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionUploadRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionUploadRestRepository.java index 24b6b96961..9b5bed759e 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionUploadRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionUploadRestRepository.java @@ -36,7 +36,7 @@ import org.springframework.stereotype.Component; * * @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.it) */ -@Component(SubmissionUploadRest.CATEGORY + "." + SubmissionUploadRest.NAME) +@Component(SubmissionUploadRest.CATEGORY + "." + SubmissionUploadRest.PLURAL_NAME) public class SubmissionUploadRestRepository extends DSpaceRestRepository { private static final Logger log = org.apache.logging.log4j.LogManager diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java index e6ee87b368..7f536d1126 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java @@ -61,7 +61,7 @@ import org.springframework.stereotype.Component; * * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.com) */ -@Component(SubscriptionRest.CATEGORY + "." + SubscriptionRest.NAME) +@Component(SubscriptionRest.CATEGORY + "." + SubscriptionRest.PLURAL_NAME) public class SubscriptionRestRepository extends DSpaceRestRepository implements InitializingBean { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SupervisionOrderRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SupervisionOrderRestRepository.java index fb2f589dbc..56cdecdd46 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SupervisionOrderRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SupervisionOrderRestRepository.java @@ -52,7 +52,7 @@ import org.springframework.stereotype.Component; * * @author Mohamed Eskander (mohamed.eskander at 4science dot it) */ -@Component(SupervisionOrderRest.CATEGORY + "." + SupervisionOrderRest.NAME) +@Component(SupervisionOrderRest.CATEGORY + "." + SupervisionOrderRest.PLURAL_NAME) public class SupervisionOrderRestRepository extends DSpaceRestRepository { private static final Logger log = diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SystemWideAlertRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SystemWideAlertRestRepository.java index 73544145b2..a5b0dfc0fc 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SystemWideAlertRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SystemWideAlertRestRepository.java @@ -40,7 +40,7 @@ import org.springframework.stereotype.Component; /** * The repository for the SystemWideAlert workload */ -@Component(SystemWideAlertRest.CATEGORY + "." + SystemWideAlertRest.NAME) +@Component(SystemWideAlertRest.CATEGORY + "." + SystemWideAlertRest.PLURAL_NAME) public class SystemWideAlertRestRepository extends DSpaceRestRepository { private static final Logger log = LogManager.getLogger(); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/TemplateItemRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/TemplateItemRestRepository.java index ad80140481..1bacaa4ddb 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/TemplateItemRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/TemplateItemRestRepository.java @@ -34,7 +34,7 @@ import org.springframework.stereotype.Component; /** * This is the repository class that is responsible for handling {@link TemplateItemRest} objects */ -@Component(TemplateItemRest.CATEGORY + "." + TemplateItemRest.NAME) +@Component(TemplateItemRest.CATEGORY + "." + TemplateItemRest.PLURAL_NAME) public class TemplateItemRestRepository extends DSpaceRestRepository { @Autowired diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionHistoryRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionHistoryRestRepository.java index f41003d17d..9207add5f7 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionHistoryRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionHistoryRestRepository.java @@ -26,7 +26,7 @@ import org.springframework.stereotype.Component; /** * Repository for the operations on the {@link VersionHistoryRest} endpoints */ -@Component(VersionHistoryRest.CATEGORY + "." + VersionHistoryRest.NAME) +@Component(VersionHistoryRest.CATEGORY + "." + VersionHistoryRest.PLURAL_NAME) public class VersionHistoryRestRepository extends DSpaceRestRepository { private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(VersionHistoryRestRepository.class); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionRestRepository.java index 585fc1de9f..665e8637cd 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionRestRepository.java @@ -48,7 +48,7 @@ import org.springframework.stereotype.Component; * * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.it) */ -@Component(VersionRest.CATEGORY + "." + VersionRest.NAME) +@Component(VersionRest.CATEGORY + "." + VersionRest.PLURAL_NAME) public class VersionRestRepository extends DSpaceRestRepository implements ReloadableEntityObjectRepository { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ViewEventRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ViewEventRestRepository.java index d49f07d439..5dc032d4d6 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ViewEventRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ViewEventRestRepository.java @@ -31,7 +31,7 @@ import org.dspace.usage.UsageEvent; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; -@Component(ViewEventRest.CATEGORY + "." + ViewEventRest.NAME) +@Component(ViewEventRest.CATEGORY + "." + ViewEventRest.PLURAL_NAME) public class ViewEventRestRepository extends AbstractDSpaceRestRepository { @Autowired diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsRestRepository.java index 3e4d600b12..cf74dd76f4 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsRestRepository.java @@ -40,7 +40,7 @@ import org.springframework.stereotype.Component; * * @author Andrea Bollini (andrea.bollini at 4science.it) */ -@Component(VocabularyRest.CATEGORY + "." + VocabularyEntryDetailsRest.NAME) +@Component(VocabularyRest.CATEGORY + "." + VocabularyEntryDetailsRest.PLURAL_NAME) public class VocabularyEntryDetailsRestRepository extends DSpaceRestRepository implements InitializingBean { 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 fcc37d1316..dbc4647a2c 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 @@ -38,7 +38,7 @@ import org.springframework.stereotype.Component; * @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.it) * @author Andrea Bollini (andrea.bollini at 4science.it) */ -@Component(VocabularyRest.CATEGORY + "." + VocabularyRest.NAME) +@Component(VocabularyRest.CATEGORY + "." + VocabularyRest.PLURAL_NAME) public class VocabularyRestRepository extends DSpaceRestRepository { @Autowired diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkflowActionRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkflowActionRestRepository.java index 75c2e5c140..92efe98494 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkflowActionRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkflowActionRestRepository.java @@ -24,7 +24,7 @@ import org.springframework.stereotype.Component; * * @author Maria Verdonck (Atmire) on 06/01/2020 */ -@Component(WorkflowActionRest.CATEGORY + "." + WorkflowActionRest.NAME) +@Component(WorkflowActionRest.CATEGORY + "." + WorkflowActionRest.PLURAL_NAME) public class WorkflowActionRestRepository extends DSpaceRestRepository { @Autowired diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkflowDefinitionRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkflowDefinitionRestRepository.java index 7aa6d7d280..8c4836a23d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkflowDefinitionRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkflowDefinitionRestRepository.java @@ -32,7 +32,7 @@ import org.springframework.stereotype.Component; * * @author Maria Verdonck (Atmire) on 11/12/2019 */ -@Component(WorkflowDefinitionRest.CATEGORY + "." + WorkflowDefinitionRest.NAME) +@Component(WorkflowDefinitionRest.CATEGORY + "." + WorkflowDefinitionRest.PLURAL_NAME) public class WorkflowDefinitionRestRepository extends DSpaceRestRepository { @Autowired 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 ee8dc12e73..983713ff7b 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 @@ -66,7 +66,7 @@ import org.springframework.web.multipart.MultipartFile; * @author Andrea Bollini (andrea.bollini at 4science.it) */ -@Component(WorkflowItemRest.CATEGORY + "." + WorkflowItemRest.NAME) +@Component(WorkflowItemRest.CATEGORY + "." + WorkflowItemRest.PLURAL_NAME) public class WorkflowItemRestRepository extends DSpaceRestRepository { public static final String OPERATION_PATH_SECTIONS = "sections"; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkflowStepRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkflowStepRestRepository.java index 798c352b22..2102bab710 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkflowStepRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkflowStepRestRepository.java @@ -24,7 +24,7 @@ import org.springframework.stereotype.Component; * * @author Maria Verdonck (Atmire) on 10/01/2020 */ -@Component(WorkflowStepRest.CATEGORY + "." + WorkflowStepRest.NAME) +@Component(WorkflowStepRest.CATEGORY + "." + WorkflowStepRest.PLURAL_NAME) public class WorkflowStepRestRepository extends DSpaceRestRepository { @Autowired diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkspaceItemRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkspaceItemRestRepository.java index b43cdb7a0b..edf7d62b95 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkspaceItemRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkspaceItemRestRepository.java @@ -72,7 +72,7 @@ import org.springframework.web.multipart.MultipartFile; * @author Andrea Bollini (andrea.bollini at 4science.it) * @author Pasquale Cavallo (pasquale.cavallo at 4science.it) */ -@Component(WorkspaceItemRest.CATEGORY + "." + WorkspaceItemRest.NAME) +@Component(WorkspaceItemRest.CATEGORY + "." + WorkspaceItemRest.PLURAL_NAME) public class WorkspaceItemRestRepository extends DSpaceRestRepository implements ReloadableEntityObjectRepository { diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/repository/MockObjectRestRepository.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/repository/MockObjectRestRepository.java index 0c54811249..596f846f13 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/repository/MockObjectRestRepository.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/repository/MockObjectRestRepository.java @@ -18,7 +18,7 @@ import org.springframework.stereotype.Component; * This class has been added to allow the MockObjectRest to act as an actual BaseObjectRest since they're * expected to have a RestRepository */ -@Component(MockObjectRest.CATEGORY + "." + MockObjectRest.NAME) +@Component(MockObjectRest.CATEGORY + "." + MockObjectRest.PLURAL_NAME) public class MockObjectRestRepository extends DSpaceRestRepository { // Added a permitAll preAuthorize annotation to allow the object to be used in tests by every user From 44fc15f74baf6aa23c70f3b73975f3c82401e499 Mon Sep 17 00:00:00 2001 From: Shankeerthan Kasilingam Date: Tue, 5 Dec 2023 16:22:01 +0530 Subject: [PATCH 294/412] 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 175fb34e6c..04d22718e8 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 class SitemapRestControllerIT extends AbstractControllerIntegrationTest { //** 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 class SitemapRestControllerIT extends AbstractControllerIntegrationTest { //** 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 295/412] 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 e09e4725ca..f6144961c6 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.OrcidQueueService; 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 0a8f6d6f3d..b9fde450c2 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.GroupService; 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 b0aa4aba13..2ec08be055 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.json.JSONObject; 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 class OpenAIRERestConnector { * * @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 class OpenAIRERestConnector { 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 class OpenAIRERestConnector { } /** - * 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 class OpenAIRERestConnector { } /** - * 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 class OpenAIRERestConnector { } /** - * 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 203f09b3c6..f5dc2b27f8 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 8ca5b7c0ea..62cef508c5 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 eu.openaire.oaf.model.base.Project; 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 @@ import org.springframework.beans.factory.annotation.Autowired; /** * 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 class OpenAIREFundingDataProvider extends AbstractExternalDataProvider { // 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 class OpenAIREFundingDataProvider extends AbstractExternalDataProvider { 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 class OpenAIREFundingDataProvider extends AbstractExternalDataProvider { 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 class OpenAIREFundingDataProvider extends AbstractExternalDataProvider { 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 class OpenAIREFundingDataProvider extends AbstractExternalDataProvider { * 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 @@ public class OpenAIREFundingDataProvider extends AbstractExternalDataProvider { } /** - * 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 class OpenAIREFundingDataProvider extends AbstractExternalDataProvider { } /** - * 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 68976430e6..6460c360ec 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.qaevent.service.QAEventService; 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 b3f7be5f52..e22f7d32a7 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 @@ package org.dspace.qaevent; 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 9de2d12460..98c38ca3f5 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 @@ import org.dspace.eperson.EPerson; * @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 3263ae9de9..ac9b96045e 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.content.QAEventProcessed; 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 class QAEventsDaoImpl extends AbstractHibernateDAO impl 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 e45dc3e159..8bd864d7da 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 @@ import org.dspace.utils.DSpace; /** * 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 14737de635..60001e7350 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 class OpenaireEventsImportScriptConfiguration 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 bbb6990bb6..9be4af2d08 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.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.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 @@ import org.springframework.beans.factory.annotation.Autowired; * 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 f1e6c30d13..8a5277ab2d 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 836d4f0896..8bae32eaef 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 b80330564f..a197b2910b 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 13d037abf8..a177571ffa 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 class CollectionTest extends AbstractDSpaceObjectTest { 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 bac80e196c..c67402dfdc 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.helper.OpenAIREHandler; 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 5e96f06ac8..d14dc99035 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.Before; 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 class OpenAIREFundingDataProviderTest extends AbstractDSpaceTest { 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 e1b7cc89cf..03bea35597 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 @@ public class RestResourceController implements InitializingBean { 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 bd9b31e14c..dc0654ee49 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.QAEvent; 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 DSpaceRestRepositoryfake.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 9db02440e4..b045865fe0 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 4a91ef051e..ea654ac55c 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 083b27d0e5..7b9cad70e9 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 class DiscoveryVersioningIT extends AbstractControllerIntegrationTest { } @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 65492cbbe8..2b03b50c55 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 class ExternalSourcesRestControllerIT extends AbstractControllerIntegrati 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 b8f886b2e4..8c76c7adea 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.dspace.app.rest.test.AbstractControllerIntegrationTest; 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 class OpenAIREFundingExternalSourcesIT extends AbstractControllerIntegrat * @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 class OpenAIREFundingExternalSourcesIT extends AbstractControllerIntegrat * @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 class OpenAIREFundingExternalSourcesIT extends AbstractControllerIntegrat * @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 class OpenAIREFundingExternalSourcesIT extends AbstractControllerIntegrat * @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 dc021f2904..592b659457 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.EntityType; 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 @@ import org.springframework.beans.factory.annotation.Autowired; public class QAEventRestRepositoryIT extends AbstractControllerIntegrationTest { @Autowired - private QAEventsDao qaEventsDao; + private QAEventsDAO qaEventsDao; @Test public void findAllNotImplementedTest() throws Exception { @@ -759,6 +759,11 @@ public class QAEventRestRepositoryIT extends AbstractControllerIntegrationTest { .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 class QAEventRestRepositoryIT extends AbstractControllerIntegrationTest { 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 baf1041ab5..34f1ae16c7 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 javax.xml.bind.JAXBException; 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.invocation.InvocationOnMock; 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 cece890450..8ac703609d 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 1060a33031..e5175c3c4e 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 ded99d0120..b43ef781ee 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 b3290ac120..b46802a46f 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 f2f50182fa..53d15d4d43 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 91ee203ae9..0b31851078 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 3a8f79732d..0a40060e51 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 c2f844d2b2..4da07b6a79 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 fb25f11598..83896feb45 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 25a23a1739..9800e42bd1 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 24c463fb53..1460c19fe4 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 25bb282672..300a47c8f2 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 8c81ed392b..fdb22bad48 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 c32ea8d40b..e6a667697d 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 39a4778356..d888e0990e 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 296/412] 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 8bd864d7da..9087606aa6 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.content.QAEvent; 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 class OpenaireEventsImport 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 c17a7a3ff5..e7a7be33c1 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 @@ import org.dspace.utils.DSpace; * @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 8d3f2cdaac..5839f5e877 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 dbe44fd2e7..35d9231ea5 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.Collection; 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 class OpenaireEventsImportIT extends AbstractIntegrationTestWithDatabase 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 300a47c8f2..8c52b8b1f2 100644 --- a/dspace/config/spring/api/qaevents.xml +++ b/dspace/config/spring/api/qaevents.xml @@ -13,7 +13,7 @@ - + From c974e73e2c1bcaf7b16e28752f0b87c99a242a02 Mon Sep 17 00:00:00 2001 From: Vincenzo Mecca Date: Wed, 13 Dec 2023 15:42:47 +0100 Subject: [PATCH 297/412] [CST-12825] Ror integration --- .../external/datamodel/ImportRecord.java | 13 + .../RorParentOrgUnitMetadataContributor.java | 109 + .../external/ror/service/RorFieldMapping.java | 38 + .../RorImportMetadataSourceServiceImpl.java | 266 ++ .../spring-dspace-addon-import-services.xml | 6 + .../RorImportMetadataSourceServiceIT.java | 137 + .../app/rest/SitemapRestControllerIT.java | 14 +- .../org/dspace/app/rest/ror-record.json | 107 + .../org/dspace/app/rest/ror-records.json | 2383 +++++++++++++++++ dspace/config/dspace.cfg | 1 + dspace/config/modules/ror.cfg | 2 + .../registries/openaire-cerif-types.xml | 27 + .../registries/schema-organization-types.xml | 6 + .../config/spring/api/external-services.xml | 14 +- dspace/config/spring/api/ror-integration.xml | 111 + dspace/config/submission-forms.xml | 10 +- 16 files changed, 3236 insertions(+), 8 deletions(-) create mode 100644 dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/RorParentOrgUnitMetadataContributor.java create mode 100644 dspace-api/src/main/java/org/dspace/importer/external/ror/service/RorFieldMapping.java create mode 100644 dspace-api/src/main/java/org/dspace/importer/external/ror/service/RorImportMetadataSourceServiceImpl.java create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/RorImportMetadataSourceServiceIT.java create mode 100644 dspace-server-webapp/src/test/resources/org/dspace/app/rest/ror-record.json create mode 100644 dspace-server-webapp/src/test/resources/org/dspace/app/rest/ror-records.json create mode 100644 dspace/config/modules/ror.cfg create mode 100644 dspace/config/registries/openaire-cerif-types.xml create mode 100644 dspace/config/spring/api/ror-integration.xml diff --git a/dspace-api/src/main/java/org/dspace/importer/external/datamodel/ImportRecord.java b/dspace-api/src/main/java/org/dspace/importer/external/datamodel/ImportRecord.java index 3fc34dc511..dcf1a62b28 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/datamodel/ImportRecord.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/datamodel/ImportRecord.java @@ -11,7 +11,9 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Optional; +import org.dspace.content.MetadataFieldName; import org.dspace.importer.external.metadatamapping.MetadatumDTO; /** @@ -94,6 +96,17 @@ public class ImportRecord { return values; } + public Optional getSingleValue(String field) { + MetadataFieldName metadataFieldName = new MetadataFieldName(field); + return getSingleValue(metadataFieldName.schema, metadataFieldName.element, metadataFieldName.qualifier); + } + + public Optional getSingleValue(String schema, String element, String qualifier) { + return getValue(schema, element, qualifier).stream() + .map(MetadatumDTO::getValue) + .findFirst(); + } + /** * Add a value to the valueList * diff --git a/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/RorParentOrgUnitMetadataContributor.java b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/RorParentOrgUnitMetadataContributor.java new file mode 100644 index 0000000000..be1910d7a5 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/RorParentOrgUnitMetadataContributor.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.importer.external.metadatamapping.contributor; + +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.dspace.importer.external.metadatamapping.MetadatumDTO; + +public class RorParentOrgUnitMetadataContributor extends SimpleJsonPathMetadataContributor { + + private String typeField; + + private String parentType; + + private String labelField; + + /** + * Retrieve the metadata associated with the given object. + * The toString() of the resulting object will be used. + * + * @param fullJson A class to retrieve metadata from. + * @return a collection of import records. Only the identifier of the found records may be put in the record. + */ + @Override + public Collection contributeMetadata(String fullJson) { + + Collection metadata = new ArrayList<>(); + Collection metadataValue = new ArrayList<>(); + + JsonNode jsonNode = convertStringJsonToJsonNode(fullJson); + JsonNode array = jsonNode.at(getQuery()); + if (!array.isArray()) { + return metadata; + } + + Iterator nodes = array.iterator(); + while (nodes.hasNext()) { + JsonNode node = nodes.next(); + + if (!node.has(labelField)) { + continue; + } + + String type = node.has(typeField) ? node.get(typeField).asText() : null; + String label = node.get(labelField).asText(); + + if (parentType.equalsIgnoreCase(type)) { + metadataValue.add(label); + } + + } + + for (String value : metadataValue) { + MetadatumDTO metadatumDto = new MetadatumDTO(); + metadatumDto.setValue(value); + metadatumDto.setElement(getField().getElement()); + metadatumDto.setQualifier(getField().getQualifier()); + metadatumDto.setSchema(getField().getSchema()); + metadata.add(metadatumDto); + } + return metadata; + } + + private JsonNode convertStringJsonToJsonNode(String json) { + ObjectMapper mapper = new ObjectMapper(); + JsonNode body = null; + try { + body = mapper.readTree(json); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + return body; + } + + public String getTypeField() { + return typeField; + } + + public void setTypeField(String typeField) { + this.typeField = typeField; + } + + public String getLabelField() { + return labelField; + } + + public void setLabelField(String labelField) { + this.labelField = labelField; + } + + public String getParentType() { + return parentType; + } + + public void setParentType(String parentType) { + this.parentType = parentType; + } + +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/importer/external/ror/service/RorFieldMapping.java b/dspace-api/src/main/java/org/dspace/importer/external/ror/service/RorFieldMapping.java new file mode 100644 index 0000000000..5248d793e2 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/importer/external/ror/service/RorFieldMapping.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.importer.external.ror.service; + +import java.util.Map; +import javax.annotation.Resource; + +import org.dspace.importer.external.metadatamapping.AbstractMetadataFieldMapping; + + +/** + * An implementation of {@link AbstractMetadataFieldMapping} + * Responsible for defining the mapping of the Scopus metadatum fields on the DSpace metadatum fields + * + * @author Pasquale Cavallo (pasquale.cavallo at 4science dot it) + */ +public class RorFieldMapping extends AbstractMetadataFieldMapping { + + /** + * Defines which metadatum is mapped on which metadatum. Note that while the key must be unique it + * only matters here for postprocessing of the value. The mapped MetadatumContributor has full control over + * what metadatafield is generated. + * + * @param metadataFieldMap The map containing the link between retrieve metadata and metadata that will be set to + * the item. + */ + @Override + @Resource(name = "rorMetadataFieldMap") + public void setMetadataFieldMap(Map metadataFieldMap) { + super.setMetadataFieldMap(metadataFieldMap); + } + +} diff --git a/dspace-api/src/main/java/org/dspace/importer/external/ror/service/RorImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/ror/service/RorImportMetadataSourceServiceImpl.java new file mode 100644 index 0000000000..ebc7caefb2 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/importer/external/ror/service/RorImportMetadataSourceServiceImpl.java @@ -0,0 +1,266 @@ +/** + * The contents of this file are subject to the license and 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.ror.service; + +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Callable; +import javax.el.MethodNotFoundException; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.http.client.utils.URIBuilder; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.dspace.content.Item; +import org.dspace.importer.external.datamodel.ImportRecord; +import org.dspace.importer.external.datamodel.Query; +import org.dspace.importer.external.exception.MetadataSourceException; +import org.dspace.importer.external.liveimportclient.service.LiveImportClient; +import org.dspace.importer.external.service.AbstractImportMetadataSourceService; +import org.dspace.importer.external.service.components.QuerySource; +import org.springframework.beans.factory.annotation.Autowired; + +public class RorImportMetadataSourceServiceImpl extends AbstractImportMetadataSourceService + implements QuerySource { + + private final static Logger log = LogManager.getLogger(); + + private String url; + + private int timeout = 1000; + + @Autowired + private LiveImportClient liveImportClient; + + @Override + public String getImportSource() { + return "ror"; + } + + @Override + public ImportRecord getRecord(String id) throws MetadataSourceException { + List records = retry(new SearchByIdCallable(id)); + return CollectionUtils.isEmpty(records) ? null : records.get(0); + } + + @Override + public int getRecordsCount(String query) throws MetadataSourceException { + return retry(new CountByQueryCallable(query)); + } + + @Override + public int getRecordsCount(Query query) throws MetadataSourceException { + return retry(new CountByQueryCallable(query)); + } + + @Override + public Collection getRecords(String query, int start, int count) throws MetadataSourceException { + return retry(new SearchByQueryCallable(query)); + } + + @Override + public Collection getRecords(Query query) throws MetadataSourceException { + return retry(new SearchByQueryCallable(query)); + } + + @Override + public ImportRecord getRecord(Query query) throws MetadataSourceException { + List records = retry(new SearchByIdCallable(query)); + return CollectionUtils.isEmpty(records) ? null : records.get(0); + } + + @Override + public Collection findMatchingRecords(Query query) throws MetadataSourceException { + throw new MethodNotFoundException("This method is not implemented for ROR"); + } + + @Override + public Collection findMatchingRecords(Item item) throws MetadataSourceException { + throw new MethodNotFoundException("This method is not implemented for ROR"); + } + + @Override + public void init() throws Exception { + } + + /** + * This class is a Callable implementation to get ADS entries based on query + * object. This Callable use as query value the string queryString passed to + * constructor. If the object will be construct through Query.class instance, a + * Query's map entry with key "query" will be used. Pagination is supported too, + * using the value of the Query's map with keys "start" and "count". + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk@4science.com) + */ + private class SearchByQueryCallable implements Callable> { + + private Query query; + + private SearchByQueryCallable(String queryString) { + query = new Query(); + query.addParameter("query", queryString); + } + + private SearchByQueryCallable(Query query) { + this.query = query; + } + + @Override + public List call() throws Exception { + return search(query.getParameterAsClass("query", String.class)); + } + } + + /** + * This class is a Callable implementation to get an ADS entry using bibcode The + * bibcode to use can be passed through the constructor as a String or as + * Query's map entry, with the key "id". + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk@4science.com) + */ + private class SearchByIdCallable implements Callable> { + private Query query; + + private SearchByIdCallable(Query query) { + this.query = query; + } + + private SearchByIdCallable(String id) { + this.query = new Query(); + query.addParameter("id", id); + } + + @Override + public List call() throws Exception { + return searchById(query.getParameterAsClass("id", String.class)); + } + } + + /** + * This class is a Callable implementation to count the number of entries for an + * ADS query. This Callable use as query value to ADS the string queryString + * passed to constructor. If the object will be construct through Query.class + * instance, the value of the Query's map with the key "query" will be used. + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk@4science.com) + */ + private class CountByQueryCallable implements Callable { + private Query query; + + private CountByQueryCallable(String queryString) { + query = new Query(); + query.addParameter("query", queryString); + } + + private CountByQueryCallable(Query query) { + this.query = query; + } + + @Override + public Integer call() throws Exception { + return count(query.getParameterAsClass("query", String.class)); + } + } + + public Integer count(String query) { + try { + Map> params = new HashMap>(); + + URIBuilder uriBuilder = new URIBuilder(this.url); + uriBuilder.addParameter("query", query); + + String resp = liveImportClient.executeHttpGetRequest(timeout, uriBuilder.toString(), params); + if (StringUtils.isEmpty(resp)) { + return 0; + } + JsonNode jsonNode = convertStringJsonToJsonNode(resp); + return jsonNode.at("/number_of_results").asInt(); + } catch (URISyntaxException e) { + e.printStackTrace(); + } + return 0; + } + + private List searchById(String id) { + + List adsResults = new ArrayList<>(); + + id = StringUtils.removeStart(id, "https://ror.org/"); + + try { + Map> params = new HashMap>(); + + URIBuilder uriBuilder = new URIBuilder(this.url + "/" + id); + + String resp = liveImportClient.executeHttpGetRequest(timeout, uriBuilder.toString(), params); + if (StringUtils.isEmpty(resp)) { + return adsResults; + } + + JsonNode jsonNode = convertStringJsonToJsonNode(resp); + adsResults.add(transformSourceRecords(jsonNode.toString())); + + } catch (URISyntaxException e) { + e.printStackTrace(); + } + return adsResults; + } + + private List search(String query) { + List adsResults = new ArrayList<>(); + try { + Map> params = new HashMap>(); + + URIBuilder uriBuilder = new URIBuilder(this.url); + uriBuilder.addParameter("query", query); + + String resp = liveImportClient.executeHttpGetRequest(timeout, uriBuilder.toString(), params); + if (StringUtils.isEmpty(resp)) { + return adsResults; + } + + JsonNode jsonNode = convertStringJsonToJsonNode(resp); + JsonNode docs = jsonNode.at("/items"); + if (docs.isArray()) { + Iterator nodes = docs.elements(); + while (nodes.hasNext()) { + JsonNode node = nodes.next(); + adsResults.add(transformSourceRecords(node.toString())); + } + } else { + adsResults.add(transformSourceRecords(docs.toString())); + } + } catch (URISyntaxException e) { + e.printStackTrace(); + } + return adsResults; + } + + private JsonNode convertStringJsonToJsonNode(String json) { + try { + return new ObjectMapper().readTree(json); + } catch (JsonProcessingException e) { + log.error("Unable to process json response.", e); + } + return null; + } + + public void setUrl(String url) { + this.url = url; + } + +} diff --git a/dspace-api/src/main/resources/spring/spring-dspace-addon-import-services.xml b/dspace-api/src/main/resources/spring/spring-dspace-addon-import-services.xml index 6b0ef3e9b9..1b201c71ef 100644 --- a/dspace-api/src/main/resources/spring/spring-dspace-addon-import-services.xml +++ b/dspace-api/src/main/resources/spring/spring-dspace-addon-import-services.xml @@ -150,6 +150,12 @@ + + + + + + diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RorImportMetadataSourceServiceIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RorImportMetadataSourceServiceIT.java new file mode 100644 index 0000000000..4f8e56f980 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RorImportMetadataSourceServiceIT.java @@ -0,0 +1,137 @@ +/** + * The contents of this file are subject to the license and 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.matcher.LambdaMatcher.matches; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasSize; +import static org.mockito.Mockito.when; + +import java.io.InputStream; +import java.nio.charset.Charset; +import java.util.Collection; +import java.util.Optional; + +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.ror.service.RorImportMetadataSourceServiceImpl; +import org.hamcrest.Matcher; +import org.junit.Test; +import org.mockito.ArgumentMatchers; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; + +public class RorImportMetadataSourceServiceIT extends AbstractLiveImportIntegrationTest { + + @Autowired + private LiveImportClientImpl liveImportClient; + + @Autowired + private RorImportMetadataSourceServiceImpl rorServiceImpl; + + @Test + public void tesGetRecords() throws Exception { + context.turnOffAuthorisationSystem(); + CloseableHttpClient originalHttpClient = liveImportClient.getHttpClient(); + CloseableHttpClient httpClient = Mockito.mock(CloseableHttpClient.class); + + try (InputStream file = getClass().getResourceAsStream("ror-records.json")) { + + String jsonResponse = IOUtils.toString(file, Charset.defaultCharset()); + + liveImportClient.setHttpClient(httpClient); + CloseableHttpResponse response = mockResponse(jsonResponse, 200, "OK"); + when(httpClient.execute(ArgumentMatchers.any())).thenReturn(response); + + context.restoreAuthSystemState(); + Collection recordsImported = rorServiceImpl.getRecords("test query", 0, 2); + assertThat(recordsImported, hasSize(10)); + + ImportRecord record = recordsImported.iterator().next(); + + assertThat(record.getValueList(), hasSize(11)); + + assertThat(record.getSingleValue("dc.title"), is("The University of Texas")); + assertThat(record.getSingleValue("organization.identifier.ror"), is("https://ror.org/02f6dcw23")); + assertThat(record.getSingleValue("oairecerif.acronym"), is("UTHSCSA")); + assertThat(record.getSingleValue("oairecerif.identifier.url"), is("http://www.uthscsa.edu/")); + assertThat(record.getSingleValue("dc.type"), is("Education")); + assertThat(record.getSingleValue("organization.address.addressCountry"), is("US")); + assertThat(record.getSingleValue("organization.foundingDate"), is("1959")); + assertThat(record.getValue("organization", "identifier", "crossrefid"), hasSize(2)); + assertThat(record.getSingleValue("organization.identifier.isni"), is("0000 0001 0629 5880")); + assertThat(record.getSingleValue("organization.parentOrganization"), is("The University of Texas System")); + + } finally { + liveImportClient.setHttpClient(originalHttpClient); + } + } + + @Test + public void tesCount() throws Exception { + context.turnOffAuthorisationSystem(); + CloseableHttpClient originalHttpClient = liveImportClient.getHttpClient(); + CloseableHttpClient httpClient = Mockito.mock(CloseableHttpClient.class); + + try (InputStream file = getClass().getResourceAsStream("ror-records.json")) { + + String jsonResponse = IOUtils.toString(file, Charset.defaultCharset()); + + liveImportClient.setHttpClient(httpClient); + CloseableHttpResponse response = mockResponse(jsonResponse, 200, "OK"); + when(httpClient.execute(ArgumentMatchers.any())).thenReturn(response); + + context.restoreAuthSystemState(); + Integer count = rorServiceImpl.count("test"); + assertThat(count, equalTo(200)); + } finally { + liveImportClient.setHttpClient(originalHttpClient); + } + } + + @Test + public void tesGetRecord() throws Exception { + context.turnOffAuthorisationSystem(); + CloseableHttpClient originalHttpClient = liveImportClient.getHttpClient(); + CloseableHttpClient httpClient = Mockito.mock(CloseableHttpClient.class); + + try (InputStream file = getClass().getResourceAsStream("ror-record.json")) { + + String jsonResponse = IOUtils.toString(file, Charset.defaultCharset()); + + liveImportClient.setHttpClient(httpClient); + CloseableHttpResponse response = mockResponse(jsonResponse, 200, "OK"); + when(httpClient.execute(ArgumentMatchers.any())).thenReturn(response); + + context.restoreAuthSystemState(); + ImportRecord record = rorServiceImpl.getRecord("https://ror.org/01sps7q28"); + assertThat(record.getValueList(), hasSize(9)); + assertThat(record.getSingleValue("dc.title"), is("The University of Texas Health Science Center at Tyler")); + assertThat(record.getSingleValue("organization.identifier.ror"), is("https://ror.org/01sps7q28")); + assertThat(record.getSingleValue("oairecerif.acronym"), is("UTHSCT")); + assertThat(record.getSingleValue("oairecerif.identifier.url"), + is("https://www.utsystem.edu/institutions/university-texas-health-science-center-tyler")); + assertThat(record.getSingleValue("dc.type"), is("Healthcare")); + assertThat(record.getSingleValue("organization.address.addressCountry"), is("US")); + assertThat(record.getSingleValue("organization.foundingDate"), is("1947")); + assertThat(record.getSingleValue("organization.identifier.isni"), is("0000 0000 9704 5790")); + assertThat(record.getSingleValue("organization.parentOrganization"), is("The University of Texas System")); + + } finally { + liveImportClient.setHttpClient(originalHttpClient); + } + } + + private Matcher> is(String value) { + return matches(optionalValue -> optionalValue.isPresent() && optionalValue.get().equals(value)); + } +} 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 175fb34e6c..04d22718e8 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 class SitemapRestControllerIT extends AbstractControllerIntegrationTest { //** 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 class SitemapRestControllerIT extends AbstractControllerIntegrationTest { //** 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(); diff --git a/dspace-server-webapp/src/test/resources/org/dspace/app/rest/ror-record.json b/dspace-server-webapp/src/test/resources/org/dspace/app/rest/ror-record.json new file mode 100644 index 0000000000..51924485b3 --- /dev/null +++ b/dspace-server-webapp/src/test/resources/org/dspace/app/rest/ror-record.json @@ -0,0 +1,107 @@ +{ + "id": "https://ror.org/01sps7q28", + "name": "The University of Texas Health Science Center at Tyler", + "email_address": null, + "ip_addresses": [ + + ], + "established": 1947, + "types": [ + "Healthcare" + ], + "relationships": [ + { + "label": "The University of Texas System", + "type": "Parent", + "id": "https://ror.org/01gek1696" + } + ], + "addresses": [ + { + "lat": 32.426014, + "lng": -95.212728, + "state": "Texas", + "state_code": "US-TX", + "city": "Tyler", + "geonames_city": { + "id": 4738214, + "city": "Tyler", + "geonames_admin1": { + "name": "Texas", + "id": 4736286, + "ascii_name": "Texas", + "code": "US.TX" + }, + "geonames_admin2": { + "name": "Smith County", + "id": 4729130, + "ascii_name": "Smith County", + "code": "US.TX.423" + }, + "license": { + "attribution": "Data from geonames.org under a CC-BY 3.0 license", + "license": "http://creativecommons.org/licenses/by/3.0/" + }, + "nuts_level1": { + "name": null, + "code": null + }, + "nuts_level2": { + "name": null, + "code": null + }, + "nuts_level3": { + "name": null, + "code": null + } + }, + "postcode": null, + "primary": false, + "line": null, + "country_geonames_id": 6252001 + } + ], + "links": [ + "https://www.utsystem.edu/institutions/university-texas-health-science-center-tyler" + ], + "aliases": [ + "East Texas Tuberculosis Sanitarium", + "UT Health Northeast" + ], + "acronyms": [ + "UTHSCT" + ], + "status": "active", + "wikipedia_url": "https://en.wikipedia.org/wiki/University_of_Texas_Health_Science_Center_at_Tyler", + "labels": [ + + ], + "country": { + "country_name": "United States", + "country_code": "US" + }, + "external_ids": { + "ISNI": { + "preferred": null, + "all": [ + "0000 0000 9704 5790" + ] + }, + "OrgRef": { + "preferred": null, + "all": [ + "3446655" + ] + }, + "Wikidata": { + "preferred": null, + "all": [ + "Q7896437" + ] + }, + "GRID": { + "preferred": "grid.267310.1", + "all": "grid.267310.1" + } + } +} \ No newline at end of file diff --git a/dspace-server-webapp/src/test/resources/org/dspace/app/rest/ror-records.json b/dspace-server-webapp/src/test/resources/org/dspace/app/rest/ror-records.json new file mode 100644 index 0000000000..91ce8d33e0 --- /dev/null +++ b/dspace-server-webapp/src/test/resources/org/dspace/app/rest/ror-records.json @@ -0,0 +1,2383 @@ +{ + "number_of_results": 200, + "time_taken": 12, + "items": [ + { + "id": "https://ror.org/02f6dcw23", + "name": "The University of Texas", + "email_address": null, + "ip_addresses": [ + + ], + "established": 1959, + "types": [ + "Education" + ], + "relationships": [ + { + "label": "Audie L. Murphy Memorial VA Hospital", + "type": "Related", + "id": "https://ror.org/035xhk118" + }, + { + "label": "San Antonio Military Medical Center", + "type": "Related", + "id": "https://ror.org/00m1mwc36" + }, + { + "label": "The University of Texas System", + "type": "Parent", + "id": "https://ror.org/01gek1696" + } + ], + "addresses": [ + { + "lat": 29.508129, + "lng": -98.574025, + "state": "Texas", + "state_code": "US-TX", + "city": "San Antonio", + "geonames_city": { + "id": 4726206, + "city": "San Antonio", + "geonames_admin1": { + "name": "Texas", + "id": 4736286, + "ascii_name": "Texas", + "code": "US.TX" + }, + "geonames_admin2": { + "name": "Bexar County", + "id": 4674023, + "ascii_name": "Bexar County", + "code": "US.TX.029" + }, + "license": { + "attribution": "Data from geonames.org under a CC-BY 3.0 license", + "license": "http://creativecommons.org/licenses/by/3.0/" + }, + "nuts_level1": { + "name": null, + "code": null + }, + "nuts_level2": { + "name": null, + "code": null + }, + "nuts_level3": { + "name": null, + "code": null + } + }, + "postcode": null, + "primary": false, + "line": null, + "country_geonames_id": 6252001 + } + ], + "links": [ + "http://www.uthscsa.edu/" + ], + "aliases": [ + + ], + "acronyms": [ + "UTHSCSA" + ], + "status": "active", + "wikipedia_url": "https://en.wikipedia.org/wiki/University_of_Texas_Health_Science_Center_at_San_Antonio", + "labels": [ + + ], + "country": { + "country_name": "United States", + "country_code": "US" + }, + "external_ids": { + "ISNI": { + "preferred": null, + "all": [ + "0000 0001 0629 5880" + ] + }, + "FundRef": { + "preferred": "100008635", + "all": [ + "100008635", + "100008636" + ] + }, + "OrgRef": { + "preferred": null, + "all": [ + "1593427" + ] + }, + "Wikidata": { + "preferred": null, + "all": [ + "Q4005868" + ] + }, + "GRID": { + "preferred": "grid.267309.9", + "all": "grid.267309.9" + } + } + }, + { + "id": "https://ror.org/01sps7q28", + "name": "The University of Texas Health Science Center at Tyler", + "email_address": null, + "ip_addresses": [ + + ], + "established": 1947, + "types": [ + "Healthcare" + ], + "relationships": [ + { + "label": "The University of Texas System", + "type": "Parent", + "id": "https://ror.org/01gek1696" + } + ], + "addresses": [ + { + "lat": 32.426014, + "lng": -95.212728, + "state": "Texas", + "state_code": "US-TX", + "city": "Tyler", + "geonames_city": { + "id": 4738214, + "city": "Tyler", + "geonames_admin1": { + "name": "Texas", + "id": 4736286, + "ascii_name": "Texas", + "code": "US.TX" + }, + "geonames_admin2": { + "name": "Smith County", + "id": 4729130, + "ascii_name": "Smith County", + "code": "US.TX.423" + }, + "license": { + "attribution": "Data from geonames.org under a CC-BY 3.0 license", + "license": "http://creativecommons.org/licenses/by/3.0/" + }, + "nuts_level1": { + "name": null, + "code": null + }, + "nuts_level2": { + "name": null, + "code": null + }, + "nuts_level3": { + "name": null, + "code": null + } + }, + "postcode": null, + "primary": false, + "line": null, + "country_geonames_id": 6252001 + } + ], + "links": [ + "https://www.utsystem.edu/institutions/university-texas-health-science-center-tyler" + ], + "aliases": [ + "East Texas Tuberculosis Sanitarium", + "UT Health Northeast" + ], + "acronyms": [ + "UTHSCT" + ], + "status": "active", + "wikipedia_url": "https://en.wikipedia.org/wiki/University_of_Texas_Health_Science_Center_at_Tyler", + "labels": [ + + ], + "country": { + "country_name": "United States", + "country_code": "US" + }, + "external_ids": { + "ISNI": { + "preferred": null, + "all": [ + "0000 0000 9704 5790" + ] + }, + "OrgRef": { + "preferred": null, + "all": [ + "3446655" + ] + }, + "Wikidata": { + "preferred": null, + "all": [ + "Q7896437" + ] + }, + "GRID": { + "preferred": "grid.267310.1", + "all": "grid.267310.1" + } + } + }, + { + "id": "https://ror.org/05byvp690", + "name": "The University of Texas Southwestern Medical Center", + "email_address": null, + "ip_addresses": [ + + ], + "established": 1943, + "types": [ + "Healthcare" + ], + "relationships": [ + { + "label": "Children's Medical Center", + "type": "Related", + "id": "https://ror.org/02ndk3y82" + }, + { + "label": "Parkland Memorial Hospital", + "type": "Related", + "id": "https://ror.org/0208r0146" + }, + { + "label": "VA North Texas Health Care System", + "type": "Related", + "id": "https://ror.org/01nzxq896" + }, + { + "label": "The University of Texas System", + "type": "Parent", + "id": "https://ror.org/01gek1696" + }, + { + "label": "Institute for Exercise and Environmental Medicine", + "type": "Child", + "id": "https://ror.org/03gqc7y13" + }, + { + "label": "Texas Health Dallas", + "type": "Child", + "id": "https://ror.org/05k07p323" + } + ], + "addresses": [ + { + "lat": 32.812185, + "lng": -96.840174, + "state": "Texas", + "state_code": "US-TX", + "city": "Dallas", + "geonames_city": { + "id": 4684888, + "city": "Dallas", + "geonames_admin1": { + "name": "Texas", + "id": 4736286, + "ascii_name": "Texas", + "code": "US.TX" + }, + "geonames_admin2": { + "name": "Dallas County", + "id": 4684904, + "ascii_name": "Dallas County", + "code": "US.TX.113" + }, + "license": { + "attribution": "Data from geonames.org under a CC-BY 3.0 license", + "license": "http://creativecommons.org/licenses/by/3.0/" + }, + "nuts_level1": { + "name": null, + "code": null + }, + "nuts_level2": { + "name": null, + "code": null + }, + "nuts_level3": { + "name": null, + "code": null + } + }, + "postcode": null, + "primary": false, + "line": null, + "country_geonames_id": 6252001 + } + ], + "links": [ + "http://www.utsouthwestern.edu/" + ], + "aliases": [ + "UT Southwestern" + ], + "acronyms": [ + + ], + "status": "active", + "wikipedia_url": "https://en.wikipedia.org/wiki/University_of_Texas_Southwestern_Medical_Center", + "labels": [ + + ], + "country": { + "country_name": "United States", + "country_code": "US" + }, + "external_ids": { + "ISNI": { + "preferred": null, + "all": [ + "0000 0000 9482 7121" + ] + }, + "FundRef": { + "preferred": "100007914", + "all": [ + "100007914", + "100010487", + "100008260" + ] + }, + "OrgRef": { + "preferred": null, + "all": [ + "617906" + ] + }, + "Wikidata": { + "preferred": null, + "all": [ + "Q2725999" + ] + }, + "GRID": { + "preferred": "grid.267313.2", + "all": "grid.267313.2" + } + } + }, + { + "id": "https://ror.org/019kgqr73", + "name": "The University of Texas at Arlington", + "email_address": "", + "ip_addresses": [ + + ], + "established": 1895, + "types": [ + "Education" + ], + "relationships": [ + { + "label": "VA North Texas Health Care System", + "type": "Related", + "id": "https://ror.org/01nzxq896" + }, + { + "label": "The University of Texas System", + "type": "Parent", + "id": "https://ror.org/01gek1696" + } + ], + "addresses": [ + { + "lat": 32.731, + "lng": -97.115, + "state": "Texas", + "state_code": "US-TX", + "city": "Arlington", + "geonames_city": { + "id": 4671240, + "city": "Arlington", + "geonames_admin1": { + "name": "Texas", + "id": 4736286, + "ascii_name": "Texas", + "code": "US.TX" + }, + "geonames_admin2": { + "name": "Tarrant County", + "id": 4735638, + "ascii_name": "Tarrant County", + "code": "US.TX.439" + }, + "license": { + "attribution": "Data from geonames.org under a CC-BY 3.0 license", + "license": "http://creativecommons.org/licenses/by/3.0/" + }, + "nuts_level1": { + "name": null, + "code": null + }, + "nuts_level2": { + "name": null, + "code": null + }, + "nuts_level3": { + "name": null, + "code": null + } + }, + "postcode": null, + "primary": false, + "line": null, + "country_geonames_id": 6252001 + } + ], + "links": [ + "http://www.uta.edu/uta/" + ], + "aliases": [ + "UT Arlington" + ], + "acronyms": [ + "UTA" + ], + "status": "active", + "wikipedia_url": "http://en.wikipedia.org/wiki/University_of_Texas_at_Arlington", + "labels": [ + { + "label": "Université du Texas à Arlington", + "iso639": "fr" + } + ], + "country": { + "country_name": "United States", + "country_code": "US" + }, + "external_ids": { + "ISNI": { + "preferred": null, + "all": [ + "0000 0001 2181 9515" + ] + }, + "FundRef": { + "preferred": null, + "all": [ + "100009497" + ] + }, + "OrgRef": { + "preferred": null, + "all": [ + "906409" + ] + }, + "Wikidata": { + "preferred": null, + "all": [ + "Q1230739" + ] + }, + "GRID": { + "preferred": "grid.267315.4", + "all": "grid.267315.4" + } + } + }, + { + "id": "https://ror.org/051smbs96", + "name": "The University of Texas of the Permian Basin", + "email_address": null, + "ip_addresses": [ + + ], + "established": 1973, + "types": [ + "Education" + ], + "relationships": [ + { + "label": "The University of Texas System", + "type": "Parent", + "id": "https://ror.org/01gek1696" + } + ], + "addresses": [ + { + "lat": 31.889444, + "lng": -102.329531, + "state": "Texas", + "state_code": "US-TX", + "city": "Odessa", + "geonames_city": { + "id": 5527554, + "city": "Odessa", + "geonames_admin1": { + "name": "Texas", + "id": 4736286, + "ascii_name": "Texas", + "code": "US.TX" + }, + "geonames_admin2": { + "name": "Ector County", + "id": 5520910, + "ascii_name": "Ector County", + "code": "US.TX.135" + }, + "license": { + "attribution": "Data from geonames.org under a CC-BY 3.0 license", + "license": "http://creativecommons.org/licenses/by/3.0/" + }, + "nuts_level1": { + "name": null, + "code": null + }, + "nuts_level2": { + "name": null, + "code": null + }, + "nuts_level3": { + "name": null, + "code": null + } + }, + "postcode": null, + "primary": false, + "line": null, + "country_geonames_id": 6252001 + } + ], + "links": [ + "http://www.utpb.edu/" + ], + "aliases": [ + "UT Permian Basin" + ], + "acronyms": [ + "UTPB" + ], + "status": "active", + "wikipedia_url": "http://en.wikipedia.org/wiki/University_of_Texas_of_the_Permian_Basin", + "labels": [ + + ], + "country": { + "country_name": "United States", + "country_code": "US" + }, + "external_ids": { + "ISNI": { + "preferred": null, + "all": [ + "0000 0000 9140 1491" + ] + }, + "OrgRef": { + "preferred": null, + "all": [ + "1419441" + ] + }, + "Wikidata": { + "preferred": null, + "all": [ + "Q2495935" + ] + }, + "GRID": { + "preferred": "grid.267328.a", + "all": "grid.267328.a" + } + } + }, + { + "id": "https://ror.org/044vy1d05", + "name": "Tokushima University", + "email_address": "", + "ip_addresses": [ + + ], + "established": 1949, + "types": [ + "Education" + ], + "relationships": [ + { + "label": "Tokushima University Hospital", + "type": "Related", + "id": "https://ror.org/021ph5e41" + } + ], + "addresses": [ + { + "lat": 34.07, + "lng": 134.56, + "state": null, + "state_code": null, + "city": "Tokushima", + "geonames_city": { + "id": 1850158, + "city": "Tokushima", + "geonames_admin1": { + "name": "Tokushima", + "id": 1850157, + "ascii_name": "Tokushima", + "code": "JP.39" + }, + "geonames_admin2": { + "name": "Tokushima Shi", + "id": 1850156, + "ascii_name": "Tokushima Shi", + "code": "JP.39.1850156" + }, + "license": { + "attribution": "Data from geonames.org under a CC-BY 3.0 license", + "license": "http://creativecommons.org/licenses/by/3.0/" + }, + "nuts_level1": { + "name": null, + "code": null + }, + "nuts_level2": { + "name": null, + "code": null + }, + "nuts_level3": { + "name": null, + "code": null + } + }, + "postcode": null, + "primary": false, + "line": null, + "country_geonames_id": 1861060 + } + ], + "links": [ + "https://www.tokushima-u.ac.jp/" + ], + "aliases": [ + "Tokushima Daigaku", + "University of Tokushima" + ], + "acronyms": [ + + ], + "status": "active", + "wikipedia_url": "https://en.wikipedia.org/wiki/University_of_Tokushima", + "labels": [ + { + "label": "徳島大学", + "iso639": "ja" + } + ], + "country": { + "country_name": "Japan", + "country_code": "JP" + }, + "external_ids": { + "ISNI": { + "preferred": null, + "all": [ + "0000 0001 1092 3579" + ] + }, + "FundRef": { + "preferred": null, + "all": [ + "501100005623" + ] + }, + "OrgRef": { + "preferred": null, + "all": [ + "15696836" + ] + }, + "Wikidata": { + "preferred": null, + "all": [ + "Q1150231" + ] + }, + "GRID": { + "preferred": "grid.267335.6", + "all": "grid.267335.6" + } + } + }, + { + "id": "https://ror.org/03np13864", + "name": "University of Trinidad and Tobago", + "email_address": null, + "ip_addresses": [ + + ], + "established": 2004, + "types": [ + "Education" + ], + "relationships": [ + + ], + "addresses": [ + { + "lat": 10.616667, + "lng": -61.216667, + "state": null, + "state_code": null, + "city": "Arima", + "geonames_city": { + "id": 3575051, + "city": "Arima", + "geonames_admin1": { + "name": "Borough of Arima", + "id": 3575052, + "ascii_name": "Borough of Arima", + "code": "TT.01" + }, + "geonames_admin2": { + "name": null, + "id": null, + "ascii_name": null, + "code": null + }, + "license": { + "attribution": "Data from geonames.org under a CC-BY 3.0 license", + "license": "http://creativecommons.org/licenses/by/3.0/" + }, + "nuts_level1": { + "name": null, + "code": null + }, + "nuts_level2": { + "name": null, + "code": null + }, + "nuts_level3": { + "name": null, + "code": null + } + }, + "postcode": null, + "primary": false, + "line": null, + "country_geonames_id": 3573591 + } + ], + "links": [ + "https://utt.edu.tt/" + ], + "aliases": [ + + ], + "acronyms": [ + "UTT" + ], + "status": "active", + "wikipedia_url": "https://en.wikipedia.org/wiki/University_of_Trinidad_and_Tobago", + "labels": [ + { + "label": "Universidad de Trinidad y Tobago", + "iso639": "es" + } + ], + "country": { + "country_name": "Trinidad and Tobago", + "country_code": "TT" + }, + "external_ids": { + "ISNI": { + "preferred": null, + "all": [ + "0000 0000 9490 0886" + ] + }, + "OrgRef": { + "preferred": null, + "all": [ + "8706288" + ] + }, + "Wikidata": { + "preferred": null, + "all": [ + "Q648244" + ] + }, + "GRID": { + "preferred": "grid.267355.0", + "all": "grid.267355.0" + } + } + }, + { + "id": "https://ror.org/04wn28048", + "name": "University of Tulsa", + "email_address": "", + "ip_addresses": [ + + ], + "established": 1894, + "types": [ + "Education" + ], + "relationships": [ + + ], + "addresses": [ + { + "lat": 36.152222, + "lng": -95.946389, + "state": "Oklahoma", + "state_code": "US-OK", + "city": "Tulsa", + "geonames_city": { + "id": 4553433, + "city": "Tulsa", + "geonames_admin1": { + "name": "Oklahoma", + "id": 4544379, + "ascii_name": "Oklahoma", + "code": "US.OK" + }, + "geonames_admin2": { + "name": "Tulsa County", + "id": 4553440, + "ascii_name": "Tulsa County", + "code": "US.OK.143" + }, + "license": { + "attribution": "Data from geonames.org under a CC-BY 3.0 license", + "license": "http://creativecommons.org/licenses/by/3.0/" + }, + "nuts_level1": { + "name": null, + "code": null + }, + "nuts_level2": { + "name": null, + "code": null + }, + "nuts_level3": { + "name": null, + "code": null + } + }, + "postcode": null, + "primary": false, + "line": null, + "country_geonames_id": 6252001 + } + ], + "links": [ + "http://utulsa.edu/" + ], + "aliases": [ + + ], + "acronyms": [ + "TU" + ], + "status": "active", + "wikipedia_url": "http://en.wikipedia.org/wiki/University_of_Tulsa", + "labels": [ + { + "label": "Université de tulsa", + "iso639": "fr" + } + ], + "country": { + "country_name": "United States", + "country_code": "US" + }, + "external_ids": { + "ISNI": { + "preferred": null, + "all": [ + "0000 0001 2160 264X" + ] + }, + "FundRef": { + "preferred": "100007147", + "all": [ + "100007147", + "100006455" + ] + }, + "OrgRef": { + "preferred": null, + "all": [ + "32043" + ] + }, + "Wikidata": { + "preferred": null, + "all": [ + "Q1848657" + ] + }, + "GRID": { + "preferred": "grid.267360.6", + "all": "grid.267360.6" + } + } + }, + { + "id": "https://ror.org/04scfb908", + "name": "Alfred Health", + "email_address": null, + "ip_addresses": [ + + ], + "established": 1871, + "types": [ + "Healthcare" + ], + "relationships": [ + { + "label": "Caulfield Hospital", + "type": "Child", + "id": "https://ror.org/01fcxf261" + }, + { + "label": "Melbourne Sexual Health Centre", + "type": "Child", + "id": "https://ror.org/013fdz725" + }, + { + "label": "National Trauma Research Institute", + "type": "Child", + "id": "https://ror.org/048t93218" + }, + { + "label": "The Alfred Hospital", + "type": "Child", + "id": "https://ror.org/01wddqe20" + } + ], + "addresses": [ + { + "lat": -37.845542, + "lng": 144.981632, + "state": "Victoria", + "state_code": "AU-VIC", + "city": "Melbourne", + "geonames_city": { + "id": 2158177, + "city": "Melbourne", + "geonames_admin1": { + "name": "Victoria", + "id": 2145234, + "ascii_name": "Victoria", + "code": "AU.07" + }, + "geonames_admin2": { + "name": "Melbourne", + "id": 7839805, + "ascii_name": "Melbourne", + "code": "AU.07.24600" + }, + "license": { + "attribution": "Data from geonames.org under a CC-BY 3.0 license", + "license": "http://creativecommons.org/licenses/by/3.0/" + }, + "nuts_level1": { + "name": null, + "code": null + }, + "nuts_level2": { + "name": null, + "code": null + }, + "nuts_level3": { + "name": null, + "code": null + } + }, + "postcode": null, + "primary": false, + "line": null, + "country_geonames_id": 2077456 + } + ], + "links": [ + "http://www.alfred.org.au/" + ], + "aliases": [ + + ], + "acronyms": [ + + ], + "status": "active", + "wikipedia_url": "", + "labels": [ + + ], + "country": { + "country_name": "Australia", + "country_code": "AU" + }, + "external_ids": { + "ISNI": { + "preferred": null, + "all": [ + "0000 0004 0432 5259" + ] + }, + "FundRef": { + "preferred": null, + "all": [ + "501100002716" + ] + }, + "GRID": { + "preferred": "grid.267362.4", + "all": "grid.267362.4" + } + } + }, + { + "id": "https://ror.org/02c2f8975", + "name": "University of Ulsan", + "email_address": null, + "ip_addresses": [ + + ], + "established": 1970, + "types": [ + "Education" + ], + "relationships": [ + { + "label": "Ulsan University Hospital", + "type": "Related", + "id": "https://ror.org/03sab2a45" + } + ], + "addresses": [ + { + "lat": 35.542772, + "lng": 129.256725, + "state": null, + "state_code": null, + "city": "Ulsan", + "geonames_city": { + "id": 1833747, + "city": "Ulsan", + "geonames_admin1": { + "name": "Ulsan", + "id": 1833742, + "ascii_name": "Ulsan", + "code": "KR.21" + }, + "geonames_admin2": { + "name": null, + "id": null, + "ascii_name": null, + "code": null + }, + "license": { + "attribution": "Data from geonames.org under a CC-BY 3.0 license", + "license": "http://creativecommons.org/licenses/by/3.0/" + }, + "nuts_level1": { + "name": null, + "code": null + }, + "nuts_level2": { + "name": null, + "code": null + }, + "nuts_level3": { + "name": null, + "code": null + } + }, + "postcode": null, + "primary": false, + "line": null, + "country_geonames_id": 1835841 + } + ], + "links": [ + "http://en.ulsan.ac.kr/contents/main/" + ], + "aliases": [ + + ], + "acronyms": [ + "UOU" + ], + "status": "active", + "wikipedia_url": "http://en.wikipedia.org/wiki/University_of_Ulsan", + "labels": [ + { + "label": "울산대학교", + "iso639": "ko" + } + ], + "country": { + "country_name": "South Korea", + "country_code": "KR" + }, + "external_ids": { + "ISNI": { + "preferred": null, + "all": [ + "0000 0004 0533 4667" + ] + }, + "FundRef": { + "preferred": null, + "all": [ + "501100002568" + ] + }, + "OrgRef": { + "preferred": "10458246", + "all": [ + "10458246", + "15162872" + ] + }, + "Wikidata": { + "preferred": null, + "all": [ + "Q491717" + ] + }, + "GRID": { + "preferred": "grid.267370.7", + "all": "grid.267370.7" + } + } + }, + { + "id": "https://ror.org/010acrp16", + "name": "University of West Alabama", + "email_address": null, + "ip_addresses": [ + + ], + "established": 1835, + "types": [ + "Education" + ], + "relationships": [ + + ], + "addresses": [ + { + "lat": 32.59, + "lng": -88.186, + "state": "Alabama", + "state_code": "US-AL", + "city": "Livingston", + "geonames_city": { + "id": 4073383, + "city": "Livingston", + "geonames_admin1": { + "name": "Alabama", + "id": 4829764, + "ascii_name": "Alabama", + "code": "US.AL" + }, + "geonames_admin2": { + "name": "Sumter County", + "id": 4092386, + "ascii_name": "Sumter County", + "code": "US.AL.119" + }, + "license": { + "attribution": "Data from geonames.org under a CC-BY 3.0 license", + "license": "http://creativecommons.org/licenses/by/3.0/" + }, + "nuts_level1": { + "name": null, + "code": null + }, + "nuts_level2": { + "name": null, + "code": null + }, + "nuts_level3": { + "name": null, + "code": null + } + }, + "postcode": null, + "primary": false, + "line": null, + "country_geonames_id": 6252001 + } + ], + "links": [ + "http://www.uwa.edu/" + ], + "aliases": [ + "Livingston Female Academy" + ], + "acronyms": [ + "UWA" + ], + "status": "active", + "wikipedia_url": "http://en.wikipedia.org/wiki/University_of_West_Alabama", + "labels": [ + + ], + "country": { + "country_name": "United States", + "country_code": "US" + }, + "external_ids": { + "ISNI": { + "preferred": null, + "all": [ + "0000 0000 9963 9197" + ] + }, + "OrgRef": { + "preferred": null, + "all": [ + "2425212" + ] + }, + "Wikidata": { + "preferred": null, + "all": [ + "Q637346" + ] + }, + "GRID": { + "preferred": "grid.267434.0", + "all": "grid.267434.0" + } + } + }, + { + "id": "https://ror.org/002w4zy91", + "name": "University of West Florida", + "email_address": null, + "ip_addresses": [ + + ], + "established": 1963, + "types": [ + "Education" + ], + "relationships": [ + { + "label": "State University System of Florida", + "type": "Parent", + "id": "https://ror.org/05sqd3t97" + } + ], + "addresses": [ + { + "lat": 30.549493, + "lng": -87.21812, + "state": "Florida", + "state_code": "US-FL", + "city": "Pensacola", + "geonames_city": { + "id": 4168228, + "city": "Pensacola", + "geonames_admin1": { + "name": "Florida", + "id": 4155751, + "ascii_name": "Florida", + "code": "US.FL" + }, + "geonames_admin2": { + "name": "Escambia County", + "id": 4154550, + "ascii_name": "Escambia County", + "code": "US.FL.033" + }, + "license": { + "attribution": "Data from geonames.org under a CC-BY 3.0 license", + "license": "http://creativecommons.org/licenses/by/3.0/" + }, + "nuts_level1": { + "name": null, + "code": null + }, + "nuts_level2": { + "name": null, + "code": null + }, + "nuts_level3": { + "name": null, + "code": null + } + }, + "postcode": null, + "primary": false, + "line": null, + "country_geonames_id": 6252001 + } + ], + "links": [ + "http://uwf.edu/" + ], + "aliases": [ + + ], + "acronyms": [ + "UWF" + ], + "status": "active", + "wikipedia_url": "http://en.wikipedia.org/wiki/University_of_West_Florida", + "labels": [ + + ], + "country": { + "country_name": "United States", + "country_code": "US" + }, + "external_ids": { + "ISNI": { + "preferred": null, + "all": [ + "0000 0001 2112 2427" + ] + }, + "FundRef": { + "preferred": null, + "all": [ + "100009842" + ] + }, + "OrgRef": { + "preferred": null, + "all": [ + "750756" + ] + }, + "Wikidata": { + "preferred": null, + "all": [ + "Q659255" + ] + }, + "GRID": { + "preferred": "grid.267436.2", + "all": "grid.267436.2" + } + } + }, + { + "id": "https://ror.org/01cqxk816", + "name": "University of West Georgia", + "email_address": null, + "ip_addresses": [ + + ], + "established": 1906, + "types": [ + "Education" + ], + "relationships": [ + { + "label": "University System of Georgia", + "type": "Parent", + "id": "https://ror.org/017wcm924" + } + ], + "addresses": [ + { + "lat": 33.573357, + "lng": -85.099593, + "state": "Georgia", + "state_code": "US-GA", + "city": "Carrollton", + "geonames_city": { + "id": 4186416, + "city": "Carrollton", + "geonames_admin1": { + "name": "Georgia", + "id": 4197000, + "ascii_name": "Georgia", + "code": "US.GA" + }, + "geonames_admin2": { + "name": "Carroll County", + "id": 4186396, + "ascii_name": "Carroll County", + "code": "US.GA.045" + }, + "license": { + "attribution": "Data from geonames.org under a CC-BY 3.0 license", + "license": "http://creativecommons.org/licenses/by/3.0/" + }, + "nuts_level1": { + "name": null, + "code": null + }, + "nuts_level2": { + "name": null, + "code": null + }, + "nuts_level3": { + "name": null, + "code": null + } + }, + "postcode": null, + "primary": false, + "line": null, + "country_geonames_id": 6252001 + } + ], + "links": [ + "http://www.westga.edu/" + ], + "aliases": [ + + ], + "acronyms": [ + "UWG" + ], + "status": "active", + "wikipedia_url": "http://en.wikipedia.org/wiki/University_of_West_Georgia", + "labels": [ + + ], + "country": { + "country_name": "United States", + "country_code": "US" + }, + "external_ids": { + "ISNI": { + "preferred": null, + "all": [ + "0000 0001 2223 6696" + ] + }, + "FundRef": { + "preferred": null, + "all": [ + "100007922" + ] + }, + "OrgRef": { + "preferred": null, + "all": [ + "595315" + ] + }, + "Wikidata": { + "preferred": null, + "all": [ + "Q2495945" + ] + }, + "GRID": { + "preferred": "grid.267437.3", + "all": "grid.267437.3" + } + } + }, + { + "id": "https://ror.org/03c8vvr84", + "name": "University of Western States", + "email_address": null, + "ip_addresses": [ + + ], + "established": 1904, + "types": [ + "Education" + ], + "relationships": [ + + ], + "addresses": [ + { + "lat": 45.543351, + "lng": -122.523973, + "state": "Oregon", + "state_code": "US-OR", + "city": "Portland", + "geonames_city": { + "id": 5746545, + "city": "Portland", + "geonames_admin1": { + "name": "Oregon", + "id": 5744337, + "ascii_name": "Oregon", + "code": "US.OR" + }, + "geonames_admin2": { + "name": "Multnomah County", + "id": 5742126, + "ascii_name": "Multnomah County", + "code": "US.OR.051" + }, + "license": { + "attribution": "Data from geonames.org under a CC-BY 3.0 license", + "license": "http://creativecommons.org/licenses/by/3.0/" + }, + "nuts_level1": { + "name": null, + "code": null + }, + "nuts_level2": { + "name": null, + "code": null + }, + "nuts_level3": { + "name": null, + "code": null + } + }, + "postcode": null, + "primary": false, + "line": null, + "country_geonames_id": 6252001 + } + ], + "links": [ + "http://www.uws.edu/" + ], + "aliases": [ + "Western States Chiropractic College" + ], + "acronyms": [ + "UWS" + ], + "status": "active", + "wikipedia_url": "http://en.wikipedia.org/wiki/University_of_Western_States", + "labels": [ + + ], + "country": { + "country_name": "United States", + "country_code": "US" + }, + "external_ids": { + "ISNI": { + "preferred": null, + "all": [ + "0000 0004 0455 9493" + ] + }, + "OrgRef": { + "preferred": null, + "all": [ + "1655050" + ] + }, + "Wikidata": { + "preferred": null, + "all": [ + "Q7896612" + ] + }, + "GRID": { + "preferred": "grid.267451.3", + "all": "grid.267451.3" + } + } + }, + { + "id": "https://ror.org/03fmjzx88", + "name": "University of Winchester", + "email_address": null, + "ip_addresses": [ + + ], + "established": 1840, + "types": [ + "Education" + ], + "relationships": [ + + ], + "addresses": [ + { + "lat": 51.060338, + "lng": -1.325418, + "state": null, + "state_code": null, + "city": "Winchester", + "geonames_city": { + "id": 2633858, + "city": "Winchester", + "geonames_admin1": { + "name": "England", + "id": 6269131, + "ascii_name": "England", + "code": "GB.ENG" + }, + "geonames_admin2": { + "name": "Hampshire", + "id": 2647554, + "ascii_name": "Hampshire", + "code": "GB.ENG.F2" + }, + "license": { + "attribution": "Data from geonames.org under a CC-BY 3.0 license", + "license": "http://creativecommons.org/licenses/by/3.0/" + }, + "nuts_level1": { + "name": "SOUTH EAST (ENGLAND)", + "code": "UKJ" + }, + "nuts_level2": { + "name": "Hampshire and Isle of Wight", + "code": "UKJ3" + }, + "nuts_level3": { + "name": "Central Hampshire", + "code": "UKJ36" + } + }, + "postcode": null, + "primary": false, + "line": null, + "country_geonames_id": 2635167 + } + ], + "links": [ + "http://www.winchester.ac.uk/pages/home.aspx" + ], + "aliases": [ + + ], + "acronyms": [ + + ], + "status": "active", + "wikipedia_url": "http://en.wikipedia.org/wiki/University_of_Winchester", + "labels": [ + + ], + "country": { + "country_name": "United Kingdom", + "country_code": "GB" + }, + "external_ids": { + "ISNI": { + "preferred": null, + "all": [ + "0000 0000 9422 2878" + ] + }, + "FundRef": { + "preferred": null, + "all": [ + "100010057" + ] + }, + "HESA": { + "preferred": null, + "all": [ + "0021" + ] + }, + "UCAS": { + "preferred": null, + "all": [ + "W76" + ] + }, + "UKPRN": { + "preferred": null, + "all": [ + "10003614" + ] + }, + "OrgRef": { + "preferred": null, + "all": [ + "3140939" + ] + }, + "Wikidata": { + "preferred": null, + "all": [ + "Q3551690" + ] + }, + "GRID": { + "preferred": "grid.267454.6", + "all": "grid.267454.6" + } + } + }, + { + "id": "https://ror.org/01gw3d370", + "name": "University of Windsor", + "email_address": "", + "ip_addresses": [ + + ], + "established": 1857, + "types": [ + "Education" + ], + "relationships": [ + + ], + "addresses": [ + { + "lat": 42.305196, + "lng": -83.067483, + "state": "Ontario", + "state_code": "CA-ON", + "city": "Windsor", + "geonames_city": { + "id": 6182962, + "city": "Windsor", + "geonames_admin1": { + "name": "Ontario", + "id": 6093943, + "ascii_name": "Ontario", + "code": "CA.08" + }, + "geonames_admin2": { + "name": null, + "id": null, + "ascii_name": null, + "code": null + }, + "license": { + "attribution": "Data from geonames.org under a CC-BY 3.0 license", + "license": "http://creativecommons.org/licenses/by/3.0/" + }, + "nuts_level1": { + "name": null, + "code": null + }, + "nuts_level2": { + "name": null, + "code": null + }, + "nuts_level3": { + "name": null, + "code": null + } + }, + "postcode": null, + "primary": false, + "line": null, + "country_geonames_id": 6251999 + } + ], + "links": [ + "http://www.uwindsor.ca/" + ], + "aliases": [ + "UWindsor", + "Assumption University of Windsor" + ], + "acronyms": [ + + ], + "status": "active", + "wikipedia_url": "http://en.wikipedia.org/wiki/University_of_Windsor", + "labels": [ + { + "label": "Université de windsor", + "iso639": "fr" + } + ], + "country": { + "country_name": "Canada", + "country_code": "CA" + }, + "external_ids": { + "ISNI": { + "preferred": null, + "all": [ + "0000 0004 1936 9596" + ] + }, + "FundRef": { + "preferred": "100009154", + "all": [ + "100009154", + "501100000083" + ] + }, + "OrgRef": { + "preferred": null, + "all": [ + "342733" + ] + }, + "Wikidata": { + "preferred": null, + "all": [ + "Q2065769" + ] + }, + "GRID": { + "preferred": "grid.267455.7", + "all": "grid.267455.7" + } + } + }, + { + "id": "https://ror.org/02gdzyx04", + "name": "University of Winnipeg", + "email_address": null, + "ip_addresses": [ + + ], + "established": 1871, + "types": [ + "Education" + ], + "relationships": [ + { + "label": "Winnipeg Institute for Theoretical Physics", + "type": "Child", + "id": "https://ror.org/010tw2j24" + } + ], + "addresses": [ + { + "lat": 49.890122, + "lng": -97.153367, + "state": "Manitoba", + "state_code": "CA-MB", + "city": "Winnipeg", + "geonames_city": { + "id": 6183235, + "city": "Winnipeg", + "geonames_admin1": { + "name": "Manitoba", + "id": 6065171, + "ascii_name": "Manitoba", + "code": "CA.03" + }, + "geonames_admin2": { + "name": null, + "id": null, + "ascii_name": null, + "code": null + }, + "license": { + "attribution": "Data from geonames.org under a CC-BY 3.0 license", + "license": "http://creativecommons.org/licenses/by/3.0/" + }, + "nuts_level1": { + "name": null, + "code": null + }, + "nuts_level2": { + "name": null, + "code": null + }, + "nuts_level3": { + "name": null, + "code": null + } + }, + "postcode": null, + "primary": false, + "line": null, + "country_geonames_id": 6251999 + } + ], + "links": [ + "http://www.uwinnipeg.ca/" + ], + "aliases": [ + + ], + "acronyms": [ + + ], + "status": "active", + "wikipedia_url": "http://en.wikipedia.org/wiki/University_of_Winnipeg", + "labels": [ + { + "label": "Université de winnipeg", + "iso639": "fr" + } + ], + "country": { + "country_name": "Canada", + "country_code": "CA" + }, + "external_ids": { + "ISNI": { + "preferred": null, + "all": [ + "0000 0001 1703 4731" + ] + }, + "FundRef": { + "preferred": null, + "all": [ + "100009367" + ] + }, + "OrgRef": { + "preferred": null, + "all": [ + "587404" + ] + }, + "Wikidata": { + "preferred": null, + "all": [ + "Q472167" + ] + }, + "GRID": { + "preferred": "grid.267457.5", + "all": "grid.267457.5" + } + } + }, + { + "id": "https://ror.org/03mnm0t94", + "name": "University of Wisconsin–Eau Claire", + "email_address": "", + "ip_addresses": [ + + ], + "established": 1916, + "types": [ + "Education" + ], + "relationships": [ + { + "label": "University of Wisconsin System", + "type": "Parent", + "id": "https://ror.org/03ydkyb10" + } + ], + "addresses": [ + { + "lat": 44.79895, + "lng": -91.499346, + "state": "Wisconsin", + "state_code": "US-WI", + "city": "Eau Claire", + "geonames_city": { + "id": 5251436, + "city": "Eau Claire", + "geonames_admin1": { + "name": "Wisconsin", + "id": 5279468, + "ascii_name": "Wisconsin", + "code": "US.WI" + }, + "geonames_admin2": { + "name": "Eau Claire County", + "id": 5251439, + "ascii_name": "Eau Claire County", + "code": "US.WI.035" + }, + "license": { + "attribution": "Data from geonames.org under a CC-BY 3.0 license", + "license": "http://creativecommons.org/licenses/by/3.0/" + }, + "nuts_level1": { + "name": null, + "code": null + }, + "nuts_level2": { + "name": null, + "code": null + }, + "nuts_level3": { + "name": null, + "code": null + } + }, + "postcode": null, + "primary": false, + "line": null, + "country_geonames_id": 6252001 + } + ], + "links": [ + "http://www.uwec.edu/" + ], + "aliases": [ + + ], + "acronyms": [ + "UWEC" + ], + "status": "active", + "wikipedia_url": "http://en.wikipedia.org/wiki/University_of_Wisconsin%E2%80%93Eau_Claire", + "labels": [ + { + "label": "Université du Wisconsin à Eau Claire", + "iso639": "fr" + } + ], + "country": { + "country_name": "United States", + "country_code": "US" + }, + "external_ids": { + "ISNI": { + "preferred": null, + "all": [ + "0000 0001 2227 2494" + ] + }, + "FundRef": { + "preferred": null, + "all": [ + "100010315" + ] + }, + "OrgRef": { + "preferred": null, + "all": [ + "496729" + ] + }, + "Wikidata": { + "preferred": null, + "all": [ + "Q3551771" + ] + }, + "GRID": { + "preferred": "grid.267460.1", + "all": "grid.267460.1" + } + } + }, + { + "id": "https://ror.org/05hbexn54", + "name": "University of Wisconsin–Green Bay", + "email_address": null, + "ip_addresses": [ + + ], + "established": 1965, + "types": [ + "Education" + ], + "relationships": [ + { + "label": "University of Wisconsin System", + "type": "Parent", + "id": "https://ror.org/03ydkyb10" + } + ], + "addresses": [ + { + "lat": 44.533203, + "lng": -87.921521, + "state": "Wisconsin", + "state_code": "US-WI", + "city": "Green Bay", + "geonames_city": { + "id": 5254962, + "city": "Green Bay", + "geonames_admin1": { + "name": "Wisconsin", + "id": 5279468, + "ascii_name": "Wisconsin", + "code": "US.WI" + }, + "geonames_admin2": { + "name": "Brown County", + "id": 5246898, + "ascii_name": "Brown County", + "code": "US.WI.009" + }, + "license": { + "attribution": "Data from geonames.org under a CC-BY 3.0 license", + "license": "http://creativecommons.org/licenses/by/3.0/" + }, + "nuts_level1": { + "name": null, + "code": null + }, + "nuts_level2": { + "name": null, + "code": null + }, + "nuts_level3": { + "name": null, + "code": null + } + }, + "postcode": null, + "primary": false, + "line": null, + "country_geonames_id": 6252001 + } + ], + "links": [ + "http://www.uwgb.edu/" + ], + "aliases": [ + + ], + "acronyms": [ + "UWGB" + ], + "status": "active", + "wikipedia_url": "http://en.wikipedia.org/wiki/University_of_Wisconsin%E2%80%93Green_Bay", + "labels": [ + { + "label": "Université du Wisconsin–Green Bay", + "iso639": "fr" + } + ], + "country": { + "country_name": "United States", + "country_code": "US" + }, + "external_ids": { + "ISNI": { + "preferred": null, + "all": [ + "0000 0001 0559 7692" + ] + }, + "OrgRef": { + "preferred": null, + "all": [ + "1513886" + ] + }, + "Wikidata": { + "preferred": null, + "all": [ + "Q2378091" + ] + }, + "GRID": { + "preferred": "grid.267461.0", + "all": "grid.267461.0" + } + } + }, + { + "id": "https://ror.org/00x8ccz20", + "name": "University of Wisconsin–La Crosse", + "email_address": "", + "ip_addresses": [ + + ], + "established": 1909, + "types": [ + "Education" + ], + "relationships": [ + { + "label": "University of Wisconsin System", + "type": "Parent", + "id": "https://ror.org/03ydkyb10" + } + ], + "addresses": [ + { + "lat": 43.815576, + "lng": -91.233517, + "state": "Wisconsin", + "state_code": "US-WI", + "city": "La Crosse", + "geonames_city": { + "id": 5258957, + "city": "La Crosse", + "geonames_admin1": { + "name": "Wisconsin", + "id": 5279468, + "ascii_name": "Wisconsin", + "code": "US.WI" + }, + "geonames_admin2": { + "name": "La Crosse County", + "id": 5258961, + "ascii_name": "La Crosse County", + "code": "US.WI.063" + }, + "license": { + "attribution": "Data from geonames.org under a CC-BY 3.0 license", + "license": "http://creativecommons.org/licenses/by/3.0/" + }, + "nuts_level1": { + "name": null, + "code": null + }, + "nuts_level2": { + "name": null, + "code": null + }, + "nuts_level3": { + "name": null, + "code": null + } + }, + "postcode": null, + "primary": false, + "line": null, + "country_geonames_id": 6252001 + } + ], + "links": [ + "http://www.uwlax.edu/Home/Future-Students/" + ], + "aliases": [ + + ], + "acronyms": [ + "UW–L" + ], + "status": "active", + "wikipedia_url": "http://en.wikipedia.org/wiki/University_of_Wisconsin%E2%80%93La_Crosse", + "labels": [ + { + "label": "Université du Wisconsin–La Crosse", + "iso639": "fr" + } + ], + "country": { + "country_name": "United States", + "country_code": "US" + }, + "external_ids": { + "ISNI": { + "preferred": null, + "all": [ + "0000 0001 2169 5137" + ] + }, + "OrgRef": { + "preferred": null, + "all": [ + "2422287" + ] + }, + "Wikidata": { + "preferred": null, + "all": [ + "Q2688358" + ] + }, + "GRID": { + "preferred": "grid.267462.3", + "all": "grid.267462.3" + } + } + } + ], + "meta": { + "types": [ + { + "id": "company", + "title": "Company", + "count": 29790 + }, + { + "id": "education", + "title": "Education", + "count": 20325 + }, + { + "id": "nonprofit", + "title": "Nonprofit", + "count": 14187 + }, + { + "id": "healthcare", + "title": "Healthcare", + "count": 13107 + }, + { + "id": "facility", + "title": "Facility", + "count": 10080 + }, + { + "id": "other", + "title": "Other", + "count": 8369 + }, + { + "id": "government", + "title": "Government", + "count": 6511 + }, + { + "id": "archive", + "title": "Archive", + "count": 2967 + } + ], + "countries": [ + { + "id": "us", + "title": "United States", + "count": 31196 + }, + { + "id": "gb", + "title": "United Kingdom", + "count": 7410 + }, + { + "id": "de", + "title": "Germany", + "count": 5189 + }, + { + "id": "cn", + "title": "China", + "count": 4846 + }, + { + "id": "fr", + "title": "France", + "count": 4344 + }, + { + "id": "jp", + "title": "Japan", + "count": 3940 + }, + { + "id": "ca", + "title": "Canada", + "count": 3392 + }, + { + "id": "in", + "title": "India", + "count": 3075 + }, + { + "id": "cz", + "title": "Czech Republic", + "count": 2780 + }, + { + "id": "ru", + "title": "Russia", + "count": 2109 + } + ], + "statuses": [ + { + "id": "active", + "title": "active", + "count": 105336 + } + ] + } +} \ No newline at end of file diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index d38ffd6433..0d293ac097 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -1663,3 +1663,4 @@ include = ${module_dir}/usage-statistics.cfg include = ${module_dir}/versioning.cfg include = ${module_dir}/workflow.cfg include = ${module_dir}/external-providers.cfg +include = ${module_dir}/ror.cfg diff --git a/dspace/config/modules/ror.cfg b/dspace/config/modules/ror.cfg new file mode 100644 index 0000000000..4a7a6cfa4f --- /dev/null +++ b/dspace/config/modules/ror.cfg @@ -0,0 +1,2 @@ +ror.orgunit-import.api-url = https://api.ror.org/organizations +ror.authority.prefix = will be referenced::ROR-ID:: \ No newline at end of file diff --git a/dspace/config/registries/openaire-cerif-types.xml b/dspace/config/registries/openaire-cerif-types.xml new file mode 100644 index 0000000000..07f6da4313 --- /dev/null +++ b/dspace/config/registries/openaire-cerif-types.xml @@ -0,0 +1,27 @@ + + + + + + OpenAIRE Types + + + + oairecerif + https://www.openaire.eu/cerif-profile/1.1/ + + + + oairecerif + identifier + url + + + + + oairecerif + acronym + + + + \ No newline at end of file diff --git a/dspace/config/registries/schema-organization-types.xml b/dspace/config/registries/schema-organization-types.xml index 91ee203ae9..a9739796e6 100644 --- a/dspace/config/registries/schema-organization-types.xml +++ b/dspace/config/registries/schema-organization-types.xml @@ -80,6 +80,12 @@ crossrefid Crossref identifier + + + organization + parentOrganization + + diff --git a/dspace/config/spring/api/external-services.xml b/dspace/config/spring/api/external-services.xml index 6d7d50c39f..831a387239 100644 --- a/dspace/config/spring/api/external-services.xml +++ b/dspace/config/spring/api/external-services.xml @@ -93,7 +93,7 @@ - + @@ -199,6 +199,18 @@ + + + + + + + + + OrgUnit + + + diff --git a/dspace/config/spring/api/ror-integration.xml b/dspace/config/spring/api/ror-integration.xml new file mode 100644 index 0000000000..65de4997e0 --- /dev/null +++ b/dspace/config/spring/api/ror-integration.xml @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dspace/config/submission-forms.xml b/dspace/config/submission-forms.xml index 39a4778356..241acc6d3b 100644 --- a/dspace/config/submission-forms.xml +++ b/dspace/config/submission-forms.xml @@ -531,18 +531,18 @@ - isProjectOfOrgUnit + isOrgUnitOfProject orgunit false Enter the organization's name - project - funder - name + dc + contributor + other onebox - + ror From 95056d509c0e37630560a14cf4536ff85be766c8 Mon Sep 17 00:00:00 2001 From: frabacche Date: Thu, 14 Dec 2023 17:04:16 +0100 Subject: [PATCH 298/412] 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 ea923251b8..2332a55caf 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 9be4af2d08..1dfcc1b6d9 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.client.solrj.response.QueryResponse; 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 class QAEventServiceImpl implements QAEventService { } @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 class QAEventServiceImpl implements QAEventService { 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 a279cac83a..97a0ee9781 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.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.data.domain.Sort.Direction; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Component; @@ -30,6 +31,8 @@ import org.springframework.stereotype.Component; @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 class QATopicRestRepository extends DSpaceRestRepository 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 class QATopicRestRepository extends DSpaceRestRepository 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 299/412] 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 35d9231ea5..6bb979f48b 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 class OpenaireEventsImportIT extends AbstractIntegrationTestWithDatabase 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 class OpenaireEventsImportIT extends AbstractIntegrationTestWithDatabase 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 class OpenaireEventsImportIT extends AbstractIntegrationTestWithDatabase 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 class OpenaireEventsImportIT extends AbstractIntegrationTestWithDatabase 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 class OpenaireEventsImportIT extends AbstractIntegrationTestWithDatabase 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 class OpenaireEventsImportIT extends AbstractIntegrationTestWithDatabase 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 class OpenaireEventsImportIT extends AbstractIntegrationTestWithDatabase 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 300/412] 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 0000000000..ce0644eb91 --- /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 da5080d589..6cbaa12084 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 301/412] 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 ce0644eb91..8d45a8a978 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 302/412] 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 489599ea76..9e90f81be3 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 2ec08be055..c96fad1de0 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 class OpenaireRestConnector { 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 154bf737d9..823080516d 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 8d45a8a978..332c7a5989 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 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.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 @@ import org.springframework.stereotype.Component; public class QAAuthorizationFeature implements AuthorizationFeature { public final static String NAME = "canSeeQA"; + @Autowired private ConfigurationService configurationService; @Override @@ -38,7 +40,7 @@ public class QAAuthorizationFeature implements AuthorizationFeature { @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 0000000000..f3e508485a --- /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 a7b0d9050c..9bcabdf0e9 100644 --- a/dspace/config/crosswalks/oai/xoai.xml +++ b/dspace/config/crosswalks/oai/xoai.xml @@ -526,7 +526,7 @@ openaire - Openaire + OpenAIRE From 91db1c07e3da864cb31653ee4b2849db058a9a1f Mon Sep 17 00:00:00 2001 From: Vincenzo Mecca Date: Tue, 19 Dec 2023 17:38:48 +0100 Subject: [PATCH 303/412] [CST-12826] ROR integration for OAI-PMH --- .../oai/metadataFormats/oai_openaire.xsl | 7 +++++++ .../spring/api/virtual-metadata.xml.openaire4 | 18 ++++++++++++++++-- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/dspace/config/crosswalks/oai/metadataFormats/oai_openaire.xsl b/dspace/config/crosswalks/oai/metadataFormats/oai_openaire.xsl index 16c63c9c1a..7e9ff074ca 100644 --- a/dspace/config/crosswalks/oai/metadataFormats/oai_openaire.xsl +++ b/dspace/config/crosswalks/oai/metadataFormats/oai_openaire.xsl @@ -457,6 +457,13 @@ + + + + + + + diff --git a/dspace/config/spring/api/virtual-metadata.xml.openaire4 b/dspace/config/spring/api/virtual-metadata.xml.openaire4 index bbdae0e759..9d0a662550 100644 --- a/dspace/config/spring/api/virtual-metadata.xml.openaire4 +++ b/dspace/config/spring/api/virtual-metadata.xml.openaire4 @@ -167,6 +167,7 @@ + @@ -201,15 +202,21 @@ --> - + - + + + + + + + ${basedir}/../../.. @@ -67,6 +68,31 @@ + + org.apache.maven.plugins + maven-dependency-plugin + + + unpack + prepare-package + + unpack + + + + + org.dspace + dspace-server-webapp + ${dspace.version} + jar + **/static/**,**/*.properties + ${project.build.directory}/additions + + + + + + ${basedir}/../../.. From 6ea529325078e92733680907ae36638250c1c34f Mon Sep 17 00:00:00 2001 From: Vincenzo Mecca Date: Thu, 28 Dec 2023 16:22:48 +0100 Subject: [PATCH 324/412] [DSC-963] Fixes testResources generation --- dspace-server-webapp/pom.xml | 49 +++++++++++++++++++++++++++--------- 1 file changed, 37 insertions(+), 12 deletions(-) diff --git a/dspace-server-webapp/pom.xml b/dspace-server-webapp/pom.xml index 05824e93fd..5671a2c79b 100644 --- a/dspace-server-webapp/pom.xml +++ b/dspace-server-webapp/pom.xml @@ -50,18 +50,43 @@ maven-resources-plugin - - - - ${basedir}/src/main/resources - - **/*.properties - **/static/** - - true - - - + + + testEnvironment + process-resources + + testResources + + + + + ${basedir}/src/test/resources + true + + + + + + webappFiltering + process-resources + + resources + + + + + ${basedir}/src/main/resources + + **/*.properties + **/static/** + **/spring/** + + true + + + + + org.apache.maven.plugins From 21b8c1b6a12cc7db056e0104e188bfa244e72b97 Mon Sep 17 00:00:00 2001 From: Vincenzo Mecca Date: Thu, 28 Dec 2023 17:09:57 +0100 Subject: [PATCH 325/412] [DSC-963] Fixes server unpacking --- dspace/modules/server/pom.xml | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/dspace/modules/server/pom.xml b/dspace/modules/server/pom.xml index a0d1411b69..c707028add 100644 --- a/dspace/modules/server/pom.xml +++ b/dspace/modules/server/pom.xml @@ -76,19 +76,14 @@ unpack prepare-package - unpack + unpack-dependencies - - - org.dspace - dspace-server-webapp - ${dspace.version} - jar - **/static/**,**/*.properties - ${project.build.directory}/additions - - + runtime + org.dspace + dspace-server-webapp + **/static/**,**/*.properties + ${project.build.directory}/additions From bb0693f3c607a58de86ed8e696030a78f9243c63 Mon Sep 17 00:00:00 2001 From: Vincenzo Mecca Date: Fri, 29 Dec 2023 09:31:20 +0100 Subject: [PATCH 326/412] [DSC-963] Fixes resource copy and filtering --- dspace-server-webapp/pom.xml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/dspace-server-webapp/pom.xml b/dspace-server-webapp/pom.xml index 5671a2c79b..d5de9611d9 100644 --- a/dspace-server-webapp/pom.xml +++ b/dspace-server-webapp/pom.xml @@ -61,7 +61,6 @@ ${basedir}/src/test/resources - true @@ -78,10 +77,15 @@ ${basedir}/src/main/resources **/*.properties + + true + + + ${basedir}/src/main/resources + **/static/** **/spring/** - true From e4da12ed2daa0933a46757d5a00161ee3a445c09 Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Fri, 29 Dec 2023 14:57:09 +0100 Subject: [PATCH 327/412] Alteration to index-discovery script to only (re-)index specific type of IndexableObject Not compatible with `-b` option since this clears entire index first (& expect to rebuild it in its entirety) Compatible with `-f` to force reindex specific type of IndexableObject --- .../java/org/dspace/discovery/IndexClient.java | 16 ++++++++++++++-- .../org/dspace/discovery/IndexClientOptions.java | 11 +++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/discovery/IndexClient.java b/dspace-api/src/main/java/org/dspace/discovery/IndexClient.java index 661c48d91c..a01d43ce8e 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/IndexClient.java +++ b/dspace-api/src/main/java/org/dspace/discovery/IndexClient.java @@ -7,6 +7,8 @@ */ package org.dspace.discovery; +import static org.dspace.discovery.IndexClientOptions.TYPE_OPTION; + import java.io.IOException; import java.sql.SQLException; import java.util.Iterator; @@ -15,6 +17,7 @@ import java.util.UUID; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.ParseException; +import org.apache.commons.lang3.StringUtils; import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.DSpaceObject; @@ -51,6 +54,11 @@ public class IndexClient extends DSpaceRunnable indexableObjectTypes = IndexObjectFactoryFactory.getInstance().getIndexFactories().stream() + .map((indexFactory -> indexFactory.getType())).collect(Collectors.toList()); options .addOption("r", "remove", true, "remove an Item, Collection or Community from index based on its handle"); options.addOption("i", "index", true, "add or update an Item, Collection or Community based on its handle or uuid"); + options.addOption(TYPE_OPTION, "type", true, "reindex only specific type of " + + "(re)indexable objects; options: " + Arrays.toString(indexableObjectTypes.toArray())); options.addOption("c", "clean", false, "clean existing index removing any documents that no longer exist in the db"); options.addOption("d", "delete", false, From 64ae49a29ff4edb87053e8319b23a6a60696f65a Mon Sep 17 00:00:00 2001 From: William Welling Date: Mon, 8 Jan 2024 10:22:52 -0600 Subject: [PATCH 328/412] Return headers for HEAD request --- .../dspace/app/rest/BitstreamRestController.java | 6 ++++++ .../app/rest/utils/HttpHeadersInitializer.java | 15 ++++++--------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java index ebd84270f2..063e4760fa 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java @@ -167,6 +167,12 @@ public class BitstreamRestController { //Send the data if (httpHeadersInitializer.isValid()) { HttpHeaders httpHeaders = httpHeadersInitializer.initialiseHeaders(); + + if (RequestMethod.HEAD.name().equals(request.getMethod())) { + log.debug("HEAD request - no response body"); + return ResponseEntity.ok().headers(httpHeaders).build(); + } + return ResponseEntity.ok().headers(httpHeaders).body(bitstreamResource); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/HttpHeadersInitializer.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/HttpHeadersInitializer.java index a69da4c5e8..854aec8868 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/HttpHeadersInitializer.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/HttpHeadersInitializer.java @@ -33,7 +33,6 @@ public class HttpHeadersInitializer { protected final Logger log = LoggerFactory.getLogger(this.getClass()); - private static final String METHOD_HEAD = "HEAD"; private static final String MULTIPART_BOUNDARY = "MULTIPART_BYTERANGES"; private static final String CONTENT_TYPE_MULTITYPE_WITH_BOUNDARY = "multipart/byteranges; boundary=" + MULTIPART_BOUNDARY; @@ -165,16 +164,14 @@ public class HttpHeadersInitializer { httpHeaders.put(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, Collections.singletonList(HttpHeaders.ACCEPT_RANGES)); - httpHeaders.put(CONTENT_DISPOSITION, Collections.singletonList(String.format(CONTENT_DISPOSITION_FORMAT, - disposition, - encodeText(fileName)))); - log.debug("Content-Disposition : {}", disposition); - // Content phase - if (METHOD_HEAD.equals(request.getMethod())) { - log.debug("HEAD request - skipping content"); - return null; + // distposition may be null here if contentType is null + if (!isNullOrEmpty(disposition)) { + httpHeaders.put(CONTENT_DISPOSITION, Collections.singletonList(String.format(CONTENT_DISPOSITION_FORMAT, + disposition, + encodeText(fileName)))); } + log.debug("Content-Disposition : {}", disposition); return httpHeaders; From 127b1ae868534b318f8fc5c0c21d300502183961 Mon Sep 17 00:00:00 2001 From: Alan Orth Date: Tue, 9 Jan 2024 11:34:52 +0300 Subject: [PATCH 329/412] dspace-api: fix typo in AuthorizeServiceImpl log --- .../main/java/org/dspace/authorize/AuthorizeServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java b/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java index 5dffe5fdfc..beff6fdc48 100644 --- a/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java @@ -895,7 +895,7 @@ public class AuthorizeServiceImpl implements AuthorizeService { return true; } } catch (SearchServiceException e) { - log.error("Failed getting getting community/collection admin status for " + log.error("Failed getting community/collection admin status for " + context.getCurrentUser().getEmail() + " The search error is: " + e.getMessage() + " The search resourceType filter was: " + query); } From 6be7e4e37047c3f33d4aec371240a3bf2f02a3ef Mon Sep 17 00:00:00 2001 From: William Welling Date: Wed, 10 Jan 2024 13:51:11 -0600 Subject: [PATCH 330/412] Add content-length to bitstream --- .../dspace/app/rest/BitstreamRestController.java | 1 + .../app/rest/utils/HttpHeadersInitializer.java | 4 ++++ .../dspace/app/rest/BitstreamRestControllerIT.java | 14 +++++++++++++- 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java index 063e4760fa..9b5ede37c8 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java @@ -138,6 +138,7 @@ public class BitstreamRestController { .withBufferSize(BUFFER_SIZE) .withFileName(name) .withChecksum(bit.getChecksum()) + .withLength(bit.getSizeBytes()) .withMimetype(mimetype) .with(request) .with(response); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/HttpHeadersInitializer.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/HttpHeadersInitializer.java index 854aec8868..d68c710a3c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/HttpHeadersInitializer.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/HttpHeadersInitializer.java @@ -14,6 +14,7 @@ import static javax.mail.internet.MimeUtility.encodeText; import java.io.IOException; import java.util.Arrays; import java.util.Collections; +import java.util.Objects; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -143,6 +144,9 @@ public class HttpHeadersInitializer { if (checksum != null) { httpHeaders.put(ETAG, Collections.singletonList(checksum)); } + if (Objects.nonNull((Long.valueOf(this.length)))) { + httpHeaders.put(HttpHeaders.CONTENT_LENGTH, Collections.singletonList(String.valueOf(this.length))); + } httpHeaders.put(LAST_MODIFIED, Collections.singletonList(FastHttpDateFormat.formatDate(lastModified))); httpHeaders.put(EXPIRES, Collections.singletonList(FastHttpDateFormat.formatDate( System.currentTimeMillis() + DEFAULT_EXPIRE_TIME))); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestControllerIT.java index 5fbf669bae..049b9b4318 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 @@ -206,6 +206,18 @@ public class BitstreamRestControllerIT extends AbstractControllerIntegrationTest } context.restoreAuthSystemState(); + //** WHEN ** + // we want to know what we are downloading before we download it + getClient().perform(head("/api/core/bitstreams/" + bitstream.getID() + "/content")) + //** THEN ** + .andExpect(status().isOk()) + + //The Content Length must match the full length + .andExpect(header().longValue("Content-Length", bitstreamContent.getBytes().length)) + .andExpect(header().string("Content-Type", "text/plain;charset=UTF-8")) + .andExpect(header().string("ETag", "\"" + bitstream.getChecksum() + "\"")) + .andExpect(content().bytes(new byte[] {})); + //** WHEN ** //We download the bitstream getClient().perform(get("/api/core/bitstreams/" + bitstream.getID() + "/content")) @@ -232,7 +244,7 @@ public class BitstreamRestControllerIT extends AbstractControllerIntegrationTest .andExpect(status().isNotModified()); //The download and head request should also be logged as a statistics record - checkNumberOfStatsRecords(bitstream, 2); + checkNumberOfStatsRecords(bitstream, 3); } @Test From 73e5c43f7c565166a7b4b920a8958be0cc0c6c01 Mon Sep 17 00:00:00 2001 From: Agustina Martinez Date: Wed, 10 Jan 2024 13:08:02 +0000 Subject: [PATCH 331/412] Skip recording usage event if administrator --- .../dspace/statistics/SolrLoggerServiceImpl.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/dspace-api/src/main/java/org/dspace/statistics/SolrLoggerServiceImpl.java b/dspace-api/src/main/java/org/dspace/statistics/SolrLoggerServiceImpl.java index 97585f5a47..5f976bbfd9 100644 --- a/dspace-api/src/main/java/org/dspace/statistics/SolrLoggerServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/statistics/SolrLoggerServiceImpl.java @@ -81,6 +81,7 @@ import org.apache.solr.common.params.MapSolrParams; import org.apache.solr.common.params.ModifiableSolrParams; import org.apache.solr.common.params.ShardParams; import org.apache.solr.common.util.NamedList; +import org.dspace.authorize.service.AuthorizeService; import org.dspace.content.Bitstream; import org.dspace.content.Bundle; import org.dspace.content.Collection; @@ -146,6 +147,8 @@ public class SolrLoggerServiceImpl implements SolrLoggerService, InitializingBea private SolrStatisticsCore solrStatisticsCore; @Autowired private GeoIpService geoIpService; + @Autowired + private AuthorizeService authorizeService; /** URL to the current-year statistics core. Prior-year shards will have a year suffixed. */ private String statisticsCoreURL; @@ -219,6 +222,16 @@ public class SolrLoggerServiceImpl implements SolrLoggerService, InitializingBea @Override public void postView(DSpaceObject dspaceObject, HttpServletRequest request, EPerson currentUser, String referrer) { + Context context = new Context(); + // Do not record statistics for Admin users + try { + if (authorizeService.isAdmin(context, currentUser)) { + return; + } + } catch (SQLException e) { + throw new RuntimeException(e); + } + if (solr == null) { return; } From 5c72d2fa653b24a00fb8153bf834d15bdecb0faa Mon Sep 17 00:00:00 2001 From: Agustina Martinez Date: Tue, 12 Dec 2023 18:40:51 +0000 Subject: [PATCH 332/412] Support for RIOXX v3 OAI profile --- .../main/java/org/dspace/xoai/app/XOAI.java | 10 + .../xoai/filter/ItemsWithBitstreamFilter.java | 57 + .../java/org/dspace/xoai/util/ItemUtils.java | 78 +- .../xoai/tests/stylesheets/RioxxXslTest.java | 35 + .../src/test/resources/rioxx-test-invalid.xml | 89 ++ .../src/test/resources/rioxx-test-valid.xml | 92 ++ .../src/test/resources/xoai-rioxx-test.xml | 217 +++ .../oai/metadataFormats/oai_openaire.xsl | 27 +- .../crosswalks/oai/metadataFormats/rioxx.xsl | 1236 +++++++++++++++++ .../crosswalks/oai/transformers/rioxx.xsl | 361 +++++ dspace/config/crosswalks/oai/xoai.xml | 128 +- .../config/entities/rioxx3-relationships.xml | 91 ++ .../config/registries/dublin-core-types.xml | 1 + .../spring/api/virtual-metadata.xml.rioxx3 | 402 ++++++ dspace/solr/oai/conf/schema.xml | 4 +- 15 files changed, 2810 insertions(+), 18 deletions(-) create mode 100644 dspace-oai/src/main/java/org/dspace/xoai/filter/ItemsWithBitstreamFilter.java create mode 100644 dspace-oai/src/test/java/org/dspace/xoai/tests/stylesheets/RioxxXslTest.java create mode 100644 dspace-oai/src/test/resources/rioxx-test-invalid.xml create mode 100644 dspace-oai/src/test/resources/rioxx-test-valid.xml create mode 100644 dspace-oai/src/test/resources/xoai-rioxx-test.xml create mode 100644 dspace/config/crosswalks/oai/metadataFormats/rioxx.xsl create mode 100644 dspace/config/crosswalks/oai/transformers/rioxx.xsl create mode 100644 dspace/config/entities/rioxx3-relationships.xml create mode 100644 dspace/config/spring/api/virtual-metadata.xml.rioxx3 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 4f842b8e94..25cc1ee365 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 @@ -450,6 +450,16 @@ public class XOAI { doc.addField("item.communities", "com_" + com.getHandle().replace("/", "_")); } + boolean hasBitstream = false; + + for (Bundle b : item.getBundles("ORIGINAL")) { + if (b.getBitstreams().size() > 0) { + hasBitstream = true; + } + } + + doc.addField("item.hasbitstream", hasBitstream); + List allData = itemService.getMetadata(item, Item.ANY, Item.ANY, Item.ANY, Item.ANY); for (MetadataValue dc : allData) { MetadataField field = dc.getMetadataField(); diff --git a/dspace-oai/src/main/java/org/dspace/xoai/filter/ItemsWithBitstreamFilter.java b/dspace-oai/src/main/java/org/dspace/xoai/filter/ItemsWithBitstreamFilter.java new file mode 100644 index 0000000000..3599c5b9e1 --- /dev/null +++ b/dspace-oai/src/main/java/org/dspace/xoai/filter/ItemsWithBitstreamFilter.java @@ -0,0 +1,57 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.xoai.filter; + +import java.sql.SQLException; + +import org.apache.log4j.LogManager; +import org.apache.log4j.Logger; +import org.dspace.content.Bundle; +import org.dspace.content.Item; +import org.dspace.handle.factory.HandleServiceFactory; +import org.dspace.handle.service.HandleService; +import org.dspace.xoai.data.DSpaceItem; +import org.dspace.xoai.filter.results.SolrFilterResult; + + +/** + * Created by Philip Vissenaekens (philip at atmire dot com) + * Date: 21/04/15 + * Time: 15:18 + */ +public class ItemsWithBitstreamFilter extends DSpaceFilter { + + private static Logger log = LogManager.getLogger(ItemsWithBitstreamFilter.class); + + private static final HandleService handleService + = HandleServiceFactory.getInstance().getHandleService(); + + @Override + public SolrFilterResult buildSolrQuery() { + return new SolrFilterResult("item.hasbitstream:true"); + } + + @Override + public boolean isShown(DSpaceItem item) { + try { + String handle = DSpaceItem.parseHandle(item.getIdentifier()); + if (handle == null) { + return false; + } + Item dspaceItem = (Item) handleService.resolveToObject(context, handle); + for (Bundle b : dspaceItem.getBundles("ORIGINAL")) { + if (b.getBitstreams().size() > 0) { + return true; + } + } + } catch (SQLException e) { + log.error(e.getMessage(), e); + } + return false; + } +} 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 938cf0d64a..20dcabcb20 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.ByteArrayOutputStream; 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,7 @@ import org.apache.logging.log4j.Logger; 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; @@ -114,23 +117,21 @@ public class ItemUtils { log.error("Null bitstream found, check item uuid: " + item.getID()); break; } + boolean primary = false; + // Check if current bitstream is in original bundle + 1 of the 2 following + // Bitstream = primary bitstream in bundle -> true + // No primary bitstream found in bundle-> only the first one gets flagged as "primary" + if (b.getName() != null && b.getName().equals("ORIGINAL") && (b.getPrimaryBitstream() != null + && b.getPrimaryBitstream().getID() == bit.getID() + || b.getPrimaryBitstream() == null && bit.getID() == bits.get(0).getID())) { + primary = true; + } + Element bitstream = create("bitstream"); bitstreams.getElement().add(bitstream); - String url = ""; - String bsName = bit.getName(); - String sid = String.valueOf(bit.getSequenceID()); + String baseUrl = configurationService.getProperty("oai.bitstream.baseUrl"); - String handle = null; - // get handle of parent Item of this bitstream, if there - // is one: - List bn = bit.getBundles(); - if (!bn.isEmpty()) { - List bi = bn.get(0).getItems(); - if (!bi.isEmpty()) { - handle = bi.get(0).getHandle(); - } - } - url = baseUrl + "/bitstreams/" + bit.getID().toString() + "/download"; + String url = baseUrl + "/bitstreams/" + bit.getID().toString() + "/download"; String cks = bit.getChecksum(); String cka = bit.getChecksumAlgorithm(); @@ -147,18 +148,65 @@ public class ItemUtils { if (description != null) { bitstream.getField().add(createValue("description", description)); } + // Add bitstream embargo information (READ policy present, for Anonymous group with a start date) + addResourcePolicyInformation(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)); bitstream.getField().add(createValue("checksum", cks)); bitstream.getField().add(createValue("checksumAlgorithm", cka)); bitstream.getField().add(createValue("sid", bit.getSequenceID() + "")); + // Add primary bitstream field to allow locating easily the primary bitstream information + bitstream.getField().add(createValue("primary", primary + "")); } } return bundles; } + /** + * This method will add metadata information about associated resource policies for a give bitstream. + * It will parse of relevant policies and add metadata information + * @param context + * @param bitstream the bitstream object + * @param bitstreamEl the bitstream metadata object to add resource policy information to + * @throws SQLException + */ + private static void addResourcePolicyInformation(Context context, Bitstream bitstream, Element bitstreamEl) + throws SQLException { + // Pre-filter access policies by DSO (bitstream) and Action (READ) + List policies = authorizeService.getPoliciesActionFilter(context, bitstream, Constants.READ); + + // Create resourcePolicies container + Element resourcePolicies = create("resourcePolicies"); + + for (ResourcePolicy policy : policies) { + String groupName = policy.getGroup() != null ? policy.getGroup().getName() : null; + String user = policy.getEPerson() != null ? policy.getEPerson().getName() : null; + String action = Constants.actionText[policy.getAction()]; + Date startDate = policy.getStartDate(); + Date endDate = policy.getEndDate(); + + SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd"); + + Element resourcePolicyEl = create("resourcePolicy"); + resourcePolicyEl.getField().add(createValue("group", groupName)); + resourcePolicyEl.getField().add(createValue("user", user)); + resourcePolicyEl.getField().add(createValue("action", action)); + if (startDate != null) { + resourcePolicyEl.getField().add(createValue("start-date", formatter.format(startDate))); + } + if (endDate != null) { + resourcePolicyEl.getField().add(createValue("end-date", formatter.format(endDate))); + } + // Add resourcePolicy to list of resourcePolicies + resourcePolicies.getElement().add(resourcePolicyEl); + } + // Add list of resource policies to the corresponding Bitstream XML Element + bitstreamEl.getElement().add(resourcePolicies); + } + private static Element createLicenseElement(Context context, Item item) throws SQLException, AuthorizeException, IOException { Element license = create("license"); @@ -178,7 +226,7 @@ public class ItemUtils { 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() + "."); + + item.getID() + "."); } } } diff --git a/dspace-oai/src/test/java/org/dspace/xoai/tests/stylesheets/RioxxXslTest.java b/dspace-oai/src/test/java/org/dspace/xoai/tests/stylesheets/RioxxXslTest.java new file mode 100644 index 0000000000..74dfaf2902 --- /dev/null +++ b/dspace-oai/src/test/java/org/dspace/xoai/tests/stylesheets/RioxxXslTest.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.xoai.tests.stylesheets; + +import static org.dspace.xoai.tests.support.XmlMatcherBuilder.xml; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.Is.is; +import static org.hamcrest.core.IsEqual.equalTo; + +import org.dspace.xoai.tests.support.XmlMatcherBuilder; +import org.junit.Test; + +public class RioxxXslTest extends AbstractXSLTest { + @Test + public void rioxxCanTransformInput() throws Exception { + String result = apply("rioxx.xsl").to(resource("xoai-rioxx-test.xml")); + + assertThat(result, is(rioxx().withXPath("//dc:title", equalTo("The Intercorrelation Between " + + "Executive Function, Physics Problem Solving, Mathematical, and Matrix Reasoning Skills: " + + "Reflections from a Small-Scale Experiment")))); + } + + private XmlMatcherBuilder rioxx() { + return xml() + .withNamespace("rioxx", "http://www.rioxx.net/schema/v3.0/rioxx/") + .withNamespace("rioxxterms", "http://docs.rioxx.net/schema/v3.0/rioxxterms/") + .withNamespace("dcterms", "http://purl.org/dc/terms/") + .withNamespace("dc", "http://purl.org/dc/elements/1.1/"); + } +} diff --git a/dspace-oai/src/test/resources/rioxx-test-invalid.xml b/dspace-oai/src/test/resources/rioxx-test-invalid.xml new file mode 100644 index 0000000000..c8daf1a28d --- /dev/null +++ b/dspace-oai/src/test/resources/rioxx-test-invalid.xml @@ -0,0 +1,89 @@ + + + Data on Secchi disc depth (the depth at which a standard white disc lowered into the water just becomes invisible to a surface observer) show that water clarity in the North Sea declined during the 20th century, with likely consequences for marine primary production. However, the causes of this trend remain unknown. Here we analyse the hypothesis that changes in the North Sea's wave climate were largely responsible by causing an increase in the concentrations of suspended particulate matter (SPM) in the water column through the resuspension of seabed sediments. First, we analysed the broad-scale statistical relationships between SPM and bed shear stress due to waves and tides. We used hindcasts of wave and current data to construct a space–time dataset of bed shear stress between 1997 and 2017 across the northwest European Continental Shelf and compared the results with satellite-derived SPM concentrations. Bed shear stress was found to drive most of the inter-annual variation in SPM in the hydrographically mixed waters of the central and southern North Sea. We then used a long-term wave reanalysis to construct a time series of bed shear stress from 1900 to 2010. This shows that bed shear stress increased significantly across much of the shelf during this period, with increases of over 20 % in the southeastern North Sea. An increase in bed shear stress of this magnitude would have resulted in a large reduction in water clarity. Wave-driven processes are rarely included in projections of climate change impacts on marine ecosystems, but our analysis indicates that this should be reconsidered for shelf sea regions. + + en + + + European Geosciences Union + https://isni.org/isni/0000000110927289 + + + 1812-0792 + + Increasing turbidity in the North Sea during the 20th century due to changing wave climate + + 2019-10-02 + + + Wilson, Robert J. + https://orcid.org/0000-0002-0592-366X + + + + Heath, Michael R. + https://orcid.org/0000-0001-6602-3107 + https://viaf.org/viaf/15147423189944882613 + + + 2019-12-09 + + 2019-10-15 + + https://purl.org/coar/resource_type/c_2df8fbb1 + + + DP190101507 + + + + 61387 + + + + https://strathprints.strath.ac.uk/70117/7/Wilson_Heath_OS2019_Increasing_turbidity_in_the_North_Sea_during_the_20th_century.pdf + + + + + https://doi.org/10.1007/s11229-020-02724-x + + + + + https://doi.org/10.15129/5d28213e-8f9f-402a-b550-fc588518cb8b + + + + + https://doi.org/10.5281/zenodo.3478185 + + diff --git a/dspace-oai/src/test/resources/rioxx-test-valid.xml b/dspace-oai/src/test/resources/rioxx-test-valid.xml new file mode 100644 index 0000000000..74ffd43eb6 --- /dev/null +++ b/dspace-oai/src/test/resources/rioxx-test-valid.xml @@ -0,0 +1,92 @@ + + + + Data on Secchi disc depth (the depth at which a standard white disc lowered into the water just becomes invisible to a surface observer) show that water clarity in the North Sea declined during the 20th century, with likely consequences for marine primary production. However, the causes of this trend remain unknown. Here we analyse the hypothesis that changes in the North Sea's wave climate were largely responsible by causing an increase in the concentrations of suspended particulate matter (SPM) in the water column through the resuspension of seabed sediments. First, we analysed the broad-scale statistical relationships between SPM and bed shear stress due to waves and tides. We used hindcasts of wave and current data to construct a space–time dataset of bed shear stress between 1997 and 2017 across the northwest European Continental Shelf and compared the results with satellite-derived SPM concentrations. Bed shear stress was found to drive most of the inter-annual variation in SPM in the hydrographically mixed waters of the central and southern North Sea. We then used a long-term wave reanalysis to construct a time series of bed shear stress from 1900 to 2010. This shows that bed shear stress increased significantly across much of the shelf during this period, with increases of over 20 % in the southeastern North Sea. An increase in bed shear stress of this magnitude would have resulted in a large reduction in water clarity. Wave-driven processes are rarely included in projections of climate change impacts on marine ecosystems, but our analysis indicates that this should be reconsidered for shelf sea regions. + + en + + + European Geosciences Union + https://isni.org/isni/0000000110927289 + + + 1812-0792 + + Increasing turbidity in the North Sea during the 20th century due to changing wave climate + + 2019-10-02 + + + Wilson, Robert J. + https://orcid.org/0000-0002-0592-366X + + + + Heath, Michael R. + https://orcid.org/0000-0001-6602-3107 + https://viaf.org/viaf/15147423189944882613 + + + 2019-12-09 + + 2019-10-15 + + https://purl.org/coar/resource_type/c_2df8fbb1 + + + DP190101507 + + + + 61387 + + + https://strathprints.strath.ac.uk/70117/ + + + https://strathprints.strath.ac.uk/70117/7/Wilson_Heath_OS2019_Increasing_turbidity_in_the_North_Sea_during_the_20th_century.pdf + + + + + https://doi.org/10.1007/s11229-020-02724-x + + + + + https://doi.org/10.15129/5d28213e-8f9f-402a-b550-fc588518cb8b + + + + + https://doi.org/10.5281/zenodo.3478185 + + diff --git a/dspace-oai/src/test/resources/xoai-rioxx-test.xml b/dspace-oai/src/test/resources/xoai-rioxx-test.xml new file mode 100644 index 0000000000..33c2c3d101 --- /dev/null +++ b/dspace-oai/src/test/resources/xoai-rioxx-test.xml @@ -0,0 +1,217 @@ + + + + + + + + Publication + + + + + + + + 2023-11-07 + + + + + + + + Tsigaridis, Konstantinos G. + virtual::44 + -1 + Wang, Rui + virtual::46 + -1 + Ellefson, Michelle R. + virtual::47 + -1 + + + + + + + 2023-11-07T11:34:10Z + + + + + 2023-11-07T11:34:10Z + + + + + 2022-11-30 + + + + + + + https://example.org/handle/1811/160 + + + + + + + eng + + + + + + The Intercorrelation Between Executive Function, Physics Problem Solving, Mathematical, and Matrix Reasoning Skills: Reflections from a Small-Scale Experiment + + + + + Article + + + + + + + a57363fa-f82e-4684-bd76-f7bc1e893603 + virtual::44 + -1 + e00b3d0d-65e2-4c30-825d-1a4839845790 + virtual::46 + -1 + bdd38a03-206d-4f9b-bafb-70e060ad176f + virtual::47 + -1 + + + + a57363fa-f82e-4684-bd76-f7bc1e893603 + virtual::44 + -1 + e00b3d0d-65e2-4c30-825d-1a4839845790 + virtual::46 + -1 + bdd38a03-206d-4f9b-bafb-70e060ad176f + virtual::47 + -1 + + + + + + 05a400b1-ff0b-4e40-80cd-a7d1b712ace2 + virtual::71 + -1 + + + + + 7524a0cf-3ea2-40c7-a265-d583425ed4d7 + virtual::71 + -1 + + + + 7524a0cf-3ea2-40c7-a265-d583425ed4d7 + virtual::71 + -1 + + + + + + + + + 0000-0003-0407-9767 + virtual::47 + -1 + + + + + + + + 2634-9876 + virtual::71 + -1 + + + + + + ORIGINAL + + + Tsigaridis et al., 2022.pdf + application/pdf + 1554917 + https://example.org/bitstreams/9121e795-0af3-4ff3-be2a-4b28418fb269/download + 42d8cd076931e43e02d0af70a36d704e + MD5 + 1 + true + + + Anonymous + Anonymous + READ + + + + + + + THUMBNAIL + + + cerj_volume_9_thumbnail.jpg + image/jpeg + 14513 + https://example.org/bitstreams/16245937-10bb-46db-9817-683a5ebd8d63/download + 8c39d691daa8e5f9d668668db7910cd6 + MD5 + 2 + false + + + Anonymous + Anonymous + READ + + + + + + + + 1811/160 + oai:example.org:1811/160 + 2023-12-13 13:07:56.51 + + open.access + + + + https://example.org + Diamond DSpace (dev) + support@example.org + + + diff --git a/dspace/config/crosswalks/oai/metadataFormats/oai_openaire.xsl b/dspace/config/crosswalks/oai/metadataFormats/oai_openaire.xsl index 16c63c9c1a..c96305db16 100644 --- a/dspace/config/crosswalks/oai/metadataFormats/oai_openaire.xsl +++ b/dspace/config/crosswalks/oai/metadataFormats/oai_openaire.xsl @@ -883,7 +883,32 @@ - + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dspace/config/crosswalks/oai/metadataFormats/rioxx.xsl b/dspace/config/crosswalks/oai/metadataFormats/rioxx.xsl new file mode 100644 index 0000000000..db67ae91fe --- /dev/null +++ b/dspace/config/crosswalks/oai/metadataFormats/rioxx.xsl @@ -0,0 +1,1236 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + http://purl.org/coar/version/c_ab4af688f83e57aa + + + http://purl.org/coar/resource_type/c_1162 + + + http://purl.org/coar/version/c_b1a7d7d4d402bcce + + + http://purl.org/coar/version/c_e19f295774971610 + + + http://purl.org/coar/version/c_fa2ee174bc00049f + + + http://purl.org/coar/version/c_dc82b40f9837b551 + + + http://purl.org/coar/version/c_71e4c1898caa6e32 + + + http://purl.org/coar/version/c_be7fb7dd8ff6fe43 + + + + + + + + + + + + + + + http://purl.org/coar/resource_type/c_1162 + + + http://purl.org/coar/resource_type/c_0640 + + + http://purl.org/coar/resource_type/c_6501 + + + http://purl.org/coar/resource_type/c_6501 + + + http://purl.org/coar/resource_type/c_b239 + + + http://purl.org/coar/resource_type/c_7a1f + + + http://purl.org/coar/resource_type/c_86bc + + + http://purl.org/coar/resource_type/c_2f33 + + + http://purl.org/coar/resource_type/c_3248 + + + http://purl.org/coar/resource_type/c_ba08 + + + http://purl.org/coar/resource_type/c_7ad9 + + + http://purl.org/coar/resource_type/c_e9a0 + + + http://purl.org/coar/resource_type/c_f744 + + + http://purl.org/coar/resource_type/c_c94f + + + http://purl.org/coar/resource_type/c_5794 + + + http://purl.org/coar/resource_type/c_6670 + + + http://purl.org/coar/resource_type/c_3e5a + + + http://purl.org/coar/resource_type/c_beb9 + + + http://purl.org/coar/resource_type/c_ddb1 + + + http://purl.org/coar/resource_type/c_db06 + + + http://purl.org/coar/resource_type/c_c513 + + + http://purl.org/coar/resource_type/c_8544 + + + http://purl.org/coar/resource_type/c_0857 + + + http://purl.org/coar/resource_type/c_bdcc + + + http://purl.org/coar/resource_type/c_8a7e + + + http://purl.org/coar/resource_type/c_2659 + + + http://purl.org/coar/resource_type/c_545b + + + http://purl.org/coar/resource_type/c_15cd + + + http://purl.org/coar/resource_type/c_816b + + + http://purl.org/coar/resource_type/c_93fc + + + http://purl.org/coar/resource_type/c_ba1f + + + http://purl.org/coar/resource_type/c_baaf + + + http://purl.org/coar/resource_type/c_efa0 + + + http://purl.org/coar/resource_type/c_5ce6 + + + http://purl.org/coar/resource_type/c_ecc8 + + + http://purl.org/coar/resource_type/c_71bd + + + http://purl.org/coar/resource_type/c_393c + + + http://purl.org/coar/resource_type/c_8042 + + + http://purl.org/coar/resource_type/c_46ec + + + http://purl.org/coar/resource_type/c_12cc + + + http://purl.org/coar/resource_type/c_12cd + + + http://purl.org/coar/resource_type/c_12ce + + + http://purl.org/coar/resource_type/c_18cc + + + http://purl.org/coar/resource_type/c_18cd + + + http://purl.org/coar/resource_type/c_18cf + + + http://purl.org/coar/resource_type/c_18cp + + + http://purl.org/coar/resource_type/c_18co + + + http://purl.org/coar/resource_type/c_18cw + + + http://purl.org/coar/resource_type/c_18ww + + + http://purl.org/coar/resource_type/c_18wz + + + http://purl.org/coar/resource_type/c_18wq + + + http://purl.org/coar/resource_type/c_186u + + + http://purl.org/coar/resource_type/c_18op + + + http://purl.org/coar/resource_type/c_18hj + + + http://purl.org/coar/resource_type/c_18ws + + + http://purl.org/coar/resource_type/c_18gh + + + http://purl.org/coar/resource_type/c_dcae04bc + + + http://purl.org/coar/resource_type/c_2df8fbb1 + + + + http://purl.org/coar/resource_type/c_1843 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + http://purl.org/coar/access_right/c_abf2 + + + http://purl.org/coar/access_right/c_f1cf + + + http://purl.org/coar/access_right/c_16ec + + + http://purl.org/coar/access_right/c_14cb + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + . + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dspace/config/crosswalks/oai/transformers/rioxx.xsl b/dspace/config/crosswalks/oai/transformers/rioxx.xsl new file mode 100644 index 0000000000..7fc597b483 --- /dev/null +++ b/dspace/config/crosswalks/oai/transformers/rioxx.xsl @@ -0,0 +1,361 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + eng + + + eng + + + spa + + + deu + + + fra + + + ita + + + jpn + + + zho + + + por + + + tur + + + + + + + + + + + + + + + + + + annotation + + + journal + + + journal article + + + editorial + + + bachelor thesis + + + bibliography + + + book + + + book part + + + book review + + + website + + + interactive resource + + + conference proceedings + + + conference object + + + conference paper + + + conference poster + + + contribution to journal + + + data paper + + + dataset + + + doctoral thesis + + + image + + + lecture + + + letter + + + master thesis + + + moving image + + + periodical + + + letter to the editor + + + patent + + + preprint + + + report + + + report part + + + research proposal + + + review + + + software + + + still image + + + technical documentation + + + workflow + + + working paper + + + thesis + + + cartographic material + + + map + + + video + + + sound + + + musical composition + + + text + + + conference paper not in proceedings + + + conference poster not in proceedings + + + musical notation + + + internal report + + + memorandum + + + other type of report + + + policy report + + + project deliverable + + + report to funding agency + + + research report + + + technical report + + + review article + + + research article + + + other + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dspace/config/crosswalks/oai/xoai.xml b/dspace/config/crosswalks/oai/xoai.xml index 9bcabdf0e9..9a4985d667 100644 --- a/dspace/config/crosswalks/oai/xoai.xml +++ b/dspace/config/crosswalks/oai/xoai.xml @@ -19,6 +19,7 @@ + This is the default context of the DSpace OAI-PMH data provider. @@ -66,7 +67,7 @@ - This contexts complies with Openaire Guidelines for Literature Repositories v3.0. + This context complies with OpenAIRE Guidelines for Literature Repositories v3.0. @@ -90,6 +91,23 @@ This contexts complies with Openaire Guidelines for Literature Repositories v4.0. + + + + + + + + + + + This contexts complies with RIOXX rules. + + @@ -185,17 +203,35 @@ http://namespace.openaire.eu/schema/oaire/ https://www.openaire.eu/schema/repo-lit/4.0/openaire.xsd + + + rioxx + metadataFormats/rioxx.xsl + http://www.rioxx.net/schema/v3.0/rioxx/ + http://www.rioxx.net/schema/v3.0/rioxx/ http://www.rioxx.net/schema/v3.0/rioxx/rioxx.xsd + + transformers/driver.xsl + Driver context transformer transformers/openaire.xsl + OpenAire context transformer transformers/openaire4.xsl + OpenAire v4 context transformer + + + transformers/rioxx.xsl + RIOXX context transformer @@ -359,6 +395,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + org.dspace.xoai.filter.DSpaceMetadataExistsFilter + + + dc.type + + + + + + org.dspace.xoai.filter.DSpaceMetadataExistsFilter + + + dc.language.iso + + + + + + + org.dspace.xoai.filter.ItemsWithBitstreamFilter + + org.dspace.xoai.filter.DSpaceMetadataExistsFilter @@ -529,5 +650,10 @@ OpenAIRE + + rioxx + RIOXX set + + diff --git a/dspace/config/entities/rioxx3-relationships.xml b/dspace/config/entities/rioxx3-relationships.xml new file mode 100644 index 0000000000..6ed577f603 --- /dev/null +++ b/dspace/config/entities/rioxx3-relationships.xml @@ -0,0 +1,91 @@ + + + + + + + + Publication + Person + isAuthorOfPublication + isPublicationOfAuthor + + 0 + + + 0 + + + + + Publication + OrgUnit + isAuthorOfPublication + isPublicationOfAuthor + + 0 + + + 0 + + + + + Publication + Person + isContributorOfPublication + isPublicationOfContributor + + 0 + + + 0 + + + + + Publication + OrgUnit + isContributorOfPublication + isPublicationOfContributor + + 0 + + + 0 + + + + + Publication + Project + isProjectOfPublication + isPublicationOfProject + + 0 + + + 0 + + + + + Project + OrgUnit + isFundingAgencyOfProject + isProjectOfFundingAgency + + 0 + + + 0 + + + + diff --git a/dspace/config/registries/dublin-core-types.xml b/dspace/config/registries/dublin-core-types.xml index d0f340f89c..9a4aefb3ff 100644 --- a/dspace/config/registries/dublin-core-types.xml +++ b/dspace/config/registries/dublin-core-types.xml @@ -265,6 +265,7 @@ dc identifier doi + The doi identifier minted by this repository. diff --git a/dspace/config/spring/api/virtual-metadata.xml.rioxx3 b/dspace/config/spring/api/virtual-metadata.xml.rioxx3 new file mode 100644 index 0000000000..7f5c0e8cea --- /dev/null +++ b/dspace/config/spring/api/virtual-metadata.xml.rioxx3 @@ -0,0 +1,402 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + person.familyName + person.givenName + organization.legalName + + + + , + + + + + + + + person.givenName + + + + + + + person.familyName + + + + + + + person.affiliation.name + + + + + + + person.identifier + + + + + + + person.identifier.orcid + + + + + + + person.identifier.isni + + + + + + + organization.legalName + + + + + + + organization.identifier + + + + + + + + + + + + organization.legalName + + + + + + + + + + + + person.familyName + person.givenName + + + + , + + + + + + + + + + + organization.legalName + + + + + + + + + + + organization.legalName + + + + + + + + + + + + + + publicationvolume.volumeNumber + + + + + + + + + + + + + + + + + + + + + + creativeworkseries.issn + + + + + + + dc.title + + + + + + + + + + + + + + publicationissue.issueNumber + + + + + + + + + + + + + + dc.title + + + + , + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + dc.title + + + + + + + oaire.fundingStream + + + + + + + dc.identifier + + + + + + + dc.identifier.uri + + + + + + + + + + + + + + + + + + + + + + + + + organization.legalName + + + + + + + organization.identifier + + + + diff --git a/dspace/solr/oai/conf/schema.xml b/dspace/solr/oai/conf/schema.xml index b2f61c2de9..aefa798348 100644 --- a/dspace/solr/oai/conf/schema.xml +++ b/dspace/solr/oai/conf/schema.xml @@ -126,7 +126,9 @@ - + + + From d645939baf07d10d0f9f0b5e7b2722556400ff97 Mon Sep 17 00:00:00 2001 From: mohamed eskander Date: Wed, 17 Jan 2024 10:39:18 +0200 Subject: [PATCH 333/412] [DURACOM-143] Fix indexing errors & further improvements --- .../indexobject/IndexFactoryImpl.java | 50 ++++++++++++------- 1 file changed, 31 insertions(+), 19 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/discovery/indexobject/IndexFactoryImpl.java b/dspace-api/src/main/java/org/dspace/discovery/indexobject/IndexFactoryImpl.java index 55c99b168e..4b012a072f 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/indexobject/IndexFactoryImpl.java +++ b/dspace-api/src/main/java/org/dspace/discovery/indexobject/IndexFactoryImpl.java @@ -2,7 +2,7 @@ * The contents of this file are subject to the license and copyright * detailed in the LICENSE and NOTICE files at the root of the source * tree and available online at - * + *

    * http://www.dspace.org/license/ */ package org.dspace.discovery.indexobject; @@ -64,7 +64,14 @@ public abstract class IndexFactoryImpl implements //Do any additional indexing, depends on the plugins for (SolrServiceIndexPlugin solrServiceIndexPlugin : ListUtils.emptyIfNull(solrServiceIndexPlugins)) { - solrServiceIndexPlugin.additionalIndex(context, indexableObject, doc); + try { + solrServiceIndexPlugin.additionalIndex(context, indexableObject, doc); + } catch (Exception e) { + log.error("An error occurred while indexing additional fields. " + + "Could not fully index item with UUID: {}. Plugin: {}", + indexableObject.getUniqueIndexID(), solrServiceIndexPlugin.getClass().getSimpleName()); + + } } return doc; @@ -82,7 +89,7 @@ public abstract class IndexFactoryImpl implements writeDocument(solrInputDocument, null); } catch (Exception e) { log.error("Error occurred while writing SOLR document for {} object {}", - indexableObject.getType(), indexableObject.getID(), e); + indexableObject.getType(), indexableObject.getID(), e); } } @@ -101,8 +108,8 @@ public abstract class IndexFactoryImpl implements if (streams != null && !streams.isEmpty()) { // limit full text indexing to first 100,000 characters unless configured otherwise final int charLimit = DSpaceServicesFactory.getInstance().getConfigurationService() - .getIntProperty("discovery.solr.fulltext.charLimit", - 100000); + .getIntProperty("discovery.solr.fulltext.charLimit", + 100000); // Use Tika's Text parser as the streams are always from the TEXT bundle (i.e. already extracted text) TextAndCSVParser tikaParser = new TextAndCSVParser(); @@ -113,6 +120,18 @@ public abstract class IndexFactoryImpl implements // Use Apache Tika to parse the full text stream(s) try (InputStream fullTextStreams = streams.getStream()) { tikaParser.parse(fullTextStreams, tikaHandler, tikaMetadata, tikaContext); + + // Write Tika metadata to "tika_meta_*" fields. + // This metadata is not very useful right now, + // but we'll keep it just in case it becomes more useful. + for (String name : tikaMetadata.names()) { + for (String value : tikaMetadata.getValues(name)) { + doc.addField("tika_meta_" + name, value); + } + } + + // Save (parsed) full text to "fulltext" field + doc.addField("fulltext", tikaHandler.toString()); } catch (SAXException saxe) { // Check if this SAXException is just a notice that this file was longer than the character limit. // Unfortunately there is not a unique, public exception type to catch here. This error is thrown @@ -121,30 +140,23 @@ public abstract class IndexFactoryImpl implements if (saxe.getMessage().contains("limit has been reached")) { // log that we only indexed up to that configured limit log.info("Full text is larger than the configured limit (discovery.solr.fulltext.charLimit)." - + " Only the first {} characters were indexed.", charLimit); + + " Only the first {} characters were indexed.", charLimit); } else { log.error("Tika parsing error. Could not index full text.", saxe); throw new IOException("Tika parsing error. Could not index full text.", saxe); } - } catch (TikaException ex) { + } catch (TikaException | IOException ex) { log.error("Tika parsing error. Could not index full text.", ex); throw new IOException("Tika parsing error. Could not index full text.", ex); + } finally { + // Add document to index + solr.add(doc); } - - // Write Tika metadata to "tika_meta_*" fields. - // This metadata is not very useful right now, but we'll keep it just in case it becomes more useful. - for (String name : tikaMetadata.names()) { - for (String value : tikaMetadata.getValues(name)) { - doc.addField("tika_meta_" + name, value); - } - } - - // Save (parsed) full text to "fulltext" field - doc.addField("fulltext", tikaHandler.toString()); + return; } - // Add document to index solr.add(doc); + } } From 324d2e3184dd35d2980e599f173adf18a7e31d64 Mon Sep 17 00:00:00 2001 From: mohamed eskander Date: Wed, 17 Jan 2024 10:56:08 +0200 Subject: [PATCH 334/412] [DURACOM-143] Fix license --- .../java/org/dspace/discovery/indexobject/IndexFactoryImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/discovery/indexobject/IndexFactoryImpl.java b/dspace-api/src/main/java/org/dspace/discovery/indexobject/IndexFactoryImpl.java index 4b012a072f..f1ae137b91 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/indexobject/IndexFactoryImpl.java +++ b/dspace-api/src/main/java/org/dspace/discovery/indexobject/IndexFactoryImpl.java @@ -2,7 +2,7 @@ * The contents of this file are subject to the license and copyright * detailed in the LICENSE and NOTICE files at the root of the source * tree and available online at - *

    + * * http://www.dspace.org/license/ */ package org.dspace.discovery.indexobject; From 848df25984b8d6cabb8b6b0c831070ffed66919a Mon Sep 17 00:00:00 2001 From: nwoodward Date: Tue, 16 Jan 2024 13:28:40 -0600 Subject: [PATCH 335/412] catch exceptions stemming from invalid id's --- .../org/dspace/content/BitstreamServiceImpl.java | 13 +++++++++---- .../java/org/dspace/content/BundleServiceImpl.java | 13 +++++++++---- .../org/dspace/content/CollectionServiceImpl.java | 13 +++++++++---- .../org/dspace/content/CommunityServiceImpl.java | 13 +++++++++---- .../java/org/dspace/content/ItemServiceImpl.java | 13 +++++++++---- .../java/org/dspace/eperson/EPersonServiceImpl.java | 13 +++++++++---- .../java/org/dspace/eperson/GroupServiceImpl.java | 13 +++++++++---- 7 files changed, 63 insertions(+), 28 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/BitstreamServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/BitstreamServiceImpl.java index 691d38f030..e23e5ce2c8 100644 --- a/dspace-api/src/main/java/org/dspace/content/BitstreamServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/BitstreamServiceImpl.java @@ -458,10 +458,15 @@ public class BitstreamServiceImpl extends DSpaceObjectServiceImpl imp @Override public Bitstream findByIdOrLegacyId(Context context, String id) throws SQLException { - if (StringUtils.isNumeric(id)) { - return findByLegacyId(context, Integer.parseInt(id)); - } else { - return find(context, UUID.fromString(id)); + try { + if (StringUtils.isNumeric(id)) { + return findByLegacyId(context, Integer.parseInt(id)); + } else { + return find(context, UUID.fromString(id)); + } + } catch (IllegalArgumentException e) { + // Not a valid legacy ID or valid UUID + return null; } } diff --git a/dspace-api/src/main/java/org/dspace/content/BundleServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/BundleServiceImpl.java index 546d48d430..3ba90c8cc2 100644 --- a/dspace-api/src/main/java/org/dspace/content/BundleServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/BundleServiceImpl.java @@ -562,10 +562,15 @@ public class BundleServiceImpl extends DSpaceObjectServiceImpl implement @Override public Bundle findByIdOrLegacyId(Context context, String id) throws SQLException { - if (StringUtils.isNumeric(id)) { - return findByLegacyId(context, Integer.parseInt(id)); - } else { - return find(context, UUID.fromString(id)); + try { + if (StringUtils.isNumeric(id)) { + return findByLegacyId(context, Integer.parseInt(id)); + } else { + return find(context, UUID.fromString(id)); + } + } catch (IllegalArgumentException e) { + // Not a valid legacy ID or valid UUID + return null; } } diff --git a/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java index be763713ec..652d2a5f38 100644 --- a/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java @@ -895,10 +895,15 @@ public class CollectionServiceImpl extends DSpaceObjectServiceImpl i @Override public Collection findByIdOrLegacyId(Context context, String id) throws SQLException { - if (StringUtils.isNumeric(id)) { - return findByLegacyId(context, Integer.parseInt(id)); - } else { - return find(context, UUID.fromString(id)); + try { + if (StringUtils.isNumeric(id)) { + return findByLegacyId(context, Integer.parseInt(id)); + } else { + return find(context, UUID.fromString(id)); + } + } catch (IllegalArgumentException e) { + // Not a valid legacy ID or valid UUID + return null; } } diff --git a/dspace-api/src/main/java/org/dspace/content/CommunityServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/CommunityServiceImpl.java index 15ac1c58a6..045adc229e 100644 --- a/dspace-api/src/main/java/org/dspace/content/CommunityServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/CommunityServiceImpl.java @@ -694,10 +694,15 @@ public class CommunityServiceImpl extends DSpaceObjectServiceImpl imp @Override public Community findByIdOrLegacyId(Context context, String id) throws SQLException { - if (StringUtils.isNumeric(id)) { - return findByLegacyId(context, Integer.parseInt(id)); - } else { - return find(context, UUID.fromString(id)); + try { + if (StringUtils.isNumeric(id)) { + return findByLegacyId(context, Integer.parseInt(id)); + } else { + return find(context, UUID.fromString(id)); + } + } catch (IllegalArgumentException e) { + // Not a valid legacy ID or valid UUID + return null; } } diff --git a/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java index f6144961c6..a0847e4be2 100644 --- a/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java @@ -1618,10 +1618,15 @@ prevent the generation of resource policy entry values with null dspace_object a @Override public Item findByIdOrLegacyId(Context context, String id) throws SQLException { - if (StringUtils.isNumeric(id)) { - return findByLegacyId(context, Integer.parseInt(id)); - } else { - return find(context, UUID.fromString(id)); + try { + if (StringUtils.isNumeric(id)) { + return findByLegacyId(context, Integer.parseInt(id)); + } else { + return find(context, UUID.fromString(id)); + } + } catch (IllegalArgumentException e) { + // Not a valid legacy ID or valid UUID + return null; } } diff --git a/dspace-api/src/main/java/org/dspace/eperson/EPersonServiceImpl.java b/dspace-api/src/main/java/org/dspace/eperson/EPersonServiceImpl.java index b9fde450c2..b9ac740685 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/EPersonServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/EPersonServiceImpl.java @@ -146,10 +146,15 @@ public class EPersonServiceImpl extends DSpaceObjectServiceImpl impleme @Override public EPerson findByIdOrLegacyId(Context context, String id) throws SQLException { - if (StringUtils.isNumeric(id)) { - return findByLegacyId(context, Integer.parseInt(id)); - } else { - return find(context, UUID.fromString(id)); + try { + if (StringUtils.isNumeric(id)) { + return findByLegacyId(context, Integer.parseInt(id)); + } else { + return find(context, UUID.fromString(id)); + } + } catch (IllegalArgumentException e) { + // Not a valid legacy ID or valid UUID + return null; } } diff --git a/dspace-api/src/main/java/org/dspace/eperson/GroupServiceImpl.java b/dspace-api/src/main/java/org/dspace/eperson/GroupServiceImpl.java index b8d8c75d0f..730053e42c 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/GroupServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/GroupServiceImpl.java @@ -872,10 +872,15 @@ public class GroupServiceImpl extends DSpaceObjectServiceImpl implements @Override public Group findByIdOrLegacyId(Context context, String id) throws SQLException { - if (org.apache.commons.lang3.StringUtils.isNumeric(id)) { - return findByLegacyId(context, Integer.parseInt(id)); - } else { - return find(context, UUIDUtils.fromString(id)); + try { + if (StringUtils.isNumeric(id)) { + return findByLegacyId(context, Integer.parseInt(id)); + } else { + return find(context, UUID.fromString(id)); + } + } catch (IllegalArgumentException e) { + // Not a valid legacy ID or valid UUID + return null; } } From ec0ab92794cdd7d4af0185cad299f09bf5b5aca8 Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Fri, 29 Dec 2023 14:57:09 +0100 Subject: [PATCH 336/412] Alteration to index-discovery script to only (re-)index specific type of IndexableObject Not compatible with `-b` option since this clears entire index first (& expect to rebuild it in its entirety) Compatible with `-f` to force reindex specific type of IndexableObject --- .../java/org/dspace/discovery/IndexClient.java | 16 ++++++++++++++-- .../org/dspace/discovery/IndexClientOptions.java | 11 +++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/discovery/IndexClient.java b/dspace-api/src/main/java/org/dspace/discovery/IndexClient.java index 661c48d91c..a01d43ce8e 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/IndexClient.java +++ b/dspace-api/src/main/java/org/dspace/discovery/IndexClient.java @@ -7,6 +7,8 @@ */ package org.dspace.discovery; +import static org.dspace.discovery.IndexClientOptions.TYPE_OPTION; + import java.io.IOException; import java.sql.SQLException; import java.util.Iterator; @@ -15,6 +17,7 @@ import java.util.UUID; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.ParseException; +import org.apache.commons.lang3.StringUtils; import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.DSpaceObject; @@ -51,6 +54,11 @@ public class IndexClient extends DSpaceRunnable indexableObjectTypes = IndexObjectFactoryFactory.getInstance().getIndexFactories().stream() + .map((indexFactory -> indexFactory.getType())).collect(Collectors.toList()); options .addOption("r", "remove", true, "remove an Item, Collection or Community from index based on its handle"); options.addOption("i", "index", true, "add or update an Item, Collection or Community based on its handle or uuid"); + options.addOption(TYPE_OPTION, "type", true, "reindex only specific type of " + + "(re)indexable objects; options: " + Arrays.toString(indexableObjectTypes.toArray())); options.addOption("c", "clean", false, "clean existing index removing any documents that no longer exist in the db"); options.addOption("d", "delete", false, From 944305a8ca9045eca5e2c970b97b6f8eaf3ea44c Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 12 Jan 2024 16:08:43 -0600 Subject: [PATCH 337/412] Remove dspace-rest, all configs and a few deprecated methods only used by this module. --- .github/workflows/build.yml | 4 +- Dockerfile | 2 +- Dockerfile.test | 15 +- .../org/dspace/content/ItemServiceImpl.java | 10 - .../java/org/dspace/content/dao/ItemDAO.java | 5 - .../dspace/content/dao/impl/ItemDAOImpl.java | 125 -- .../dspace/content/service/ItemService.java | 6 - dspace-rest/README.md | 194 --- dspace-rest/pom.xml | 202 ---- .../org/dspace/rest/BitstreamResource.java | 783 ------------ .../org/dspace/rest/CollectionsResource.java | 755 ------------ .../org/dspace/rest/CommunitiesResource.java | 1052 ----------------- .../dspace/rest/DSpaceRestApplication.java | 19 - .../rest/FilteredCollectionsResource.java | 215 ---- .../dspace/rest/FilteredItemsResource.java | 217 ---- .../java/org/dspace/rest/FiltersResource.java | 60 - .../java/org/dspace/rest/HandleResource.java | 109 -- .../org/dspace/rest/HierarchyResource.java | 140 --- .../java/org/dspace/rest/ItemsResource.java | 1007 ---------------- .../dspace/rest/MetadataRegistryResource.java | 738 ------------ .../main/java/org/dspace/rest/Resource.java | 212 ---- .../main/java/org/dspace/rest/RestIndex.java | 301 ----- .../java/org/dspace/rest/RestReports.java | 86 -- .../DSpaceAuthenticationProvider.java | 129 -- ...rectAuthenticationLoginSuccessHandler.java | 41 - ...ectAuthenticationLogoutSuccessHandler.java | 39 - .../org/dspace/rest/common/Bitstream.java | 199 ---- .../java/org/dspace/rest/common/CheckSum.java | 40 - .../org/dspace/rest/common/Collection.java | 225 ---- .../org/dspace/rest/common/Community.java | 217 ---- .../org/dspace/rest/common/DSpaceObject.java | 107 -- .../rest/common/FilteredCollection.java | 191 --- .../rest/common/HierarchyCollection.java | 24 - .../rest/common/HierarchyCommunity.java | 44 - .../dspace/rest/common/HierarchyObject.java | 51 - .../org/dspace/rest/common/HierarchySite.java | 24 - .../java/org/dspace/rest/common/Item.java | 219 ---- .../org/dspace/rest/common/ItemFilter.java | 274 ----- .../dspace/rest/common/ItemFilterQuery.java | 77 -- .../org/dspace/rest/common/MetadataEntry.java | 77 -- .../org/dspace/rest/common/MetadataField.java | 132 --- .../dspace/rest/common/MetadataSchema.java | 105 -- .../java/org/dspace/rest/common/Report.java | 47 - .../dspace/rest/common/ResourcePolicy.java | 195 --- .../java/org/dspace/rest/common/Status.java | 111 -- .../rest/exceptions/ContextException.java | 31 - .../dspace/rest/filter/ItemFilterDefs.java | 159 --- .../rest/filter/ItemFilterDefsMeta.java | 177 --- .../rest/filter/ItemFilterDefsMisc.java | 206 ---- .../rest/filter/ItemFilterDefsPerm.java | 138 --- .../dspace/rest/filter/ItemFilterList.java | 12 - .../org/dspace/rest/filter/ItemFilterSet.java | 143 --- .../dspace/rest/filter/ItemFilterTest.java | 29 - .../dspace/rest/filter/ItemFilterUtil.java | 278 ----- .../java/org/dspace/utils/DSpaceWebapp.java | 28 - .../webapp/WEB-INF/applicationContext.xml | 59 - .../WEB-INF/security-applicationContext.xml | 80 -- dspace-rest/src/main/webapp/WEB-INF/web.xml | 119 -- .../webapp/static/reports/authenticate.html | 58 - .../src/main/webapp/static/reports/index.html | 77 -- .../src/main/webapp/static/reports/query.html | 105 -- .../main/webapp/static/reports/restClient.css | 98 -- .../webapp/static/reports/restCollReport.js | 510 -------- .../webapp/static/reports/restQueryReport.js | 350 ------ .../main/webapp/static/reports/restReport.js | 745 ------------ .../src/main/webapp/static/reports/spin.js | 369 ------ .../dspace/rest/common/TestJAXBSchema.java | 68 -- .../org/dspace/rest/common/expected_xsd0.xsd | 154 --- .../DSpaceKernelServletContextListener.java | 121 -- .../servicemanager/servlet/package-info.java | 14 - ...SpaceKernelServletContextListenerTest.java | 132 --- .../servicemanager/servlet/SampleServlet.java | 92 -- dspace/config/modules/rest.cfg | 139 --- dspace/modules/pom.xml | 14 - dspace/modules/rest/pom.xml | 123 -- .../modules/rest/src/main/webapp/.gitignore | 0 dspace/pom.xml | 1 - dspace/src/main/config/build.xml | 4 - dspace/src/main/docker/README.md | 3 +- pom.xml | 36 +- 80 files changed, 9 insertions(+), 13488 deletions(-) delete mode 100644 dspace-rest/README.md delete mode 100644 dspace-rest/pom.xml delete mode 100644 dspace-rest/src/main/java/org/dspace/rest/BitstreamResource.java delete mode 100644 dspace-rest/src/main/java/org/dspace/rest/CollectionsResource.java delete mode 100644 dspace-rest/src/main/java/org/dspace/rest/CommunitiesResource.java delete mode 100644 dspace-rest/src/main/java/org/dspace/rest/DSpaceRestApplication.java delete mode 100644 dspace-rest/src/main/java/org/dspace/rest/FilteredCollectionsResource.java delete mode 100644 dspace-rest/src/main/java/org/dspace/rest/FilteredItemsResource.java delete mode 100644 dspace-rest/src/main/java/org/dspace/rest/FiltersResource.java delete mode 100644 dspace-rest/src/main/java/org/dspace/rest/HandleResource.java delete mode 100644 dspace-rest/src/main/java/org/dspace/rest/HierarchyResource.java delete mode 100644 dspace-rest/src/main/java/org/dspace/rest/ItemsResource.java delete mode 100644 dspace-rest/src/main/java/org/dspace/rest/MetadataRegistryResource.java delete mode 100644 dspace-rest/src/main/java/org/dspace/rest/Resource.java delete mode 100644 dspace-rest/src/main/java/org/dspace/rest/RestIndex.java delete mode 100644 dspace-rest/src/main/java/org/dspace/rest/RestReports.java delete mode 100644 dspace-rest/src/main/java/org/dspace/rest/authentication/DSpaceAuthenticationProvider.java delete mode 100644 dspace-rest/src/main/java/org/dspace/rest/authentication/NoRedirectAuthenticationLoginSuccessHandler.java delete mode 100644 dspace-rest/src/main/java/org/dspace/rest/authentication/NoRedirectAuthenticationLogoutSuccessHandler.java delete mode 100644 dspace-rest/src/main/java/org/dspace/rest/common/Bitstream.java delete mode 100644 dspace-rest/src/main/java/org/dspace/rest/common/CheckSum.java delete mode 100644 dspace-rest/src/main/java/org/dspace/rest/common/Collection.java delete mode 100644 dspace-rest/src/main/java/org/dspace/rest/common/Community.java delete mode 100644 dspace-rest/src/main/java/org/dspace/rest/common/DSpaceObject.java delete mode 100644 dspace-rest/src/main/java/org/dspace/rest/common/FilteredCollection.java delete mode 100644 dspace-rest/src/main/java/org/dspace/rest/common/HierarchyCollection.java delete mode 100644 dspace-rest/src/main/java/org/dspace/rest/common/HierarchyCommunity.java delete mode 100644 dspace-rest/src/main/java/org/dspace/rest/common/HierarchyObject.java delete mode 100644 dspace-rest/src/main/java/org/dspace/rest/common/HierarchySite.java delete mode 100644 dspace-rest/src/main/java/org/dspace/rest/common/Item.java delete mode 100644 dspace-rest/src/main/java/org/dspace/rest/common/ItemFilter.java delete mode 100644 dspace-rest/src/main/java/org/dspace/rest/common/ItemFilterQuery.java delete mode 100644 dspace-rest/src/main/java/org/dspace/rest/common/MetadataEntry.java delete mode 100644 dspace-rest/src/main/java/org/dspace/rest/common/MetadataField.java delete mode 100644 dspace-rest/src/main/java/org/dspace/rest/common/MetadataSchema.java delete mode 100644 dspace-rest/src/main/java/org/dspace/rest/common/Report.java delete mode 100644 dspace-rest/src/main/java/org/dspace/rest/common/ResourcePolicy.java delete mode 100644 dspace-rest/src/main/java/org/dspace/rest/common/Status.java delete mode 100644 dspace-rest/src/main/java/org/dspace/rest/exceptions/ContextException.java delete mode 100644 dspace-rest/src/main/java/org/dspace/rest/filter/ItemFilterDefs.java delete mode 100644 dspace-rest/src/main/java/org/dspace/rest/filter/ItemFilterDefsMeta.java delete mode 100644 dspace-rest/src/main/java/org/dspace/rest/filter/ItemFilterDefsMisc.java delete mode 100644 dspace-rest/src/main/java/org/dspace/rest/filter/ItemFilterDefsPerm.java delete mode 100644 dspace-rest/src/main/java/org/dspace/rest/filter/ItemFilterList.java delete mode 100644 dspace-rest/src/main/java/org/dspace/rest/filter/ItemFilterSet.java delete mode 100644 dspace-rest/src/main/java/org/dspace/rest/filter/ItemFilterTest.java delete mode 100644 dspace-rest/src/main/java/org/dspace/rest/filter/ItemFilterUtil.java delete mode 100644 dspace-rest/src/main/java/org/dspace/utils/DSpaceWebapp.java delete mode 100644 dspace-rest/src/main/webapp/WEB-INF/applicationContext.xml delete mode 100644 dspace-rest/src/main/webapp/WEB-INF/security-applicationContext.xml delete mode 100644 dspace-rest/src/main/webapp/WEB-INF/web.xml delete mode 100644 dspace-rest/src/main/webapp/static/reports/authenticate.html delete mode 100644 dspace-rest/src/main/webapp/static/reports/index.html delete mode 100644 dspace-rest/src/main/webapp/static/reports/query.html delete mode 100644 dspace-rest/src/main/webapp/static/reports/restClient.css delete mode 100644 dspace-rest/src/main/webapp/static/reports/restCollReport.js delete mode 100644 dspace-rest/src/main/webapp/static/reports/restQueryReport.js delete mode 100644 dspace-rest/src/main/webapp/static/reports/restReport.js delete mode 100644 dspace-rest/src/main/webapp/static/reports/spin.js delete mode 100644 dspace-rest/src/test/java/org/dspace/rest/common/TestJAXBSchema.java delete mode 100644 dspace-rest/src/test/resources/org/dspace/rest/common/expected_xsd0.xsd delete mode 100644 dspace-services/src/main/java/org/dspace/servicemanager/servlet/DSpaceKernelServletContextListener.java delete mode 100644 dspace-services/src/main/java/org/dspace/servicemanager/servlet/package-info.java delete mode 100644 dspace-services/src/test/java/org/dspace/servicemanager/servlet/DSpaceKernelServletContextListenerTest.java delete mode 100644 dspace-services/src/test/java/org/dspace/servicemanager/servlet/SampleServlet.java delete mode 100644 dspace/modules/rest/pom.xml delete mode 100644 dspace/modules/rest/src/main/webapp/.gitignore diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d6913078e4..a2a0d6294f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -21,11 +21,11 @@ jobs: # Also specify version of Java to use (this can allow us to optionally run tests on multiple JDKs in future) matrix: include: - # NOTE: Unit Tests include deprecated REST API v6 (as it has unit tests) + # NOTE: Unit Tests include a retry for occasionally failing tests # - surefire.rerunFailingTestsCount => try again for flakey tests, and keep track of/report on number of retries - type: "Unit Tests" java: 11 - mvnflags: "-DskipUnitTests=false -Pdspace-rest -Dsurefire.rerunFailingTestsCount=2" + mvnflags: "-DskipUnitTests=false -Dsurefire.rerunFailingTestsCount=2" resultsdir: "**/target/surefire-reports/**" # NOTE: ITs skip all code validation checks, as they are already done by Unit Test job. # - enforcer.skip => Skip maven-enforcer-plugin rules diff --git a/Dockerfile b/Dockerfile index bef894d79b..5bcd683768 100644 --- a/Dockerfile +++ b/Dockerfile @@ -19,7 +19,7 @@ RUN mkdir /install \ USER dspace # Copy the DSpace source code (from local machine) into the workdir (excluding .dockerignore contents) ADD --chown=dspace . /app/ -# Build DSpace (note: this build doesn't include the optional, deprecated "dspace-rest" webapp) +# Build DSpace # Copy the dspace-installer directory to /install. Clean up the build to keep the docker image small # 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. diff --git a/Dockerfile.test b/Dockerfile.test index 16a04d0002..6fcc4eda6b 100644 --- a/Dockerfile.test +++ b/Dockerfile.test @@ -21,9 +21,9 @@ RUN mkdir /install \ USER dspace # Copy the DSpace source code (from local machine) into the workdir (excluding .dockerignore contents) ADD --chown=dspace . /app/ -# Build DSpace (INCLUDING the optional, deprecated "dspace-rest" webapp) +# Build DSpace # Copy the dspace-installer directory to /install. Clean up the build to keep the docker image small -RUN mvn --no-transfer-progress package -Pdspace-rest && \ +RUN mvn --no-transfer-progress package && \ mv /app/dspace/target/${TARGET_DIR}/* /install && \ mvn clean @@ -67,17 +67,10 @@ ENV CATALINA_OPTS=-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=*:800 # Link the DSpace 'server' webapp into Tomcat's webapps directory. # This ensures that when we start Tomcat, it runs from /server path (e.g. http://localhost:8080/server/) -# Also link the v6.x (deprecated) REST API off the "/rest" path -RUN ln -s $DSPACE_INSTALL/webapps/server /usr/local/tomcat/webapps/server && \ - ln -s $DSPACE_INSTALL/webapps/rest /usr/local/tomcat/webapps/rest +RUN ln -s $DSPACE_INSTALL/webapps/server /usr/local/tomcat/webapps/server # If you wish to run "server" webapp off the ROOT path, then comment out the above RUN, and uncomment the below RUN. # You also MUST update the 'dspace.server.url' configuration to match. # Please note that server webapp should only run on one path at a time. #RUN mv /usr/local/tomcat/webapps/ROOT /usr/local/tomcat/webapps/ROOT.bk && \ -# ln -s $DSPACE_INSTALL/webapps/server /usr/local/tomcat/webapps/ROOT && \ -# ln -s $DSPACE_INSTALL/webapps/rest /usr/local/tomcat/webapps/rest +# ln -s $DSPACE_INSTALL/webapps/server /usr/local/tomcat/webapps/ROOT -# Overwrite the v6.x (deprecated) REST API's web.xml, so that we can run it on HTTP (defaults to requiring HTTPS) -# WARNING: THIS IS OBVIOUSLY INSECURE. NEVER DO THIS IN PRODUCTION. -COPY dspace/src/main/docker/test/rest_web.xml $DSPACE_INSTALL/webapps/rest/WEB-INF/web.xml -RUN sed -i -e "s|\${dspace.dir}|$DSPACE_INSTALL|" $DSPACE_INSTALL/webapps/rest/WEB-INF/web.xml 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 a0847e4be2..9791f69abb 100644 --- a/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java @@ -1435,16 +1435,6 @@ prevent the generation of resource policy entry values with null dspace_object a } } - @Override - public Iterator findByMetadataQuery(Context context, List> listFieldList, - List query_op, List query_val, List collectionUuids, - String regexClause, int offset, int limit) - throws SQLException, AuthorizeException, IOException { - return itemDAO - .findByMetadataQuery(context, listFieldList, query_op, query_val, collectionUuids, regexClause, offset, - limit); - } - @Override public DSpaceObject getAdminObject(Context context, Item item, int action) throws SQLException { DSpaceObject adminObject = null; diff --git a/dspace-api/src/main/java/org/dspace/content/dao/ItemDAO.java b/dspace-api/src/main/java/org/dspace/content/dao/ItemDAO.java index 86da51e6cc..49d3527a35 100644 --- a/dspace-api/src/main/java/org/dspace/content/dao/ItemDAO.java +++ b/dspace-api/src/main/java/org/dspace/content/dao/ItemDAO.java @@ -11,7 +11,6 @@ import java.sql.SQLException; import java.util.Date; import java.util.Iterator; import java.util.List; -import java.util.UUID; import org.dspace.content.Collection; import org.dspace.content.Community; @@ -80,10 +79,6 @@ public interface ItemDAO extends DSpaceObjectLegacySupportDAO { public Iterator findByMetadataField(Context context, MetadataField metadataField, String value, boolean inArchive) throws SQLException; - public Iterator findByMetadataQuery(Context context, List> listFieldList, - List query_op, List query_val, List collectionUuids, - String regexClause, int offset, int limit) throws SQLException; - public Iterator findByAuthorityValue(Context context, MetadataField metadataField, String authority, boolean inArchive) throws SQLException; diff --git a/dspace-api/src/main/java/org/dspace/content/dao/impl/ItemDAOImpl.java b/dspace-api/src/main/java/org/dspace/content/dao/impl/ItemDAOImpl.java index aad8cf3c50..5c840f68e9 100644 --- a/dspace-api/src/main/java/org/dspace/content/dao/impl/ItemDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/dao/impl/ItemDAOImpl.java @@ -12,7 +12,6 @@ import java.util.Collections; import java.util.Date; import java.util.Iterator; import java.util.List; -import java.util.UUID; import javax.persistence.Query; import javax.persistence.TemporalType; import javax.persistence.criteria.CriteriaBuilder; @@ -24,20 +23,10 @@ import org.dspace.content.Collection; import org.dspace.content.Item; import org.dspace.content.Item_; import org.dspace.content.MetadataField; -import org.dspace.content.MetadataValue; import org.dspace.content.dao.ItemDAO; import org.dspace.core.AbstractHibernateDSODAO; import org.dspace.core.Context; import org.dspace.eperson.EPerson; -import org.hibernate.Criteria; -import org.hibernate.criterion.Criterion; -import org.hibernate.criterion.DetachedCriteria; -import org.hibernate.criterion.Order; -import org.hibernate.criterion.Projections; -import org.hibernate.criterion.Property; -import org.hibernate.criterion.Restrictions; -import org.hibernate.criterion.Subqueries; -import org.hibernate.type.StandardBasicTypes; /** * Hibernate implementation of the Database Access Object interface class for the Item object. @@ -174,120 +163,6 @@ public class ItemDAOImpl extends AbstractHibernateDSODAO implements ItemDA return iterate(query); } - enum OP { - equals { - public Criterion buildPredicate(String val, String regexClause) { - return Property.forName("mv.value").eq(val); - } - }, - not_equals { - public Criterion buildPredicate(String val, String regexClause) { - return OP.equals.buildPredicate(val, regexClause); - } - }, - like { - public Criterion buildPredicate(String val, String regexClause) { - return Property.forName("mv.value").like(val); - } - }, - not_like { - public Criterion buildPredicate(String val, String regexClause) { - return OP.like.buildPredicate(val, regexClause); - } - }, - contains { - public Criterion buildPredicate(String val, String regexClause) { - return Property.forName("mv.value").like("%" + val + "%"); - } - }, - doesnt_contain { - public Criterion buildPredicate(String val, String regexClause) { - return OP.contains.buildPredicate(val, regexClause); - } - }, - exists { - public Criterion buildPredicate(String val, String regexClause) { - return Property.forName("mv.value").isNotNull(); - } - }, - doesnt_exist { - public Criterion buildPredicate(String val, String regexClause) { - return OP.exists.buildPredicate(val, regexClause); - } - }, - matches { - public Criterion buildPredicate(String val, String regexClause) { - return Restrictions.sqlRestriction(regexClause, val, StandardBasicTypes.STRING); - } - }, - doesnt_match { - public Criterion buildPredicate(String val, String regexClause) { - return OP.matches.buildPredicate(val, regexClause); - } - - }; - public abstract Criterion buildPredicate(String val, String regexClause); - } - - @Override - @Deprecated - public Iterator findByMetadataQuery(Context context, List> listFieldList, - List query_op, List query_val, List collectionUuids, - String regexClause, int offset, int limit) throws SQLException { - - Criteria criteria = getHibernateSession(context).createCriteria(Item.class, "item"); - criteria.setFirstResult(offset); - criteria.setMaxResults(limit); - - if (!collectionUuids.isEmpty()) { - DetachedCriteria dcollCriteria = DetachedCriteria.forClass(Collection.class, "coll"); - dcollCriteria.setProjection(Projections.property("coll.id")); - dcollCriteria.add(Restrictions.eqProperty("coll.id", "item.owningCollection")); - dcollCriteria.add(Restrictions.in("coll.id", collectionUuids)); - criteria.add(Subqueries.exists(dcollCriteria)); - } - - int index = Math.min(listFieldList.size(), Math.min(query_op.size(), query_val.size())); - StringBuilder sb = new StringBuilder(); - - for (int i = 0; i < index; i++) { - OP op = OP.valueOf(query_op.get(i)); - if (op == null) { - log.warn("Skipping Invalid Operator: " + query_op.get(i)); - continue; - } - - if (op == OP.matches || op == OP.doesnt_match) { - if (regexClause.isEmpty()) { - log.warn("Skipping Unsupported Regex Operator: " + query_op.get(i)); - continue; - } - } - - DetachedCriteria subcriteria = DetachedCriteria.forClass(MetadataValue.class, "mv"); - subcriteria.add(Property.forName("mv.dSpaceObject").eqProperty("item.id")); - subcriteria.setProjection(Projections.property("mv.dSpaceObject")); - - if (!listFieldList.get(i).isEmpty()) { - subcriteria.add(Restrictions.in("metadataField", listFieldList.get(i))); - } - - subcriteria.add(op.buildPredicate(query_val.get(i), regexClause)); - - if (op == OP.exists || op == OP.equals || op == OP.like || op == OP.contains || op == OP.matches) { - criteria.add(Subqueries.exists(subcriteria)); - } else { - criteria.add(Subqueries.notExists(subcriteria)); - } - } - criteria.addOrder(Order.asc("item.id")); - - log.debug(String.format("Running custom query with %d filters", index)); - - return ((List) criteria.list()).iterator(); - - } - @Override public Iterator findByAuthorityValue(Context context, MetadataField metadataField, String authority, boolean inArchive) throws SQLException { 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 de7644af83..43a804cde2 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 @@ -23,7 +23,6 @@ import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.EntityType; import org.dspace.content.Item; -import org.dspace.content.MetadataField; import org.dspace.content.MetadataValue; import org.dspace.content.Thumbnail; import org.dspace.content.WorkspaceItem; @@ -749,11 +748,6 @@ public interface ItemService String schema, String element, String qualifier, String value) throws SQLException, AuthorizeException, IOException; - public Iterator findByMetadataQuery(Context context, List> listFieldList, - List query_op, List query_val, List collectionUuids, - String regexClause, int offset, int limit) - throws SQLException, AuthorizeException, IOException; - /** * Find all the items in the archive with a given authority key value * in the indicated metadata field. diff --git a/dspace-rest/README.md b/dspace-rest/README.md deleted file mode 100644 index 07d71d66ed..0000000000 --- a/dspace-rest/README.md +++ /dev/null @@ -1,194 +0,0 @@ -#DSpace REST API (Jersey) - DEPRECATED - -A RESTful web services API for DSpace, built using JAX-RS1 JERSEY. - -_This REST API has been deprecated and will be removed in v8. Please use the Server API (/server) webapp instead._ - -##Getting Started -This REST API is integrated directly into the DSpace codebase. - - * Rebuild as usual: mvn + ant - * Deploy the webapp (i.e to Tomcat) - * `````` - - -REST API can do all CRUD (create, read, update, delete) operations over communities, collections, items, bitstream and bitstream policies. Without logging into the REST API, you have read access as an anonymous user (member of the Anonymous group). If you want to make changes in DSpace using the REST API, you must log into the API using the "login" endpoint and then use the returned token in request header of your subsequent API calls. - -##Endpoints - -| Resource |CREATE|READ list|READ single|Edit|Delete|Search| -| ------------- |------|:-------:|-----------|----|------|------| -| /communities | Y | Y | Y | Y | Y | | -| /collections | Y | Y | Y | Y | Y | Y | -| /items | Y | Y | Y | Y | Y | Y | -| /bitstreams | Y | Y | Y | Y | Y | || - -Search in collections is possible only by name and search in items only by metadata field. - -###Index -Get information on how to use the API -- GET http://localhost:8080 - -Test whether the REST API is running and available -- GET http://localhost:8080/rest/test - -Log into REST API -- POST http://localhost:8080/rest/login - -Logout from REST API -- POST http://localhost:8080/rest/logout - -Get status of REST API and the logged-in user -- GET http://localhost:8080/rest/status - - -###Communities -View the list of top-level communities -- GET http://localhost:8080/rest/communities/top-communities - -View the list of all communities -- GET http://localhost:8080/rest/communities[?expand={collections,parentCommunity,subCommunities,logo,all}] - -View a specific community -- GET http://localhost:8080/rest/communities/:ID[?expand={collections,parentCommunity,subCommunities,logo,all}] - -View the list of subcollections in community -- GET http://localhost:8080/rest/communities/:ID/collections[?expand={items,parentCommunityList,license,logo,all}] - -View the list of subcommunities in community -- GET http://localhost:8080/rest/communities/:ID/communities[?expand={collections,parentCommunity,subCommunities,logo,all}] - -Create new top-level community -- POST http://localhost:8080/rest/communities - -Create new subcollection in community -- POST http://localhost:8080/rest/communities/:ID/collections - -Create new subcommunity in community -- POST http://localhost:8080/rest/communities/:ID/communities - -Update community -- PUT http://localhost:8080/rest/communities/:ID - -Delete community -- DELETE http://localhost:8080/rest/communities/:ID - -Delete subcollection in community -- DELETE http://localhost:8080/rest/communities/:ID/collections/:ID - -Delete subcommunity in community -- DELETE http://localhost:8080/rest/communities/:ID/communities/:ID - - -###Collections -View the list of collections -- GET http://localhost:8080/rest/collections[?expand={items,parentCommunityList,license,logo,all}] - -View a specific collection -- GET http://localhost:8080/rest/collections/:ID[?expand={items,parentCommunityList,license,logo,all}] - -View items in collection -- GET http://localhost:8080/rest/collections/:ID/items[?expand={metadata,parentCollection,parentcollectionList,parentCommunityList,bitstreams,all}] - -Create item in collection -- POST http://localhost:8080/rest/collections/:ID/items - -Find collection by name -- POST http://localhost:8080/rest/collections/find-collection - -Update collection -- PUT http://localhost:8080/rest/collections/:ID - -Delete collection -- DELETE http://localhost:8080/rest/collections/:ID - -Delete item in collection -- DELETE http://localhost:8080/rest/collections/:ID/items/:ID - - -###Items -View the list of items -- GET http://localhost:8080/rest/items[?expand={metadata,parentCollection,parentcollectionList,parentCommunityList,bitstreams,all}] - -View speciific item -- GET http://localhost:8080/rest/items/:ID[?expand={metadata,parentCollection,parentcollectionList,parentCommunityList,bitstreams,all}] - -View an Item and view its bitstreams -- GET http://localhost:8080/rest/items/:ID/bitstreams[?expand={parent,policies,all}] - -View an Item, and view its metadata -- GET http://localhost:8080/rest/items/:ID/metadata - -Find item by metadata -- POST http://localhost:8080/rest/items/find-by-metadata-field - -Add metadata to item -- POST http://localhost:8080/rest/items/:ID/metadata - -Create bitstream in item -- POST http://localhost:8080/rest/items/:ID/bitstreams - -Update metadata in item -- PUT http://localhost:8080/rest/items/:ID/metadata - -Delete item -- DELETE http://localhost:8080/rest/items/:ID - -Delete all metadata in item -- DELETE http://localhost:8080/rest/items/:ID/metadata - -Delete bitstream in item -- DELETE http://localhost:8080/rest/items/:ID/bitstreams/:ID - - -###Bitstreams -View the list of bitstreams -- GET http://localhost:8080/rest/bitstreams[?expand={parent,policies,all}] - -View information about a bitstream -- GET http://localhost:8080/rest/bitstreams/:ID[?expand={parent,policies,all}] - -View/Download a specific Bitstream -- GET http://localhost:8080/rest/bitstreams/:ID/retrieve - -View the list of policies of bitstream -- GET http://localhost:8080/rest/bitstreams/:ID/policy - -Add policy to bitstream -- POST http://localhost:8080/rest/bitstreams/:ID/policy - -Update bitstream -- PUT http://localhost:8080/rest/bitstreams/:ID - -Update data of bitstream -- PUT http://localhost:8080/rest/bitstreams/:ID/data - -Delete bitstream -- DELETE http://localhost:8080/rest/bitstreams/:ID - -Delete policy of bitstream -- DELETE http://localhost:8080/rest/bitstreams/:ID/policy/:ID - - -####Statistics -Recording view events of items and download events of bitstreams (set stats = true in rest.cfg to enable recording of events) -http://localhost:8080/rest/items/:ID?userIP=ip&userAgent=userAgent&xforwardedfor=xforwardedfor -If no parameters are given, the details of the HTTP request sender are used in statistics. -This enables tools like proxies to supply the details of their user rather than themselves. - - -###Handles -Lookup a DSpaceObject by its Handle, this produces the name/ID that you look up in /bitstreams, /items, /collections, /communities -- http://localhost:8080/rest/handle/{prefix}/{suffix} - -##Expand -There is an ?expand= query parameter for more expensive operations. You can add it at the end of the request URL. -It is optional, all, some or none. The response will usually indicate what the available "expand" options are. - -##HTTP Responses -* 200 OK - The requested object/objects exists -* 401 Unauthorized - The anonymous user does not have READ access to that object -* 404 Not Found - The specified object doesn't exist -* 405 Method Not Allowed - Wrong request method (GET,POST,PUT,DELETE) or wrong data format (JSON/XML). -* 415 Unsupported Media Type - Missing "Content-Type: application/json" or "Content-Type: application/xml" request header -* 500 Server Error - Likely a SQLException, IOException, more details in the logs. diff --git a/dspace-rest/pom.xml b/dspace-rest/pom.xml deleted file mode 100644 index d7daf92aba..0000000000 --- a/dspace-rest/pom.xml +++ /dev/null @@ -1,202 +0,0 @@ - - 4.0.0 - org.dspace - dspace-rest - war - 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! - http://demo.dspace.org - - - org.dspace - dspace-parent - 8.0-SNAPSHOT - .. - - - - - ${basedir}/.. - - - - - org.apache.maven.plugins - maven-war-plugin - - true - - true - - - - com.mycila - license-maven-plugin - - - - **/static/reports/spin.js - **/static/reports/README.md - **/*.xsd - - - - - - - - - - org.glassfish.jersey.core - jersey-server - ${jersey.version} - - - org.glassfish.jersey.containers - jersey-container-servlet - ${jersey.version} - - - org.glassfish.jersey.media - jersey-media-json-jackson - ${jersey.version} - - - org.glassfish.jersey.media - jersey-media-jaxb - ${jersey.version} - - - - - org.springframework - spring-core - - - - org.springframework - spring-context - - - - org.springframework - spring-web - - - - - org.glassfish.jersey.ext - jersey-spring5 - ${jersey.version} - - - - org.springframework - spring - - - org.springframework - spring-core - - - org.springframework - spring-web - - - org.springframework - spring-beans - - - org.springframework - spring-context - - - org.springframework - spring-aop - - - - jakarta.annotation - jakarta.annotation-api - - - - - org.springframework.security - spring-security-core - ${spring-security.version} - - - - org.springframework - spring-expression - - - - - org.springframework.security - spring-security-web - ${spring-security.version} - - - - org.springframework - spring-expression - - - - - org.springframework.security - spring-security-config - ${spring-security.version} - - - - org.dspace - dspace-api - - - - - org.apache.commons - commons-dbcp2 - - - org.postgresql - postgresql - - - javax.servlet - javax.servlet-api - provided - - - org.atteo - evo-inflector - 1.2.1 - - - org.apache.logging.log4j - log4j-api - - - org.apache.logging.log4j - log4j-core - - - org.apache.logging.log4j - log4j-web - - - org.dspace - dspace-services - - - junit - junit - test - - - diff --git a/dspace-rest/src/main/java/org/dspace/rest/BitstreamResource.java b/dspace-rest/src/main/java/org/dspace/rest/BitstreamResource.java deleted file mode 100644 index 3a6ad85960..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/BitstreamResource.java +++ /dev/null @@ -1,783 +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.rest; - -import java.io.IOException; -import java.io.InputStream; -import java.net.URLConnection; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; -import javax.servlet.http.HttpServletRequest; -import javax.ws.rs.Consumes; -import javax.ws.rs.DELETE; -import javax.ws.rs.DefaultValue; -import javax.ws.rs.GET; -import javax.ws.rs.POST; -import javax.ws.rs.PUT; -import javax.ws.rs.Path; -import javax.ws.rs.PathParam; -import javax.ws.rs.Produces; -import javax.ws.rs.QueryParam; -import javax.ws.rs.WebApplicationException; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.HttpHeaders; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; -import javax.ws.rs.core.Response.Status; - -import org.apache.logging.log4j.Logger; -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.BitstreamFormat; -import org.dspace.content.factory.ContentServiceFactory; -import org.dspace.content.service.BitstreamFormatService; -import org.dspace.content.service.BitstreamService; -import org.dspace.content.service.BundleService; -import org.dspace.eperson.factory.EPersonServiceFactory; -import org.dspace.eperson.service.GroupService; -import org.dspace.rest.common.Bitstream; -import org.dspace.rest.common.ResourcePolicy; -import org.dspace.rest.exceptions.ContextException; -import org.dspace.storage.bitstore.factory.StorageServiceFactory; -import org.dspace.storage.bitstore.service.BitstreamStorageService; -import org.dspace.usage.UsageEvent; - -/** - * @author Rostislav Novak (Computing and Information Centre, CTU in Prague) - */ -// Every DSpace class used without namespace is from package -// org.dspace.rest.common.*. Otherwise namespace is defined. -@Path("/bitstreams") -public class BitstreamResource extends Resource { - protected BitstreamService bitstreamService = ContentServiceFactory.getInstance().getBitstreamService(); - protected BundleService bundleService = ContentServiceFactory.getInstance().getBundleService(); - protected AuthorizeService authorizeService = AuthorizeServiceFactory.getInstance().getAuthorizeService(); - protected BitstreamFormatService bitstreamFormatService = ContentServiceFactory.getInstance() - .getBitstreamFormatService(); - protected BitstreamStorageService bitstreamStorageService = StorageServiceFactory.getInstance() - .getBitstreamStorageService(); - protected ResourcePolicyService resourcePolicyService = AuthorizeServiceFactory.getInstance() - .getResourcePolicyService(); - protected GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); - - private static Logger log = org.apache.logging.log4j.LogManager.getLogger(BitstreamResource.class); - - /** - * Return bitstream properties without file data. It can throw - * WebApplicationException with three response codes. Response code - * NOT_FOUND(404) or UNAUTHORIZED(401) or INTERNAL_SERVER_ERROR(500). Bad - * request is when the bitstream id does not exist. UNAUTHORIZED if the user - * logged into the DSpace context does not have the permission to access the - * bitstream. Server error when something went wrong. - * - * @param bitstreamId Id of bitstream in DSpace. - * @param expand This string defines which additional optional fields will be added - * to bitstream response. Individual options are separated by commas without - * spaces. The options are: "all", "parent". - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the item as the user logged into the - * context. The value of the "rest-dspace-token" header must be set - * to the token received from the login method response. - * @param request Servlet's HTTP request object. - * @return If user is allowed to read bitstream, it returns instance of - * bitstream. Otherwise, it throws WebApplicationException with - * response code UNAUTHORIZED. - * @throws WebApplicationException It can happen on: Bad request, unauthorized, SQL exception - * and context exception(could not create context). - */ - @GET - @Path("/{bitstream_id}") - @Produces( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public Bitstream getBitstream(@PathParam("bitstream_id") String bitstreamId, @QueryParam("expand") String expand, - @QueryParam("userIP") String user_ip, @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, @Context HttpHeaders headers, - @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Reading bitstream(id=" + bitstreamId + ") metadata."); - org.dspace.core.Context context = null; - Bitstream bitstream = null; - - try { - context = createContext(); - org.dspace.content.Bitstream dspaceBitstream = findBitstream(context, bitstreamId, - org.dspace.core.Constants.READ); - - writeStats(dspaceBitstream, UsageEvent.Action.VIEW, user_ip, user_agent, xforwardedfor, headers, - request, context); - - bitstream = new Bitstream(dspaceBitstream, servletContext, expand, context); - context.complete(); - log.trace("Bitstream(id=" + bitstreamId + ") was successfully read."); - - } catch (SQLException e) { - processException( - "Someting went wrong while reading bitstream(id=" + bitstreamId + ") from database! Message: " + e, - context); - } catch (ContextException e) { - processException( - "Someting went wrong while reading bitstream(id=" + bitstreamId + "), ContextException. Message: " - + e.getMessage(), context); - } finally { - processFinally(context); - } - - return bitstream; - } - - /** - * Return all bitstream resource policies from all bundles, in which - * the bitstream is present. - * - * @param bitstreamId Id of bitstream in DSpace. - * @param headers If you want to access the item as the user logged into the context. - * The header "rest-dspace-token" with the token passed - * from the login method must be set. - * @return Returns an array of ResourcePolicy objects. - */ - @GET - @Path("/{bitstream_id}/policy") - @Produces( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public ResourcePolicy[] getBitstreamPolicies(@PathParam("bitstream_id") String bitstreamId, - @Context HttpHeaders headers) { - - log.info("Reading bitstream(id=" + bitstreamId + ") policies."); - org.dspace.core.Context context = null; - ResourcePolicy[] policies = null; - - try { - context = createContext(); - org.dspace.content.Bitstream dspaceBitstream = findBitstream(context, bitstreamId, - org.dspace.core.Constants.READ); - policies = new Bitstream(dspaceBitstream, servletContext, "policies", context).getPolicies(); - - context.complete(); - log.trace("Policies for bitstream(id=" + bitstreamId + ") was successfully read."); - - } catch (SQLException e) { - processException("Someting went wrong while reading policies of bitstream(id=" + bitstreamId - + "), SQLException! Message: " + e, context); - } catch (ContextException e) { - processException("Someting went wrong while reading policies of bitstream(id=" + bitstreamId - + "), ContextException. Message: " + e.getMessage(), context); - } finally { - processFinally(context); - } - - return policies; - } - - /** - * Read list of bitstreams. It throws WebApplicationException with response - * code INTERNAL_SERVER_ERROR(500), if there was problem while reading - * bitstreams from database. - * - * @param expand This string defines which additional optional fields will be added - * to bitstream response. Individual options are separated by commas without - * spaces. The options are: "all", "parent". - * @param limit How many bitstreams will be in the list. Default value is 100. - * @param offset On which offset (item) the list starts. Default value is 0. - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the item as the user logged into the context. - * The header "rest-dspace-token" with the token passed - * from the login method must be set. - * @param request Servlet's HTTP request object. - * @return Returns an array of bistreams. Array doesn't contain bitstreams for - * which the user doesn't have read permission. - * @throws WebApplicationException Thrown in case of a problem with reading the database or with - * creating a context. - */ - @GET - @Produces( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public Bitstream[] getBitstreams(@QueryParam("expand") String expand, - @QueryParam("limit") @DefaultValue("100") Integer limit, - @QueryParam("offset") @DefaultValue("0") Integer offset, - @QueryParam("userIP") String user_ip, @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, @Context HttpHeaders headers, - @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Reading bitstreams.(offset=" + offset + ",limit=" + limit + ")"); - org.dspace.core.Context context = null; - List bitstreams = new ArrayList(); - - try { - context = createContext(); - List dspaceBitstreams = bitstreamService.findAll(context); - - if (!((limit != null) && (limit >= 0) && (offset != null) && (offset >= 0))) { - log.warn("Paging was badly set."); - limit = 100; - offset = 0; - } - - // TODO If bitstream doesn't exist, throws exception. - for (int i = offset; (i < (offset + limit)) && (i < dspaceBitstreams.size()); i++) { - if (authorizeService - .authorizeActionBoolean(context, dspaceBitstreams.get(i), org.dspace.core.Constants.READ)) { - if (bitstreamService.getParentObject(context, dspaceBitstreams - .get(i)) != null) { // To eliminate bitstreams which cause exception, because of - // reading under administrator permissions - bitstreams.add(new Bitstream(dspaceBitstreams.get(i), servletContext, expand, context)); - writeStats(dspaceBitstreams.get(i), UsageEvent.Action.VIEW, user_ip, user_agent, - xforwardedfor, headers, request, context); - } - } - } - - context.complete(); - log.trace("Bitstreams were successfully read."); - - } catch (SQLException e) { - processException("Something went wrong while reading bitstreams from database!. Message: " + e, context); - } catch (ContextException e) { - processException( - "Something went wrong while reading bitstreams, ContextException. Message: " + e.getMessage(), - context); - } finally { - processFinally(context); - } - - return bitstreams.toArray(new Bitstream[0]); - } - - /** - * Read bitstream data. May throw WebApplicationException with the - * INTERNAL_SERVER_ERROR(500) code. Caused by three exceptions: IOException if - * there was a problem with reading bitstream file. SQLException if there was - * a problem while reading from database. And AuthorizeException if there was - * a problem with authorization of user logged to DSpace context. - * - * @param bitstreamId Id of the bitstream, whose data will be read. - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the item as the user logged into the context. - * The header "rest-dspace-token" with the token passed - * from the login method must be set. - * @param request Servlet's HTTP request object. - * @return Returns response with data with file content type. It can - * return the NOT_FOUND(404) response code in case of wrong bitstream - * id. Or response code UNAUTHORIZED(401) if user is not - * allowed to read bitstream. - * @throws WebApplicationException Thrown if there was a problem: reading the file data; or reading - * the database; or creating the context; or with authorization. - */ - @GET - @Path("/{bitstream_id}/retrieve") - public javax.ws.rs.core.Response getBitstreamData(@PathParam("bitstream_id") String bitstreamId, - @QueryParam("userIP") String user_ip, - @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, - @Context HttpHeaders headers, @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Reading data of bitstream(id=" + bitstreamId + ")."); - org.dspace.core.Context context = null; - InputStream inputStream = null; - String type = null; - String name = null; - - try { - context = createContext(); - org.dspace.content.Bitstream dspaceBitstream = findBitstream(context, bitstreamId, - org.dspace.core.Constants.READ); - - writeStats(dspaceBitstream, UsageEvent.Action.VIEW, user_ip, user_agent, xforwardedfor, headers, - request, context); - - log.trace("Bitstream(id=" + bitstreamId + ") data was successfully read."); - inputStream = bitstreamService.retrieve(context, dspaceBitstream); - type = dspaceBitstream.getFormat(context).getMIMEType(); - name = dspaceBitstream.getName(); - - context.complete(); - } catch (IOException e) { - processException("Could not read file of bitstream(id=" + bitstreamId + ")! Message: " + e, context); - } catch (SQLException e) { - processException( - "Something went wrong while reading bitstream(id=" + bitstreamId + ") from database! Message: " + e, - context); - } catch (AuthorizeException e) { - processException( - "Could not retrieve file of bitstream(id=" + bitstreamId + "), AuthorizeException! Message: " + e, - context); - } catch (ContextException e) { - processException( - "Could not retrieve file of bitstream(id=" + bitstreamId + "), ContextException! Message: " + e - .getMessage(), - context); - } finally { - processFinally(context); - } - - return Response.ok(inputStream).type(type) - .header("Content-Disposition", "attachment; filename=\"" + name + "\"") - .build(); - } - - /** - * Add bitstream policy to all bundles containing the bitstream. - * - * @param bitstreamId Id of bitstream in DSpace. - * @param policy Policy to be added. The following attributes are not - * applied: epersonId, - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the item as the user logged into the context. - * The header "rest-dspace-token" with the token passed - * from the login method must be set. - * @param request Servlet's HTTP request object. - * @return Returns ok, if all was ok. Otherwise status code 500. - */ - @POST - @Path("/{bitstream_id}/policy") - @Consumes( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public javax.ws.rs.core.Response addBitstreamPolicy(@PathParam("bitstream_id") String bitstreamId, - ResourcePolicy policy, - @QueryParam("userIP") String user_ip, - @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, - @Context HttpHeaders headers, - @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Adding bitstream(id=" + bitstreamId + ") " + policy - .getAction() + " policy with permission for group(id=" + policy.getGroupId() - + ")."); - org.dspace.core.Context context = null; - - try { - context = createContext(); - org.dspace.content.Bitstream dspaceBitstream = findBitstream(context, bitstreamId, - org.dspace.core.Constants.WRITE); - - writeStats(dspaceBitstream, UsageEvent.Action.UPDATE, user_ip, user_agent, xforwardedfor, headers, - request, context); - - addPolicyToBitstream(context, policy, dspaceBitstream); - - context.complete(); - log.trace("Policy for bitstream(id=" + bitstreamId + ") was successfully added."); - - } catch (SQLException e) { - processException("Someting went wrong while adding policy to bitstream(id=" + bitstreamId - + "), SQLException! Message: " + e, context); - } catch (ContextException e) { - processException("Someting went wrong while adding policy to bitstream(id=" + bitstreamId - + "), ContextException. Message: " + e.getMessage(), context); - } catch (AuthorizeException e) { - processException("Someting went wrong while adding policy to bitstream(id=" + bitstreamId - + "), AuthorizeException! Message: " + e, context); - } finally { - processFinally(context); - } - return Response.status(Status.OK).build(); - } - - /** - * Update bitstream metadata. Replaces everything on targeted bitstream. - * May throw WebApplicationException caused by two exceptions: - * SQLException, if there was a problem with the database. AuthorizeException if - * there was a problem with the authorization to edit bitstream metadata. - * - * @param bitstreamId Id of bistream to be updated. - * @param bitstream Bitstream with will be placed. It must have filled user - * credentials. - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the item as the user logged into the context. - * The header "rest-dspace-token" with the token passed - * from the login method must be set. - * @param request Servlet's HTTP request object. - * @return Return response codes: OK(200), NOT_FOUND(404) if bitstream does - * not exist and UNAUTHORIZED(401) if user is not allowed to write - * to bitstream. - * @throws WebApplicationException Thrown when: Error reading from database; or error - * creating context; or error regarding bitstream authorization. - */ - @PUT - @Path("/{bitstream_id}") - @Consumes( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public Response updateBitstream(@PathParam("bitstream_id") String bitstreamId, Bitstream bitstream, - @QueryParam("userIP") String user_ip, @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, @Context HttpHeaders headers, - @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Updating bitstream(id=" + bitstreamId + ") metadata."); - org.dspace.core.Context context = null; - - try { - context = createContext(); - org.dspace.content.Bitstream dspaceBitstream = findBitstream(context, bitstreamId, - org.dspace.core.Constants.WRITE); - - writeStats(dspaceBitstream, UsageEvent.Action.UPDATE, user_ip, user_agent, xforwardedfor, - headers, request, context); - - log.trace("Updating bitstream metadata."); - - dspaceBitstream.setDescription(context, bitstream.getDescription()); - if (getMimeType(bitstream.getName()) == null) { - BitstreamFormat unknownFormat = bitstreamFormatService.findUnknown(context); - bitstreamService.setFormat(context, dspaceBitstream, unknownFormat); - } else { - BitstreamFormat guessedFormat = bitstreamFormatService - .findByMIMEType(context, getMimeType(bitstream.getName())); - bitstreamService.setFormat(context, dspaceBitstream, guessedFormat); - } - dspaceBitstream.setName(context, bitstream.getName()); - Integer sequenceId = bitstream.getSequenceId(); - if (sequenceId != null && sequenceId.intValue() != -1) { - dspaceBitstream.setSequenceID(sequenceId); - } - - bitstreamService.update(context, dspaceBitstream); - - if (bitstream.getPolicies() != null) { - log.trace("Updating bitstream policies."); - - // Remove all old bitstream policies. - authorizeService.removeAllPolicies(context, dspaceBitstream); - - // Add all new bitstream policies - for (ResourcePolicy policy : bitstream.getPolicies()) { - addPolicyToBitstream(context, policy, dspaceBitstream); - } - } - - context.complete(); - - } catch (SQLException e) { - processException("Could not update bitstream(id=" + bitstreamId + ") metadata, SQLException. Message: " + e, - context); - } catch (AuthorizeException e) { - processException( - "Could not update bitstream(id=" + bitstreamId + ") metadata, AuthorizeException. Message: " + e, - context); - } catch (ContextException e) { - processException( - "Could not update bitstream(id=" + bitstreamId + ") metadata, ContextException. Message: " + e - .getMessage(), - context); - } finally { - processFinally(context); - } - - log.info("Bitstream metadata(id=" + bitstreamId + ") were successfully updated."); - return Response.ok().build(); - } - - /** - * Update bitstream data. Changes bitstream data by editing database rows. - * May throw WebApplicationException caused by: SQLException if there was - * a problem editing or reading the database, IOException if there was - * a problem with reading from InputStream, Exception if there was another - * problem. - * - * @param bitstreamId Id of bistream to be updated. - * @param is InputStream filled with new data. - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the item as the user logged into the context. - * The header "rest-dspace-token" with the token passed - * from the login method must be set. - * @param request Servlet's HTTP request object. - * @return Return response if bitstream was updated. Response codes: - * OK(200), NOT_FOUND(404) if id of bitstream was bad. And - * UNAUTHORIZED(401) if user is not allowed to update bitstream. - * @throws WebApplicationException This exception can be thrown in this cases: Problem with - * reading or writing to database. Or problem with reading from - * InputStream. - */ - // TODO Change to better logic, without editing database. - @PUT - @Path("/{bitstream_id}/data") - public Response updateBitstreamData(@PathParam("bitstream_id") String bitstreamId, InputStream is, - @QueryParam("userIP") String user_ip, - @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, @Context HttpHeaders headers, - @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Updating bitstream(id=" + bitstreamId + ") data."); - org.dspace.core.Context context = null; - - try { - context = createContext(); - org.dspace.content.Bitstream dspaceBitstream = findBitstream(context, bitstreamId, - org.dspace.core.Constants.WRITE); - - writeStats(dspaceBitstream, UsageEvent.Action.UPDATE, user_ip, user_agent, xforwardedfor, - headers, request, context); - - log.trace("Creating new bitstream."); - - UUID newBitstreamId = bitstreamStorageService.store(context, dspaceBitstream, is); - log.trace("Bitstream data stored: " + newBitstreamId); - context.complete(); - } catch (SQLException e) { - processException("Could not update bitstream(id=" + bitstreamId + ") data, SQLException. Message: " + e, - context); - } catch (IOException e) { - processException("Could not update bitstream(id=" + bitstreamId + ") data, IOException. Message: " + e, - context); - } catch (ContextException e) { - processException( - "Could not update bitstream(id=" + bitstreamId + ") data, ContextException. Message: " + e.getMessage(), - context); - } finally { - processFinally(context); - } - - log.info("Bitstream(id=" + bitstreamId + ") data was successfully updated."); - return Response.ok().build(); - } - - /** - * Delete bitstream from all bundles in DSpace. May throw - * WebApplicationException, which can be caused by three exceptions. - * SQLException if there was a problem reading from database or removing - * from database. AuthorizeException, if user doesn't have permission to delete - * the bitstream or file. IOException, if there was a problem deleting the file. - * - * @param bitstreamId Id of bitstream to be deleted. - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the item as the user logged into the context. - * The header "rest-dspace-token" with the token passed - * from the login method must be set. - * @param request Servlet's HTTP request object. - * @return Return response codes: OK(200), NOT_FOUND(404) if bitstream of - * that id does not exist and UNAUTHORIZED(401) if user is not - * allowed to delete bitstream. - * @throws WebApplicationException Can be thrown if there was a problem reading or editing - * the database. Or problem deleting the file. Or problem with - * authorization to bitstream and bundles. Or problem with - * creating context. - */ - @DELETE - @Path("/{bitstream_id}") - public Response deleteBitstream(@PathParam("bitstream_id") String bitstreamId, @QueryParam("userIP") String user_ip, - @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, - @Context HttpHeaders headers, @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Deleting bitstream(id=" + bitstreamId + ")."); - org.dspace.core.Context context = null; - - try { - context = createContext(); - org.dspace.content.Bitstream dspaceBitstream = findBitstream(context, bitstreamId, - org.dspace.core.Constants.DELETE); - - writeStats(dspaceBitstream, UsageEvent.Action.DELETE, user_ip, user_agent, xforwardedfor, - headers, request, context); - - log.trace("Deleting bitstream from all bundles."); - bitstreamService.delete(context, dspaceBitstream); - - context.complete(); - } catch (SQLException e) { - processException("Could not delete bitstream(id=" + bitstreamId + "), SQLException. Message: " + e, - context); - } catch (AuthorizeException e) { - processException("Could not delete bitstream(id=" + bitstreamId + "), AuthorizeException. Message: " + e, - context); - } catch (IOException e) { - processException("Could not delete bitstream(id=" + bitstreamId + "), IOException. Message: " + e, context); - } catch (ContextException e) { - processException( - "Could not delete bitstream(id=" + bitstreamId + "), ContextException. Message:" + e.getMessage(), - context); - } finally { - processFinally(context); - } - - log.info("Bitstream(id=" + bitstreamId + ") was successfully deleted."); - return Response.ok().build(); - } - - /** - * Delete policy. - * - * @param bitstreamId Id of the DSpace bitstream whose policy will be deleted. - * @param policyId Id of the policy to delete. - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the item as the user logged into the context. - * The header "rest-dspace-token" with the token passed - * from the login method must be set. - * @param request Servlet's HTTP request object. - * @return It returns Ok, if all was ok. Otherwise status code 500. - */ - @DELETE - @Path("/{bitstream_id}/policy/{policy_id}") - public javax.ws.rs.core.Response deleteBitstreamPolicy(@PathParam("bitstream_id") String bitstreamId, - @PathParam("policy_id") Integer policyId, - @QueryParam("userIP") String user_ip, - @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, - @Context HttpHeaders headers, - @Context HttpServletRequest request) - throws WebApplicationException { - log.info("Deleting policy(id=" + policyId + ") from bitstream(id=" + bitstreamId + ")."); - org.dspace.core.Context context = null; - - try { - context = createContext(); - org.dspace.content.Bitstream dspaceBitstream = findBitstream(context, bitstreamId, - org.dspace.core.Constants.WRITE); - - writeStats(dspaceBitstream, UsageEvent.Action.UPDATE, user_ip, user_agent, xforwardedfor, headers, - request, context); - - org.dspace.authorize.ResourcePolicy resourcePolicy = resourcePolicyService.find(context, policyId); - if (resourcePolicy.getdSpaceObject().getID().equals(dspaceBitstream.getID()) && authorizeService - .authorizeActionBoolean(context, dspaceBitstream, org.dspace.core.Constants.REMOVE)) { - - try { - resourcePolicyService.delete(context, resourcePolicy); - } catch (AuthorizeException e) { - processException( - "Someting went wrong while deleting policy(id=" + policyId + ") to bitstream(id=" + bitstreamId - + "), AuthorizeException! Message: " + e, context); - } - log.trace("Policy for bitstream(id=" + bitstreamId + ") was successfully removed."); - } - - context.complete(); - } catch (SQLException e) { - processException( - "Someting went wrong while deleting policy(id=" + policyId + ") to bitstream(id=" + bitstreamId - + "), SQLException! Message: " + e, context); - } catch (ContextException e) { - processException( - "Someting went wrong while deleting policy(id=" + policyId + ") to bitstream(id=" + bitstreamId - + "), ContextException. Message: " + e.getMessage(), context); - } finally { - processFinally(context); - } - - return Response.status(Status.OK).build(); - } - - /** - * Return the MIME type of the file, by file extension. - * - * @param name Name of file. - * @return String filled with type of file in MIME style. - */ - static String getMimeType(String name) { - return URLConnection.guessContentTypeFromName(name); - } - - /** - * Add policy(org.dspace.rest.common.ResourcePolicy) to bitstream. - * - * @param context Context to create DSpace ResourcePolicy. - * @param policy Policy which will be added to bitstream. - * @param dspaceBitstream DSpace Bitstream object. - * @throws SQLException An exception that provides information on a database access error or other errors. - * @throws AuthorizeException Exception indicating the current user of the context does not have permission - * to perform a particular action. - */ - private void addPolicyToBitstream(org.dspace.core.Context context, ResourcePolicy policy, - org.dspace.content.Bitstream dspaceBitstream) - throws SQLException, AuthorizeException { - org.dspace.authorize.ResourcePolicy dspacePolicy = resourcePolicyService.create(context); - dspacePolicy.setAction(policy.getActionInt()); - dspacePolicy.setGroup(groupService.findByIdOrLegacyId(context, policy.getGroupId())); - dspacePolicy.setdSpaceObject(dspaceBitstream); - dspacePolicy.setStartDate(policy.getStartDate()); - dspacePolicy.setEndDate(policy.getEndDate()); - dspacePolicy.setRpDescription(policy.getRpDescription()); - dspacePolicy.setRpName(policy.getRpName()); - - resourcePolicyService.update(context, dspacePolicy); - } - - /** - * Find bitstream from DSpace database. This encapsulates the - * org.dspace.content.Bitstream.find method with a check whether the item exists and - * whether the user logged into the context has permission to preform the requested action. - * - * @param context Context of actual logged user. - * @param id Id of bitstream in DSpace. - * @param action Constant from org.dspace.core.Constants. - * @return Returns DSpace bitstream. - * @throws WebApplicationException Is thrown when item with passed id is not exists and if user - * has no permission to do passed action. - */ - private org.dspace.content.Bitstream findBitstream(org.dspace.core.Context context, String id, int action) - throws WebApplicationException { - org.dspace.content.Bitstream bitstream = null; - try { - bitstream = bitstreamService.findByIdOrLegacyId(context, id); - - if ((bitstream == null) || (bitstreamService.getParentObject(context, bitstream) == null)) { - context.abort(); - log.warn("Bitstream(id=" + id + ") was not found!"); - throw new WebApplicationException(Response.Status.NOT_FOUND); - } else if (!authorizeService.authorizeActionBoolean(context, bitstream, action)) { - context.abort(); - if (context.getCurrentUser() != null) { - log.error("User(" + context.getCurrentUser().getEmail() + ") doesn't have the permission to " - + getActionString(action) + " bitstream!"); - } else { - log.error( - "User(anonymous) doesn't have the permission to " + getActionString(action) + " bitsteam!"); - } - throw new WebApplicationException(Response.Status.UNAUTHORIZED); - } - - } catch (SQLException e) { - processException("Something went wrong while finding bitstream. SQLException, Message:" + e, context); - } - return bitstream; - } -} diff --git a/dspace-rest/src/main/java/org/dspace/rest/CollectionsResource.java b/dspace-rest/src/main/java/org/dspace/rest/CollectionsResource.java deleted file mode 100644 index 395a0af766..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/CollectionsResource.java +++ /dev/null @@ -1,755 +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.rest; - -import static org.dspace.content.service.DSpaceObjectService.MD_COPYRIGHT_TEXT; -import static org.dspace.content.service.DSpaceObjectService.MD_INTRODUCTORY_TEXT; -import static org.dspace.content.service.DSpaceObjectService.MD_LICENSE; -import static org.dspace.content.service.DSpaceObjectService.MD_NAME; -import static org.dspace.content.service.DSpaceObjectService.MD_SHORT_DESCRIPTION; -import static org.dspace.content.service.DSpaceObjectService.MD_SIDEBAR_TEXT; - -import java.io.IOException; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import javax.servlet.http.HttpServletRequest; -import javax.ws.rs.Consumes; -import javax.ws.rs.DELETE; -import javax.ws.rs.DefaultValue; -import javax.ws.rs.GET; -import javax.ws.rs.POST; -import javax.ws.rs.PUT; -import javax.ws.rs.Path; -import javax.ws.rs.PathParam; -import javax.ws.rs.Produces; -import javax.ws.rs.QueryParam; -import javax.ws.rs.WebApplicationException; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.HttpHeaders; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; - -import org.apache.logging.log4j.Logger; -import org.dspace.authorize.AuthorizeException; -import org.dspace.authorize.factory.AuthorizeServiceFactory; -import org.dspace.authorize.service.AuthorizeService; -import org.dspace.content.factory.ContentServiceFactory; -import org.dspace.content.service.CollectionService; -import org.dspace.content.service.ItemService; -import org.dspace.content.service.WorkspaceItemService; -import org.dspace.core.Constants; -import org.dspace.core.LogHelper; -import org.dspace.rest.common.Collection; -import org.dspace.rest.common.Item; -import org.dspace.rest.common.MetadataEntry; -import org.dspace.rest.exceptions.ContextException; -import org.dspace.usage.UsageEvent; -import org.dspace.workflow.WorkflowService; -import org.dspace.workflow.factory.WorkflowServiceFactory; - -/** - * This class provides all CRUD operation over collections. - * - * @author Rostislav Novak (Computing and Information Centre, CTU in Prague) - */ -@Path("/collections") -public class CollectionsResource extends Resource { - protected CollectionService collectionService = ContentServiceFactory.getInstance().getCollectionService(); - protected ItemService itemService = ContentServiceFactory.getInstance().getItemService(); - protected AuthorizeService authorizeService = AuthorizeServiceFactory.getInstance().getAuthorizeService(); - protected WorkspaceItemService workspaceItemService = ContentServiceFactory.getInstance().getWorkspaceItemService(); - protected WorkflowService workflowService = WorkflowServiceFactory.getInstance().getWorkflowService(); - - private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(CollectionsResource.class); - - /** - * Return instance of collection with passed id. You can add more properties - * through expand parameter. - * - * @param collectionId Id of collection in DSpace. - * @param expand String in which is what you want to add to returned instance - * of collection. Options are: "all", "parentCommunityList", - * "parentCommunity", "items", "license" and "logo". If you want - * to use multiple options, it must be separated by commas. - * @param limit Limit value for items in list in collection. Default value is - * 100. - * @param offset Offset of start index in list of items of collection. Default - * value is 0. - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the collection as the user logged into the - * context. The value of the "rest-dspace-token" header must be set - * to the token received from the login method response. - * @param request Servlet's HTTP request object. - * @return Return instance of collection. It can also return status code - * NOT_FOUND(404) if id of collection is incorrect or status code - * UNATHORIZED(401) if user has no permission to read collection. - * @throws WebApplicationException It is thrown when was problem with database reading - * (SQLException) or problem with creating - * context(ContextException). It is thrown by NOT_FOUND and - * UNATHORIZED status codes, too. - */ - @GET - @Path("/{collection_id}") - @Produces( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public org.dspace.rest.common.Collection getCollection(@PathParam("collection_id") String collectionId, - @QueryParam("expand") String expand, - @QueryParam("limit") @DefaultValue("100") Integer limit, - @QueryParam("offset") @DefaultValue("0") Integer offset, - @QueryParam("userIP") String user_ip, - @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, - @Context HttpHeaders headers, - @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Reading collection(id=" + collectionId + ")."); - org.dspace.core.Context context = null; - Collection collection = null; - - try { - context = createContext(); - - org.dspace.content.Collection dspaceCollection = findCollection(context, collectionId, - org.dspace.core.Constants.READ); - writeStats(dspaceCollection, UsageEvent.Action.VIEW, user_ip, user_agent, xforwardedfor, - headers, request, context); - - collection = new Collection(dspaceCollection, servletContext, expand, context, limit, offset); - context.complete(); - - } catch (SQLException e) { - processException("Could not read collection(id=" + collectionId + "), SQLException. Message: " + e, - context); - } catch (ContextException e) { - processException( - "Could not read collection(id=" + collectionId + "), ContextException. Message: " + e.getMessage(), - context); - } finally { - processFinally(context); - } - - log.trace("Collection(id=" + collectionId + ") has been successfully read."); - return collection; - } - - /** - * Return array of all collections in DSpace. You can add more properties - * through expand parameter. - * - * @param expand String in which is what you want to add to returned instance - * of collection. Options are: "all", "parentCommunityList", - * "parentCommunity", "items", "license" and "logo". If you want - * to use multiple options, it must be separated by commas. - * @param limit Limit value for items in list in collection. Default value is - * 100. - * @param offset Offset of start index in list of items of collection. Default - * value is 0. - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the collections as the user logged into the - * context. The value of the "rest-dspace-token" header must be set - * to the token received from the login method response. - * @param request Servlet's HTTP request object. - * @return Return array of collection, on which has logged user permission - * to view. - * @throws WebApplicationException It is thrown when was problem with database reading - * (SQLException) or problem with creating - * context(ContextException). - */ - @GET - @Produces( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public org.dspace.rest.common.Collection[] getCollections(@QueryParam("expand") String expand, - @QueryParam("limit") @DefaultValue("100") Integer limit, - @QueryParam("offset") @DefaultValue("0") Integer offset, - @QueryParam("userIP") String user_ip, - @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, - @Context HttpHeaders headers, - @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Reading all collections.(offset=" + offset + ",limit=" + limit + ")"); - org.dspace.core.Context context = null; - List collections = new ArrayList<>(); - - try { - context = createContext(); - - if (!((limit != null) && (limit >= 0) && (offset != null) && (offset >= 0))) { - log.warn("Paging was badly set."); - limit = 100; - offset = 0; - } - - List dspaceCollections = collectionService.findAll(context, limit, offset); - for (org.dspace.content.Collection dspaceCollection : dspaceCollections) { - if (authorizeService - .authorizeActionBoolean(context, dspaceCollection, org.dspace.core.Constants.READ)) { - Collection collection = new org.dspace.rest.common.Collection(dspaceCollection, servletContext, - null, context, limit, - offset); - collections.add(collection); - writeStats(dspaceCollection, UsageEvent.Action.VIEW, user_ip, user_agent, - xforwardedfor, headers, request, context); - } - } - context.complete(); - } catch (SQLException e) { - processException("Something went wrong while reading collections from database. Message: " + e, context); - } catch (ContextException e) { - processException("Something went wrong while reading collections, ContextError. Message: " + e.getMessage(), - context); - } finally { - processFinally(context); - } - - log.trace("All collections were successfully read."); - return collections.toArray(new org.dspace.rest.common.Collection[0]); - } - - /** - * Return array of items in collection. You can add more properties to items - * with expand parameter. - * - * @param collectionId Id of collection in DSpace. - * @param expand String which define, what additional properties will be in - * returned item. Options are separeted by commas and are: "all", - * "metadata", "parentCollection", "parentCollectionList", - * "parentCommunityList" and "bitstreams". - * @param limit Limit value for items in array. Default value is 100. - * @param offset Offset of start index in array of items of collection. Default - * value is 0. - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the collection as the user logged into the - * context. The value of the "rest-dspace-token" header must be set - * to the token received from the login method response. - * @param request Servlet's HTTP request object. - * @return Return array of items, on which has logged user permission to - * read. It can also return status code NOT_FOUND(404) if id of - * collection is incorrect or status code UNATHORIZED(401) if user - * has no permission to read collection. - * @throws WebApplicationException It is thrown when was problem with database reading - * (SQLException) or problem with creating - * context(ContextException). It is thrown by NOT_FOUND and - * UNATHORIZED status codes, too. - */ - @GET - @Path("/{collection_id}/items") - @Produces( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public org.dspace.rest.common.Item[] getCollectionItems(@PathParam("collection_id") String collectionId, - @QueryParam("expand") String expand, - @QueryParam("limit") @DefaultValue("100") Integer limit, - @QueryParam("offset") @DefaultValue("0") Integer offset, - @QueryParam("userIP") String user_ip, - @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, - @Context HttpHeaders headers, - @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Reading collection(id=" + collectionId + ") items."); - org.dspace.core.Context context = null; - List items = null; - - try { - context = createContext(); - - org.dspace.content.Collection dspaceCollection = findCollection(context, collectionId, - org.dspace.core.Constants.READ); - writeStats(dspaceCollection, UsageEvent.Action.VIEW, user_ip, user_agent, xforwardedfor, - headers, request, context); - - items = new ArrayList<>(); - Iterator dspaceItems = itemService.findByCollection(context, dspaceCollection, - limit, offset); - - while (dspaceItems.hasNext()) { - org.dspace.content.Item dspaceItem = dspaceItems.next(); - - if (itemService.isItemListedForUser(context, dspaceItem)) { - items.add(new Item(dspaceItem, servletContext, expand, context)); - writeStats(dspaceItem, UsageEvent.Action.VIEW, user_ip, user_agent, xforwardedfor, - headers, request, context); - } - } - - context.complete(); - } catch (SQLException e) { - processException("Could not read collection items, SQLException. Message: " + e, context); - } catch (ContextException e) { - processException("Could not read collection items, ContextException. Message: " + e.getMessage(), context); - } finally { - processFinally(context); - } - - log.trace("All items in collection(id=" + collectionId + ") were successfully read."); - return items.toArray(new Item[0]); - } - - /** - * Create item in collection. Item can be without filled metadata. - * - * @param collectionId Id of collection in which will be item created. - * @param item Item filled only with metadata, other variables are ignored. - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the collection as the user logged into the - * context. The value of the "rest-dspace-token" header must be set - * to the token received from the login method response. - * @param request Servlet's HTTP request object. - * @return Return status code with item. Return status (OK)200 if item was - * created. NOT_FOUND(404) if id of collection does not exists. - * UNAUTHORIZED(401) if user have not permission to write items in - * collection. - * @throws WebApplicationException It is thrown when was problem with database reading or - * writing (SQLException) or problem with creating - * context(ContextException) or problem with authorization to - * collection or IOException or problem with index item into - * browse index. It is thrown by NOT_FOUND and UNATHORIZED - * status codes, too. - */ - @POST - @Path("/{collection_id}/items") - @Consumes( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public Item addCollectionItem(@PathParam("collection_id") String collectionId, Item item, - @QueryParam("userIP") String user_ip, @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, @Context HttpHeaders headers, - @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Create item in collection(id=" + collectionId + ")."); - org.dspace.core.Context context = null; - Item returnItem = null; - - try { - context = createContext(); - org.dspace.content.Collection dspaceCollection = findCollection(context, collectionId, - org.dspace.core.Constants.WRITE); - - writeStats(dspaceCollection, UsageEvent.Action.UPDATE, user_ip, user_agent, xforwardedfor, - headers, request, context); - - log.trace("Creating item in collection(id=" + collectionId + ")."); - org.dspace.content.WorkspaceItem workspaceItem = workspaceItemService - .create(context, dspaceCollection, false); - org.dspace.content.Item dspaceItem = workspaceItem.getItem(); - - log.trace("Adding metadata to item(id=" + dspaceItem.getID() + ")."); - if (item.getMetadata() != null) { - for (MetadataEntry entry : item.getMetadata()) { - String data[] = mySplit(entry.getKey()); - itemService.addMetadata(context, dspaceItem, data[0], data[1], data[2], entry.getLanguage(), - entry.getValue()); - } - } - - workspaceItemService.update(context, workspaceItem); - - try { - // Must insert the item into workflow - log.trace("Starting workflow for item(id=" + dspaceItem.getID() + ")."); - workflowService.start(context, workspaceItem); - } catch (Exception e) { - log.error( - LogHelper.getHeader(context, "Error while starting workflow", - "Item id: " + dspaceItem.getID()), - e); - throw new ContextException("Error while starting workflow for item(id=" + dspaceItem.getID() + ")", e); - } - - returnItem = new Item(workspaceItem.getItem(), servletContext, "", context); - - context.complete(); - - } catch (SQLException e) { - processException("Could not add item into collection(id=" + collectionId + "), SQLException. Message: " + e, - context); - } catch (AuthorizeException e) { - processException( - "Could not add item into collection(id=" + collectionId + "), AuthorizeException. Message: " + e, - context); - } catch (ContextException e) { - processException( - "Could not add item into collection(id=" + collectionId + "), ContextException. Message: " + e - .getMessage(), - context); - } finally { - processFinally(context); - } - - log.info( - "Item successfully created in collection(id=" + collectionId + "). Item handle=" + returnItem.getHandle()); - return returnItem; - } - - /** - * Update collection. It replace all properties. - * - * @param collectionId Id of collection in DSpace. - * @param collection Collection which will replace properties of actual collection. - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the collection as the user logged into the - * context. The value of the "rest-dspace-token" header must be set - * to the token received from the login method response. - * @param request Servlet's HTTP request object. - * @return Return response 200 if was everything all right. Otherwise 400 - * when id of community was incorrect or 401 if was problem with - * permission to write into collection. - * @throws WebApplicationException It is thrown when was problem with database reading or - * writing. Or problem with authorization to collection. Or - * problem with creating context. - */ - @PUT - @Path("/{collection_id}") - @Consumes( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public Response updateCollection(@PathParam("collection_id") String collectionId, - org.dspace.rest.common.Collection collection, @QueryParam("userIP") String user_ip, - @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, - @Context HttpHeaders headers, @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Updating collection(id=" + collectionId + ")."); - org.dspace.core.Context context = null; - - try { - context = createContext(); - org.dspace.content.Collection dspaceCollection = findCollection(context, collectionId, - org.dspace.core.Constants.WRITE); - - writeStats(dspaceCollection, UsageEvent.Action.UPDATE, user_ip, user_agent, xforwardedfor, - headers, request, context); - - collectionService.setMetadataSingleValue(context, dspaceCollection, - MD_NAME, collection.getName(), null); - collectionService.setMetadataSingleValue(context, dspaceCollection, - MD_LICENSE, collection.getLicense(), null); - - // dspaceCollection.setLogo(collection.getLogo()); // TODO Add this option. - collectionService.setMetadataSingleValue(context, dspaceCollection, - MD_COPYRIGHT_TEXT, collection.getCopyrightText(), null); - collectionService.setMetadataSingleValue(context, dspaceCollection, - MD_INTRODUCTORY_TEXT, collection.getIntroductoryText(), null); - collectionService.setMetadataSingleValue(context, dspaceCollection, - MD_SHORT_DESCRIPTION, collection.getShortDescription(), null); - collectionService.setMetadataSingleValue(context, dspaceCollection, - MD_SIDEBAR_TEXT, collection.getSidebarText(), null); - collectionService.update(context, dspaceCollection); - - context.complete(); - - } catch (ContextException e) { - processException( - "Could not update collection(id=" + collectionId + "), ContextException. Message: " + e.getMessage(), - context); - } catch (SQLException e) { - processException("Could not update collection(id=" + collectionId + "), SQLException. Message: " + e, - context); - } catch (AuthorizeException e) { - processException("Could not update collection(id=" + collectionId + "), AuthorizeException. Message: " + e, - context); - } finally { - processFinally(context); - } - - log.info("Collection(id=" + collectionId + ") successfully updated."); - return Response.ok().build(); - } - - /** - * Delete collection. - * - * @param collectionId Id of collection which will be deleted. - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the collection as the user logged into the - * context. The value of the "rest-dspace-token" header must be set - * to the token received from the login method response. - * @param request Servlet's HTTP request object. - * @return Return response code OK(200) if was everything all right. - * Otherwise return NOT_FOUND(404) if was id of community or - * collection incorrect. Or (UNAUTHORIZED)401 if was problem with - * permission to community or collection. - * @throws WebApplicationException Thrown if there was a problem with creating context or problem - * with database reading or writing. Or problem with deleting - * collection caused by IOException or authorization. - */ - @DELETE - @Path("/{collection_id}") - @Consumes( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public Response deleteCollection(@PathParam("collection_id") String collectionId, - @QueryParam("userIP") String user_ip, - @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, - @Context HttpHeaders headers, @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Delete collection(id=" + collectionId + ")."); - org.dspace.core.Context context = null; - - try { - context = createContext(); - org.dspace.content.Collection dspaceCollection = findCollection(context, collectionId, - org.dspace.core.Constants.DELETE); - - writeStats(dspaceCollection, UsageEvent.Action.REMOVE, user_ip, user_agent, xforwardedfor, - headers, request, context); - - collectionService.delete(context, dspaceCollection); - collectionService.update(context, dspaceCollection); - - context.complete(); - } catch (ContextException e) { - processException( - "Could not delete collection(id=" + collectionId + "), ContextException. Message: " + e.getMessage(), - context); - } catch (SQLException e) { - processException("Could not delete collection(id=" + collectionId + "), SQLException. Message: " + e, - context); - } catch (AuthorizeException e) { - processException("Could not delete collection(id=" + collectionId + "), AuthorizeException. Message: " + e, - context); - } catch (IOException e) { - processException("Could not delete collection(id=" + collectionId + "), IOException. Message: " + e, - context); - } finally { - processFinally(context); - } - - log.info("Collection(id=" + collectionId + ") was successfully deleted."); - return Response.ok().build(); - } - - /** - * Delete item in collection. - * - * @param collectionId Id of collection which will be deleted. - * @param itemId Id of item in colletion. - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the collection as the user logged into the - * context. The value of the "rest-dspace-token" header must be set - * to the token received from the login method response. - * @param request Servlet's HTTP request object. - * @return It returns status code: OK(200). NOT_FOUND(404) if item or - * collection was not found, UNAUTHORIZED(401) if user is not - * allowed to delete item or permission to write into collection. - * @throws WebApplicationException It can be thrown by: SQLException, when was problem with - * database reading or writting. AuthorizeException, when was - * problem with authorization to item or collection. - * IOException, when was problem with removing item. - * ContextException, when was problem with creating context of - * DSpace. - */ - @DELETE - @Path("/{collection_id}/items/{item_id}") - @Consumes( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public Response deleteCollectionItem(@PathParam("collection_id") String collectionId, - @PathParam("item_id") String itemId, - @QueryParam("userIP") String user_ip, - @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, - @Context HttpHeaders headers, @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Delete item(id=" + itemId + ") in collection(id=" + collectionId + ")."); - org.dspace.core.Context context = null; - - try { - context = createContext(); - - org.dspace.content.Collection dspaceCollection = collectionService - .findByIdOrLegacyId(context, collectionId); - org.dspace.content.Item item = itemService.findByIdOrLegacyId(context, itemId); - - - if (dspaceCollection == null) { - //throw collection not exist - log.warn("Collection(id=" + itemId + ") was not found!"); - throw new WebApplicationException(Response.Status.NOT_FOUND); - } - - if (item == null) { - //throw item not exist - log.warn("Item(id=" + itemId + ") was not found!"); - throw new WebApplicationException(Response.Status.NOT_FOUND); - } - - if (!authorizeService.authorizeActionBoolean(context, item, Constants.REMOVE) - || !authorizeService.authorizeActionBoolean(context, dspaceCollection, Constants.REMOVE)) { - //throw auth - if (context.getCurrentUser() != null) { - log.error( - "User(" + context.getCurrentUser().getEmail() + ") does not have permission to delete item!"); - } else { - log.error("User(anonymous) has not permission to delete item!"); - } - throw new WebApplicationException(Response.Status.UNAUTHORIZED); - } - - collectionService.removeItem(context, dspaceCollection, item); - collectionService.update(context, dspaceCollection); - itemService.update(context, item); - - writeStats(dspaceCollection, UsageEvent.Action.UPDATE, user_ip, user_agent, xforwardedfor, - headers, request, context); - writeStats(item, UsageEvent.Action.REMOVE, user_ip, user_agent, xforwardedfor, headers, request, context); - - context.complete(); - - } catch (ContextException e) { - processException("Could not delete item(id=" + itemId + ") in collection(id=" + collectionId - + "), ContextException. Message: " + e.getMessage(), context); - } catch (SQLException e) { - processException("Could not delete item(id=" + itemId + ") in collection(id=" + collectionId - + "), SQLException. Message: " + e, context); - } catch (AuthorizeException e) { - processException("Could not delete item(id=" + itemId + ") in collection(id=" + collectionId - + "), AuthorizeException. Message: " + e, context); - } catch (IOException e) { - processException("Could not delete item(id=" + itemId + ") in collection(id=" + collectionId - + "), IOException. Message: " + e, context); - } finally { - processFinally(context); - } - - log.info("Item(id=" + itemId + ") in collection(id=" + collectionId + ") was successfully deleted."); - return Response.ok().build(); - } - - /** - * Search for first collection with passed name. - * - * @param name Name of collection. - * @param headers If you want to access the collection as the user logged into the - * context. The value of the "rest-dspace-token" header must be set - * to the token received from the login method response. - * @return It returns null if collection was not found. Otherwise returns - * first founded collection. - * @throws WebApplicationException A general exception a servlet can throw when it encounters difficulty. - */ - @POST - @Path("/find-collection") - @Consumes( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - @Produces( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public Collection findCollectionByName(String name, @Context HttpHeaders headers) throws WebApplicationException { - log.info("Searching for first collection with name=" + name + "."); - org.dspace.core.Context context = null; - Collection collection = null; - - try { - context = createContext(); - - List dspaceCollections = collectionService.findAll(context); - //TODO, this would be more efficient with a findByName query - - for (org.dspace.content.Collection dspaceCollection : dspaceCollections) { - if (authorizeService - .authorizeActionBoolean(context, dspaceCollection, org.dspace.core.Constants.READ)) { - if (dspaceCollection.getName().equals(name)) { - collection = new Collection(dspaceCollection, servletContext, "", context, 100, 0); - break; - } - } - } - - context.complete(); - - } catch (SQLException e) { - processException( - "Something went wrong while searching for collection(name=" + name + ") from database. Message: " - + e, context); - } catch (ContextException e) { - processException( - "Something went wrong while searching for collection(name=" + name + "), ContextError. Message: " - + e.getMessage(), context); - } finally { - processFinally(context); - } - - if (collection == null) { - log.info("Collection was not found."); - } else { - log.info("Collection was found with id(" + collection.getUUID() + ")."); - } - return collection; - } - - /** - * Find collection from DSpace database. It is encapsulation of method - * org.dspace.content.Collection.find with checking if item exist and if - * user logged into context has permission to do passed action. - * - * @param context Context of actual logged user. - * @param id Id of collection in DSpace. - * @param action Constant from org.dspace.core.Constants. - * @return It returns DSpace collection. - * @throws WebApplicationException Is thrown when item with passed id is not exists and if user - * has no permission to do passed action. - */ - private org.dspace.content.Collection findCollection(org.dspace.core.Context context, String id, int action) - throws WebApplicationException { - org.dspace.content.Collection collection = null; - try { - collection = collectionService.findByIdOrLegacyId(context, id); - - if (collection == null) { - context.abort(); - log.warn("Collection(id=" + id + ") was not found!"); - throw new WebApplicationException(Response.Status.NOT_FOUND); - } else if (!authorizeService.authorizeActionBoolean(context, collection, action)) { - context.abort(); - if (context.getCurrentUser() != null) { - log.error("User(" + context.getCurrentUser().getEmail() + ") has not permission to " - + getActionString(action) + " collection!"); - } else { - log.error("User(anonymous) has not permission to " + getActionString(action) + " collection!"); - } - throw new WebApplicationException(Response.Status.UNAUTHORIZED); - } - - } catch (SQLException e) { - processException("Something get wrong while finding collection(id=" + id + "). SQLException, Message: " + e, - context); - } - return collection; - } -} diff --git a/dspace-rest/src/main/java/org/dspace/rest/CommunitiesResource.java b/dspace-rest/src/main/java/org/dspace/rest/CommunitiesResource.java deleted file mode 100644 index c3d4840910..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/CommunitiesResource.java +++ /dev/null @@ -1,1052 +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.rest; - -import static org.dspace.content.service.DSpaceObjectService.MD_COPYRIGHT_TEXT; -import static org.dspace.content.service.DSpaceObjectService.MD_INTRODUCTORY_TEXT; -import static org.dspace.content.service.DSpaceObjectService.MD_LICENSE; -import static org.dspace.content.service.DSpaceObjectService.MD_NAME; -import static org.dspace.content.service.DSpaceObjectService.MD_SHORT_DESCRIPTION; -import static org.dspace.content.service.DSpaceObjectService.MD_SIDEBAR_TEXT; - -import java.io.IOException; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.List; -import javax.servlet.http.HttpServletRequest; -import javax.ws.rs.Consumes; -import javax.ws.rs.DELETE; -import javax.ws.rs.DefaultValue; -import javax.ws.rs.GET; -import javax.ws.rs.POST; -import javax.ws.rs.PUT; -import javax.ws.rs.Path; -import javax.ws.rs.PathParam; -import javax.ws.rs.Produces; -import javax.ws.rs.QueryParam; -import javax.ws.rs.WebApplicationException; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.HttpHeaders; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; - -import org.apache.logging.log4j.Logger; -import org.dspace.authorize.AuthorizeException; -import org.dspace.authorize.factory.AuthorizeServiceFactory; -import org.dspace.authorize.service.AuthorizeService; -import org.dspace.content.factory.ContentServiceFactory; -import org.dspace.content.service.CollectionService; -import org.dspace.content.service.CommunityService; -import org.dspace.rest.common.Collection; -import org.dspace.rest.common.Community; -import org.dspace.rest.exceptions.ContextException; -import org.dspace.usage.UsageEvent; - -/** - * Class which provides CRUD methods over communities. - * - * @author Rostislav Novak (Computing and Information Centre, CTU in Prague) - */ -@Path("/communities") -public class CommunitiesResource extends Resource { - protected CommunityService communityService = ContentServiceFactory.getInstance().getCommunityService(); - protected CollectionService collectionService = ContentServiceFactory.getInstance().getCollectionService(); - protected AuthorizeService authorizeService = AuthorizeServiceFactory.getInstance().getAuthorizeService(); - - private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(CommunitiesResource.class); - - /** - * Returns community with basic properties. If you want more, use expand - * parameter or method for community collections or subcommunities. - * - * @param communityId Id of community in DSpace. - * @param expand String in which is what you want to add to returned instance - * of community. Options are: "all", "parentCommunity", - * "collections", "subCommunities" and "logo". If you want to use - * multiple options, it must be separated by commas. - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the community as the user logged into the - * context. The value of the "rest-dspace-token" header must be set - * to the token received from the login method response. - * @param request Servlet's HTTP request object. - * @return Return instance of org.dspace.rest.common.Community. - * @throws WebApplicationException Thrown if there was a problem with creating context or problem - * with database reading. Also if id of community is incorrect - * or logged user into context has no permission to read. - */ - @GET - @Path("/{community_id}") - @Produces( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public Community getCommunity(@PathParam("community_id") String communityId, @QueryParam("expand") String expand, - @QueryParam("userIP") String user_ip, @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, @Context HttpHeaders headers, - @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Reading community(id=" + communityId + ")."); - org.dspace.core.Context context = null; - Community community = null; - - try { - context = createContext(); - - org.dspace.content.Community dspaceCommunity = findCommunity(context, communityId, - org.dspace.core.Constants.READ); - writeStats(dspaceCommunity, UsageEvent.Action.VIEW, user_ip, user_agent, xforwardedfor, headers, - request, context); - - community = new Community(dspaceCommunity, servletContext, expand, context); - context.complete(); - - } catch (SQLException e) { - processException("Could not read community(id=" + communityId + "), SQLException. Message:" + e, context); - } catch (ContextException e) { - processException( - "Could not read community(id=" + communityId + "), ContextException. Message:" + e.getMessage(), - context); - } finally { - processFinally(context); - } - - - log.trace("Community(id=" + communityId + ") was successfully read."); - return community; - } - - /** - * Return all communities in DSpace. - * - * @param expand String in which is what you want to add to returned instance - * of community. Options are: "all", "parentCommunity", - * "collections", "subCommunities" and "logo". If you want to use - * multiple options, it must be separated by commas. - * @param limit Maximum communities in array. Default value is 100. - * @param offset Index from which will start array of communities. - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the community as the user logged into the - * context. The value of the "rest-dspace-token" header must be set - * to the token received from the login method response. - * @param request Servlet's HTTP request object. - * @return Return array of communities. - * @throws WebApplicationException It can be caused by creating context or while was problem - * with reading community from database(SQLException). - */ - @GET - @Produces( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public Community[] getCommunities(@QueryParam("expand") String expand, - @QueryParam("limit") @DefaultValue("100") Integer limit, - @QueryParam("offset") @DefaultValue("0") Integer offset, - @QueryParam("userIP") String user_ip, @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, @Context HttpHeaders headers, - @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Reading all communities.(offset=" + offset + " ,limit=" + limit + ")."); - org.dspace.core.Context context = null; - ArrayList communities = null; - - try { - context = createContext(); - - List dspaceCommunities = communityService.findAll(context); - communities = new ArrayList<>(); - - if (!((limit != null) && (limit >= 0) && (offset != null) && (offset >= 0))) { - log.warn("Paging was badly set, using default values."); - limit = 100; - offset = 0; - } - - for (int i = offset; (i < (offset + limit)) && i < dspaceCommunities.size(); i++) { - if (authorizeService - .authorizeActionBoolean(context, dspaceCommunities.get(i), org.dspace.core.Constants.READ)) { - Community community = new Community(dspaceCommunities.get(i), servletContext, expand, context); - writeStats(dspaceCommunities.get(i), UsageEvent.Action.VIEW, user_ip, user_agent, - xforwardedfor, headers, request, context); - communities.add(community); - } - } - - context.complete(); - } catch (SQLException e) { - processException("Could not read communities, SQLException. Message:" + e, context); - } catch (ContextException e) { - processException("Could not read communities, ContextException. Message:" + e.getMessage(), context); - } finally { - processFinally(context); - } - - log.trace("All communities successfully read."); - return communities.toArray(new Community[0]); - } - - /** - * Return all top communities in DSpace. Top communities are communities on - * the root of tree. - * - * @param expand String in which is what you want to add to returned instance - * of community. Options are: "all", "parentCommunity", - * "collections", "subCommunities" and "logo". If you want to use - * multiple options, it must be separated by commas. - * @param limit Maximum communities in array. Default value is 100. - * @param offset Index from which will start array of communities. Default - * value is 0. - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the community as the user logged into the - * context. The value of the "rest-dspace-token" header must be set - * to the token received from the login method response. - * @param request Servlet's HTTP request object. - * @return Return array of top communities. - * @throws WebApplicationException It can be caused by creating context or while was problem - * with reading community from database(SQLException). - */ - @GET - @Path("/top-communities") - @Produces( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public Community[] getTopCommunities(@QueryParam("expand") String expand, - @QueryParam("limit") @DefaultValue("20") Integer limit, - @QueryParam("offset") @DefaultValue("0") Integer offset, - @QueryParam("userIP") String user_ip, - @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, - @Context HttpHeaders headers, @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Reading all top communities.(offset=" + offset + " ,limit=" + limit + ")."); - org.dspace.core.Context context = null; - ArrayList communities = null; - - try { - context = createContext(); - - List dspaceCommunities = communityService.findAllTop(context); - communities = new ArrayList<>(); - - if (!((limit != null) && (limit >= 0) && (offset != null) && (offset >= 0))) { - log.warn("Paging was badly set, using default values."); - limit = 100; - offset = 0; - } - - for (int i = offset; (i < (offset + limit)) && i < dspaceCommunities.size(); i++) { - if (authorizeService - .authorizeActionBoolean(context, dspaceCommunities.get(i), org.dspace.core.Constants.READ)) { - Community community = new Community(dspaceCommunities.get(i), servletContext, expand, context); - writeStats(dspaceCommunities.get(i), UsageEvent.Action.VIEW, user_ip, user_agent, - xforwardedfor, headers, request, context); - communities.add(community); - } - } - - context.complete(); - } catch (SQLException e) { - processException("Could not read top communities, SQLException. Message:" + e, context); - } catch (ContextException e) { - processException("Could not read top communities, ContextException. Message:" + e.getMessage(), context); - } finally { - processFinally(context); - } - - log.trace("All top communities successfully read."); - return communities.toArray(new Community[0]); - } - - /** - * Return all collections of community. - * - * @param communityId Id of community in DSpace. - * @param expand String in which is what you want to add to returned instance - * of collection. Options are: "all", "parentCommunityList", - * "parentCommunity", "items", "license" and "logo". If you want - * to use multiple options, it must be separated by commas. - * @param limit Maximum collection in array. Default value is 100. - * @param offset Index from which will start array of collections. Default - * value is 0. - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the community as the user logged into the - * context. The value of the "rest-dspace-token" header must be set - * to the token received from the login method response. - * @param request Servlet's HTTP request object. - * @return Return array of collections of community. - * @throws WebApplicationException It can be caused by creating context or while was problem - * with reading community from database(SQLException). - */ - @GET - @Path("/{community_id}/collections") - @Produces( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public Collection[] getCommunityCollections(@PathParam("community_id") String communityId, - @QueryParam("expand") String expand, - @QueryParam("limit") @DefaultValue("100") Integer limit, - @QueryParam("offset") @DefaultValue("0") Integer offset, - @QueryParam("userIP") String user_ip, - @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, - @Context HttpHeaders headers, @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Reading community(id=" + communityId + ") collections."); - org.dspace.core.Context context = null; - ArrayList collections = null; - - try { - context = createContext(); - - org.dspace.content.Community dspaceCommunity = findCommunity(context, communityId, - org.dspace.core.Constants.READ); - writeStats(dspaceCommunity, UsageEvent.Action.VIEW, user_ip, user_agent, xforwardedfor, headers, - request, context); - - if (!((limit != null) && (limit >= 0) && (offset != null) && (offset >= 0))) { - log.warn("Pagging was badly set, using default values."); - limit = 100; - offset = 0; - } - - collections = new ArrayList<>(); - List dspaceCollections = dspaceCommunity.getCollections(); - for (int i = offset; (i < (offset + limit)) && (i < dspaceCollections.size()); i++) { - if (authorizeService - .authorizeActionBoolean(context, dspaceCollections.get(i), org.dspace.core.Constants.READ)) { - collections.add(new Collection(dspaceCollections.get(i), servletContext, expand, context, 20, 0)); - writeStats(dspaceCollections.get(i), UsageEvent.Action.VIEW, user_ip, user_agent, - xforwardedfor, headers, request, context); - } - } - - context.complete(); - } catch (SQLException e) { - processException("Could not read community(id=" + communityId + ") collections, SQLException. Message:" + e, - context); - } catch (ContextException e) { - processException( - "Could not read community(id=" + communityId + ") collections, ContextException. Message:" + e - .getMessage(), - context); - } finally { - processFinally(context); - } - - log.trace("Community(id=" + communityId + ") collections were successfully read."); - return collections.toArray(new Collection[0]); - } - - /** - * Return all subcommunities of community. - * - * @param communityId Id of community in DSpace. - * @param expand String in which is what you want to add to returned instance - * of community. Options are: "all", "parentCommunity", - * "collections", "subCommunities" and "logo". If you want to use - * multiple options, it must be separated by commas. - * @param limit Maximum communities in array. Default value is 20. - * @param offset Index from which will start array of communities. Default - * value is 0. - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the community as the user logged into the - * context. The value of the "rest-dspace-token" header must be set - * to the token received from the login method response. - * @param request Servlet's HTTP request object. - * @return Return array of subcommunities of community. - * @throws WebApplicationException It can be caused by creating context or while was problem - * with reading community from database(SQLException). - */ - @GET - @Path("/{community_id}/communities") - @Produces( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public Community[] getCommunityCommunities(@PathParam("community_id") String communityId, - @QueryParam("expand") String expand, - @QueryParam("limit") @DefaultValue("20") Integer limit, - @QueryParam("offset") @DefaultValue("0") Integer offset, - @QueryParam("userIP") String user_ip, - @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, - @Context HttpHeaders headers, @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Reading community(id=" + communityId + ") subcommunities."); - org.dspace.core.Context context = null; - ArrayList communities = null; - - try { - context = createContext(); - - org.dspace.content.Community dspaceCommunity = findCommunity(context, communityId, - org.dspace.core.Constants.READ); - writeStats(dspaceCommunity, UsageEvent.Action.VIEW, user_ip, user_agent, xforwardedfor, headers, - request, context); - - if (!((limit != null) && (limit >= 0) && (offset != null) && (offset >= 0))) { - log.warn("Pagging was badly set, using default values."); - limit = 100; - offset = 0; - } - - communities = new ArrayList<>(); - List dspaceCommunities = dspaceCommunity.getSubcommunities(); - for (int i = offset; (i < (offset + limit)) && (i < dspaceCommunities.size()); i++) { - if (authorizeService - .authorizeActionBoolean(context, dspaceCommunities.get(i), org.dspace.core.Constants.READ)) { - communities.add(new Community(dspaceCommunities.get(i), servletContext, expand, context)); - writeStats(dspaceCommunities.get(i), UsageEvent.Action.VIEW, user_ip, user_agent, - xforwardedfor, headers, request, context); - } - } - - context.complete(); - } catch (SQLException e) { - processException( - "Could not read community(id=" + communityId + ") subcommunities, SQLException. Message:" + e, - context); - } catch (ContextException e) { - processException( - "Could not read community(id=" + communityId + ") subcommunities, ContextException. Message:" - + e.getMessage(), context); - } finally { - processFinally(context); - } - - log.trace("Community(id=" + communityId + ") subcommunities were successfully read."); - return communities.toArray(new Community[0]); - } - - /** - * Create community at top level. Creating community at top level has - * permission only admin. - * - * @param community Community which will be created at top level of communities. - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the community as the user logged into the - * context. The value of the "rest-dspace-token" header must be set - * to the token received from the login method response. - * @param request Servlet's HTTP request object. - * @return Returns response with handle of community, if was all ok. - * @throws WebApplicationException It can be thrown by SQLException, AuthorizeException and - * ContextException. - */ - @POST - @Consumes( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public Community createCommunity(Community community, @QueryParam("userIP") String user_ip, - @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, - @Context HttpHeaders headers, @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Creating community at top level."); - org.dspace.core.Context context = null; - Community retCommunity = null; - - try { - context = createContext(); - if (!authorizeService.isAdmin(context)) { - context.abort(); - String user = "anonymous"; - if (context.getCurrentUser() != null) { - user = context.getCurrentUser().getEmail(); - } - log.error("User(" + user + ") has not permission to create community!"); - throw new WebApplicationException(Response.Status.UNAUTHORIZED); - } - - org.dspace.content.Community dspaceCommunity = communityService.create(null, context); - writeStats(dspaceCommunity, UsageEvent.Action.CREATE, user_ip, user_agent, xforwardedfor, - headers, request, context); - - communityService.setMetadataSingleValue(context, dspaceCommunity, - MD_NAME, community.getName(), null); - communityService.setMetadataSingleValue(context, dspaceCommunity, - MD_COPYRIGHT_TEXT, community.getCopyrightText(), null); - communityService.setMetadataSingleValue(context, dspaceCommunity, - MD_INTRODUCTORY_TEXT, community.getIntroductoryText(), null); - communityService.setMetadataSingleValue(context, dspaceCommunity, - MD_SHORT_DESCRIPTION, community.getShortDescription(), null); - communityService.setMetadataSingleValue(context, dspaceCommunity, - MD_SIDEBAR_TEXT, community.getSidebarText(), null); - communityService.update(context, dspaceCommunity); - - retCommunity = new Community(dspaceCommunity, servletContext, "", context); - context.complete(); - } catch (SQLException e) { - processException("Could not create new top community, SQLException. Message: " + e, context); - } catch (ContextException e) { - processException("Could not create new top community, ContextException. Message: " + e.getMessage(), - context); - } catch (AuthorizeException e) { - processException("Could not create new top community, AuthorizeException. Message: " + e.getMessage(), - context); - } finally { - processFinally(context); - } - - - log.info("Community at top level has been successfully created. Handle:" + retCommunity.getHandle()); - return retCommunity; - } - - /** - * Create collection in community. - * - * @param communityId Id of community in DSpace. - * @param collection Collection which will be added into community. - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the community as the user logged into the - * context. The value of the "rest-dspace-token" header must be set - * to the token received from the login method response. - * @param request Servlet's HTTP request object. - * @return Return response 200 if was everything all right. Otherwise 400 - * when id of community was incorrect or 401 if was problem with - * permission to write into collection. - * @throws WebApplicationException It is thrown when was problem with database reading or - * writing. Or problem with authorization to community. Or - * problem with creating context. - */ - @POST - @Path("/{community_id}/collections") - @Consumes( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public Collection addCommunityCollection(@PathParam("community_id") String communityId, Collection collection, - @QueryParam("userIP") String user_ip, - @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, - @Context HttpHeaders headers, @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Adding collection into community(id=" + communityId + ")."); - org.dspace.core.Context context = null; - Collection retCollection = null; - - try { - context = createContext(); - - org.dspace.content.Community dspaceCommunity = findCommunity(context, communityId, - org.dspace.core.Constants.WRITE); - writeStats(dspaceCommunity, UsageEvent.Action.UPDATE, user_ip, user_agent, xforwardedfor, - headers, request, context); - org.dspace.content.Collection dspaceCollection = collectionService.create(context, dspaceCommunity); - collectionService.setMetadataSingleValue(context, dspaceCollection, - MD_LICENSE, collection.getLicense(), null); - // dspaceCollection.setLogo(collection.getLogo()); // TODO Add this option. - collectionService.setMetadataSingleValue(context, dspaceCollection, - MD_NAME, collection.getName(), null); - collectionService.setMetadataSingleValue(context, dspaceCollection, - MD_COPYRIGHT_TEXT, collection.getCopyrightText(), null); - collectionService.setMetadataSingleValue(context, dspaceCollection, - MD_INTRODUCTORY_TEXT, collection.getIntroductoryText(), null); - collectionService.setMetadataSingleValue(context, dspaceCollection, - MD_SHORT_DESCRIPTION, collection.getShortDescription(), null); - collectionService.setMetadataSingleValue(context, dspaceCollection, - MD_SIDEBAR_TEXT, collection.getSidebarText(), null); - collectionService.update(context, dspaceCollection); - communityService.update(context, dspaceCommunity); - retCollection = new Collection(dspaceCollection, servletContext, "", context, 100, 0); - context.complete(); - - } catch (SQLException e) { - processException( - "Could not add collection into community(id=" + communityId + "), SQLException. Message:" + e, - context); - } catch (AuthorizeException e) { - processException( - "Could not add collection into community(id=" + communityId + "), AuthorizeException. Message:" + e, - context); - } catch (ContextException e) { - processException( - "Could not add collection into community(id=" + communityId + "), ContextException. Message:" - + e.getMessage(), context); - } finally { - processFinally(context); - } - - - log.info("Collection was successfully added into community(id=" + communityId + "). Collection handle=" - + retCollection.getHandle()); - return retCollection; - } - - /** - * Create subcommunity in community. - * - * @param communityId Id of community in DSpace, in which will be created - * subcommunity. - * @param community Community which will be added into community. - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the community as the user logged into the - * context. The value of the "rest-dspace-token" header must be set - * to the token received from the login method response. - * @param request Servlet's HTTP request object. - * @return Return response 200 if was everything all right. Otherwise 400 - * when id of community was incorrect or 401 if was problem with - * permission to write into collection. - * @throws WebApplicationException It is thrown when was problem with database reading or - * writing. Or problem with authorization to community. Or - * problem with creating context. - */ - @POST - @Path("/{community_id}/communities") - @Consumes( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public Community addCommunityCommunity(@PathParam("community_id") String communityId, Community community, - @QueryParam("userIP") String user_ip, - @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, - @Context HttpHeaders headers, @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Add subcommunity into community(id=" + communityId + ")."); - org.dspace.core.Context context = null; - Community retCommunity = null; - - try { - context = createContext(); - org.dspace.content.Community dspaceParentCommunity = findCommunity(context, communityId, - org.dspace.core.Constants.WRITE); - - writeStats(dspaceParentCommunity, UsageEvent.Action.UPDATE, user_ip, user_agent, xforwardedfor, - headers, request, context); - - org.dspace.content.Community dspaceCommunity = communityService - .createSubcommunity(context, dspaceParentCommunity); - communityService.setMetadataSingleValue(context, dspaceCommunity, - MD_NAME, community.getName(), null); - communityService.setMetadataSingleValue(context, dspaceCommunity, - MD_COPYRIGHT_TEXT, community.getCopyrightText(), null); - communityService.setMetadataSingleValue(context, dspaceCommunity, - MD_INTRODUCTORY_TEXT, community.getIntroductoryText(), null); - communityService.setMetadataSingleValue(context, dspaceCommunity, - MD_SHORT_DESCRIPTION, community.getShortDescription(), null); - communityService.setMetadataSingleValue(context, dspaceCommunity, - MD_SIDEBAR_TEXT, community.getSidebarText(), null); - communityService.update(context, dspaceCommunity); - communityService.update(context, dspaceParentCommunity); - - retCommunity = new Community(dspaceCommunity, servletContext, "", context); - context.complete(); - - } catch (SQLException e) { - processException( - "Could not add subcommunity into community(id=" + communityId + "), SQLException. Message:" + e, - context); - } catch (AuthorizeException e) { - processException( - "Could not add subcommunity into community(id=" + communityId + "), AuthorizeException. Message:" - + e, context); - } catch (ContextException e) { - processException( - "Could not add subcommunity into community(id=" + communityId + "), ContextException. Message:" + e, - context); - } finally { - processFinally(context); - } - - - log.info("Subcommunity was successfully added in community(id=" + communityId + ")."); - return retCommunity; - } - - /** - * Update community. Replace all information about community except: id, - * handle and expandle items. - * - * @param communityId Id of community in DSpace. - * @param community Instance of community which will replace actual community in - * DSpace. - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the community as the user logged into the - * context. The value of the "rest-dspace-token" header must be set - * to the token received from the login method response. - * @param request Servlet's HTTP request object. - * @return Response 200 if was all ok. Otherwise 400 if id was incorrect or - * 401 if logged user has no permission to delete community. - * @throws WebApplicationException Thrown if there was a problem with creating context or problem - * with database reading or writing. Or problem with writing to - * community caused by authorization. - */ - @PUT - @Path("/{community_id}") - @Consumes( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public Response updateCommunity(@PathParam("community_id") String communityId, Community community, - @QueryParam("userIP") String user_ip, @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, @Context HttpHeaders headers, - @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Updating community(id=" + communityId + ")."); - org.dspace.core.Context context = null; - - try { - context = createContext(); - - org.dspace.content.Community dspaceCommunity = findCommunity(context, communityId, - org.dspace.core.Constants.WRITE); - writeStats(dspaceCommunity, UsageEvent.Action.UPDATE, user_ip, user_agent, xforwardedfor, - headers, request, context); - - // dspaceCommunity.setLogo(arg0); // TODO Add this option. - communityService.setMetadataSingleValue(context, dspaceCommunity, - MD_NAME, community.getName(), null); - communityService.setMetadataSingleValue(context, dspaceCommunity, - MD_COPYRIGHT_TEXT, community.getCopyrightText(), null); - communityService.setMetadataSingleValue(context, dspaceCommunity, - MD_INTRODUCTORY_TEXT, community.getIntroductoryText(), null); - communityService.setMetadataSingleValue(context, dspaceCommunity, - MD_SHORT_DESCRIPTION, community.getShortDescription(), null); - communityService.setMetadataSingleValue(context, dspaceCommunity, - MD_SIDEBAR_TEXT, community.getSidebarText(), null); - communityService.update(context, dspaceCommunity); - context.complete(); - - } catch (SQLException e) { - processException("Could not update community(id=" + communityId + "), AuthorizeException. Message:" + e, - context); - } catch (ContextException e) { - processException("Could not update community(id=" + communityId + "), ContextException Message:" + e, - context); - } catch (AuthorizeException e) { - processException("Could not update community(id=" + communityId + "), AuthorizeException Message:" + e, - context); - } finally { - processFinally(context); - } - - log.info("Community(id=" + communityId + ") has been successfully updated."); - return Response.ok().build(); - } - - /** - * Delete community from DSpace. It delete it everything with community! - * - * @param communityId Id of community in DSpace. - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the community as the user logged into the - * context. The value of the "rest-dspace-token" header must be set - * to the token received from the login method response. - * @param request Servlet's HTTP request object. - * @return Return response code OK(200) if was everything all right. - * Otherwise return NOT_FOUND(404) if was id of community incorrect. - * Or (UNAUTHORIZED)401 if was problem with permission to community. - * @throws WebApplicationException Thrown if there was a problem with creating context or problem - * with database reading or deleting. Or problem with deleting - * community caused by IOException or authorization. - */ - @DELETE - @Path("/{community_id}") - public Response deleteCommunity(@PathParam("community_id") String communityId, @QueryParam("userIP") String user_ip, - @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, - @Context HttpHeaders headers, @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Deleting community(id=" + communityId + ")."); - org.dspace.core.Context context = null; - - try { - context = createContext(); - - org.dspace.content.Community community = findCommunity(context, communityId, - org.dspace.core.Constants.DELETE); - writeStats(community, UsageEvent.Action.DELETE, user_ip, user_agent, xforwardedfor, headers, - request, context); - - communityService.delete(context, community); - communityService.update(context, community); - context.complete(); - - } catch (SQLException e) { - processException("Could not delete community(id=" + communityId + "), SQLException. Message:" + e, context); - } catch (AuthorizeException e) { - processException("Could not delete community(id=" + communityId + "), AuthorizeException. Message:" + e, - context); - } catch (IOException e) { - processException("Could not delete community(id=" + communityId + "), IOException. Message:" + e, context); - } catch (ContextException e) { - processException( - "Could not delete community(id=" + communityId + "), ContextException. Message:" + e.getMessage(), - context); - } finally { - processFinally(context); - } - - - log.info("Community(id=" + communityId + ") was successfully deleted."); - return Response.status(Response.Status.OK).build(); - } - - /** - * Delete collection in community. - * - * @param communityId Id of community in DSpace. - * @param collectionId Id of collection which will be deleted. - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the community as the user logged into the - * context. The value of the "rest-dspace-token" header must be set - * to the token received from the login method response. - * @param request Servlet's HTTP request object. - * @return Return response code OK(200) if was everything all right. - * Otherwise return NOT_FOUND(404) if was id of community or - * collection incorrect. Or (UNAUTHORIZED)401 if was problem with - * permission to community or collection. - * @throws WebApplicationException Thrown if there was a problem with creating context or problem - * with database reading or deleting. Or problem with deleting - * collection caused by IOException or authorization. - */ - @DELETE - @Path("/{community_id}/collections/{collection_id}") - public Response deleteCommunityCollection(@PathParam("community_id") String communityId, - @PathParam("collection_id") String collectionId, - @QueryParam("userIP") String user_ip, - @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, - @Context HttpHeaders headers, @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Deleting collection(id=" + collectionId + ") in community(id=" + communityId + ")."); - org.dspace.core.Context context = null; - - try { - context = createContext(); - - org.dspace.content.Community community = findCommunity(context, communityId, - org.dspace.core.Constants.WRITE); - org.dspace.content.Collection collection = collectionService.findByIdOrLegacyId(context, collectionId); - - if (collection == null) { - context.abort(); - log.warn("Collection(id=" + collectionId + ") was not found!"); - throw new WebApplicationException(Response.Status.NOT_FOUND); - } else if (!authorizeService - .authorizeActionBoolean(context, collection, org.dspace.core.Constants.REMOVE)) { - context.abort(); - if (context.getCurrentUser() != null) { - log.error( - "User(" + context.getCurrentUser().getEmail() + ") has not permission to delete collection!"); - } else { - log.error("User(anonymous) has not permission to delete collection!"); - } - throw new WebApplicationException(Response.Status.UNAUTHORIZED); - } - - communityService.removeCollection(context, community, collection); - communityService.update(context, community); - collectionService.update(context, collection); - - writeStats(community, UsageEvent.Action.UPDATE, user_ip, user_agent, xforwardedfor, headers, - request, context); - writeStats(collection, UsageEvent.Action.DELETE, user_ip, user_agent, xforwardedfor, headers, - request, context); - - context.complete(); - - } catch (SQLException e) { - processException("Could not delete collection(id=" + collectionId + ") in community(id=" + communityId - + "), SQLException. Message:" + e, context); - } catch (AuthorizeException e) { - processException("Could not delete collection(id=" + collectionId + ") in community(id=" + communityId - + "), AuthorizeException. Message:" + e, context); - } catch (IOException e) { - processException("Could not delete collection(id=" + collectionId + ") in community(id=" + communityId - + "), IOException. Message:" + e, context); - } catch (ContextException e) { - processException("Could not delete collection(id=" + collectionId + ") in community(id=" + communityId - + "), ContextExcpetion. Message:" + e.getMessage(), context); - } finally { - processFinally(context); - } - - - log.info("Collection(id=" + collectionId + ") in community(id=" + communityId + ") was successfully deleted."); - return Response.status(Response.Status.OK).build(); - } - - /** - * Delete subcommunity in community. - * - * @param parentCommunityId Id of community in DSpace. - * @param subcommunityId Id of community which will be deleted. - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the community as the user logged into the - * context. The value of the "rest-dspace-token" header must be set - * to the token received from the login method response. - * @param request Servlet's HTTP request object. - * @return Return response code OK(200) if was everything all right. - * Otherwise return NOT_FOUND(404) if was id of community or - * subcommunity incorrect. Or (UNAUTHORIZED)401 if was problem with - * permission to community or subcommunity. - * @throws WebApplicationException Thrown if there was a problem with creating context or problem - * with database reading or deleting. Or problem with deleting - * subcommunity caused by IOException or authorization. - */ - @DELETE - @Path("/{community_id}/communities/{community_id2}") - public Response deleteCommunityCommunity(@PathParam("community_id") String parentCommunityId, - @PathParam("community_id2") String subcommunityId, - @QueryParam("userIP") String user_ip, - @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, - @Context HttpHeaders headers, @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Deleting community(id=" + parentCommunityId + ")."); - org.dspace.core.Context context = null; - - try { - context = createContext(); - - org.dspace.content.Community parentCommunity = findCommunity(context, parentCommunityId, - org.dspace.core.Constants.WRITE); - org.dspace.content.Community subcommunity = communityService.findByIdOrLegacyId(context, subcommunityId); - - if (subcommunity == null) { - context.abort(); - log.warn("Subcommunity(id=" + subcommunityId + ") in community(id=" + ") was not found!"); - throw new WebApplicationException(Response.Status.NOT_FOUND); - } else if (!authorizeService - .authorizeActionBoolean(context, subcommunity, org.dspace.core.Constants.REMOVE)) { - context.abort(); - if (context.getCurrentUser() != null) { - log.error( - "User(" + context.getCurrentUser().getEmail() + ") has not permission to delete community!"); - } else { - log.error("User(anonymous) has not permission to delete community!"); - } - throw new WebApplicationException(Response.Status.UNAUTHORIZED); - } - - communityService.removeSubcommunity(context, parentCommunity, subcommunity); - communityService.update(context, parentCommunity); - communityService.update(context, subcommunity); - - writeStats(parentCommunity, UsageEvent.Action.UPDATE, user_ip, user_agent, xforwardedfor, - headers, request, context); - writeStats(subcommunity, UsageEvent.Action.DELETE, user_ip, user_agent, xforwardedfor, headers, - request, context); - - context.complete(); - - } catch (SQLException e) { - processException( - "Could not delete subcommunity(id=" + subcommunityId + ") in community(id=" + parentCommunityId - + "), SQLException. Message:" + e, context); - } catch (AuthorizeException e) { - processException( - "Could not delete subcommunity(id=" + subcommunityId + ") in community(id=" + parentCommunityId - + "), AuthorizeException. Message:" + e, context); - } catch (IOException e) { - processException( - "Could not delete subcommunity(id=" + subcommunityId + ") in community(id=" + parentCommunityId - + "), IOException. Message:" + e, context); - } catch (ContextException e) { - processException( - "Could not delete subcommunity(id=" + subcommunityId + ") in community(id=" + parentCommunityId - + "), ContextException. Message:" + e.getMessage(), context); - } finally { - processFinally(context); - } - - - log.info("Subcommunity(id=" + subcommunityId + ") from community(id=" + parentCommunityId - + ") was successfully deleted."); - return Response.status(Response.Status.OK).build(); - } - - /** - * Find community from DSpace database. It is encapsulation of method - * org.dspace.content.Community.find with checking if item exist and if user - * logged into context has permission to do passed action. - * - * @param context Context of actual logged user. - * @param id Id of community in DSpace. - * @param action Constant from org.dspace.core.Constants. - * @return It returns DSpace collection. - * @throws WebApplicationException Is thrown when item with passed id is not exists and if user - * has no permission to do passed action. - */ - private org.dspace.content.Community findCommunity(org.dspace.core.Context context, String id, int action) - throws WebApplicationException { - org.dspace.content.Community community = null; - try { - community = communityService.findByIdOrLegacyId(context, id); - - if (community == null) { - context.abort(); - log.warn("Community(id=" + id + ") was not found!"); - throw new WebApplicationException(Response.Status.NOT_FOUND); - } else if (!authorizeService.authorizeActionBoolean(context, community, action)) { - context.abort(); - if (context.getCurrentUser() != null) { - log.error("User(" + context.getCurrentUser().getEmail() + ") has not permission to " - + getActionString(action) + " community!"); - } else { - log.error("User(anonymous) has not permission to " + getActionString(action) + " community!"); - } - throw new WebApplicationException(Response.Status.UNAUTHORIZED); - } - - } catch (SQLException e) { - processException("Something get wrong while finding community(id=" + id + "). SQLException, Message:" + e, - context); - } - return community; - } -} diff --git a/dspace-rest/src/main/java/org/dspace/rest/DSpaceRestApplication.java b/dspace-rest/src/main/java/org/dspace/rest/DSpaceRestApplication.java deleted file mode 100644 index baa5c8555b..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/DSpaceRestApplication.java +++ /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/ - */ -package org.dspace.rest; - -import org.glassfish.jersey.jackson.JacksonFeature; -import org.glassfish.jersey.server.ResourceConfig; - -public class DSpaceRestApplication extends ResourceConfig { - - public DSpaceRestApplication() { - register(JacksonFeature.class); - packages("org.dspace.rest"); - } -} diff --git a/dspace-rest/src/main/java/org/dspace/rest/FilteredCollectionsResource.java b/dspace-rest/src/main/java/org/dspace/rest/FilteredCollectionsResource.java deleted file mode 100644 index 133ed50d9c..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/FilteredCollectionsResource.java +++ /dev/null @@ -1,215 +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.rest; - -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.List; -import javax.servlet.ServletContext; -import javax.servlet.http.HttpServletRequest; -import javax.ws.rs.DefaultValue; -import javax.ws.rs.GET; -import javax.ws.rs.Path; -import javax.ws.rs.PathParam; -import javax.ws.rs.Produces; -import javax.ws.rs.QueryParam; -import javax.ws.rs.WebApplicationException; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.HttpHeaders; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; - -import org.apache.logging.log4j.Logger; -import org.dspace.authorize.factory.AuthorizeServiceFactory; -import org.dspace.authorize.service.AuthorizeService; -import org.dspace.content.factory.ContentServiceFactory; -import org.dspace.content.service.CollectionService; -import org.dspace.rest.common.FilteredCollection; -import org.dspace.rest.exceptions.ContextException; -import org.dspace.services.ConfigurationService; -import org.dspace.services.factory.DSpaceServicesFactory; -import org.dspace.usage.UsageEvent; - -/* - * This class provides the items within a collection evaluated against a set of Item Filters. - * - * @author Terry Brady, Georgetown University - */ -@Path("/filtered-collections") -public class FilteredCollectionsResource extends Resource { - protected AuthorizeService authorizeService = AuthorizeServiceFactory.getInstance().getAuthorizeService(); - protected CollectionService collectionService = ContentServiceFactory.getInstance().getCollectionService(); - protected ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); - private static Logger log = org.apache.logging.log4j.LogManager.getLogger(FilteredCollectionsResource.class); - - /** - * Return array of all collections in DSpace. You can add more properties - * through expand parameter. - * - * @param expand String in which is what you want to add to returned instance - * of collection. Options are: "all", "parentCommunityList", - * "parentCommunity", "topCommunity", "items", "license" and "logo". - * If you want to use multiple options, it must be separated by commas. - * @param limit Limit value for items in list in collection. Default value is - * 100. - * @param offset Offset of start index in list of items of collection. Default - * value is 0. - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param filters Comma separated list of Item Filters to use to evaluate against - * the items in a collection - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param servletContext Context of the servlet container. - * @param headers If you want to access the collections as the user logged into the - * context. The value of the "rest-dspace-token" header must be set - * to the token received from the login method response. - * @param request Servlet's HTTP request object. - * @return Return array of collection, on which has logged user permission - * to view. - * @throws WebApplicationException It is thrown when was problem with database reading - * (SQLException) or problem with creating - * context(ContextException). - */ - @GET - @Produces( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public org.dspace.rest.common.FilteredCollection[] getCollections(@QueryParam("expand") String expand, - @QueryParam("limit") @DefaultValue("100") - Integer limit, - @QueryParam("offset") @DefaultValue("0") - Integer offset, - @QueryParam("userIP") String user_ip, - @QueryParam("userAgent") String user_agent, - @QueryParam("filters") @DefaultValue("is_item") - String filters, - @QueryParam("xforwardedfor") String xforwardedfor, - @Context ServletContext servletContext, - @Context HttpHeaders headers, - @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Reading all filtered collections.(offset=" + offset + ",limit=" + limit + ")"); - org.dspace.core.Context context = null; - List collections = new ArrayList(); - - try { - context = createContext(); - - if (!((limit != null) && (limit >= 0) && (offset != null) && (offset >= 0))) { - log.warn("Paging was badly set."); - limit = 100; - offset = 0; - } - - List dspaceCollections = collectionService.findAll(context, limit, offset); - for (org.dspace.content.Collection dspaceCollection : dspaceCollections) { - if (authorizeService - .authorizeActionBoolean(context, dspaceCollection, org.dspace.core.Constants.READ)) { - FilteredCollection collection = new org.dspace.rest.common.FilteredCollection(dspaceCollection, - servletContext, - filters, expand, - context, limit, - offset); - collections.add(collection); - writeStats(dspaceCollection, UsageEvent.Action.VIEW, user_ip, user_agent, - xforwardedfor, headers, request, context); - } - } - context.complete(); - } catch (SQLException e) { - processException("Something went wrong while reading collections from database. Message: " + e, context); - } catch (ContextException e) { - processException("Something went wrong while reading collections, ContextError. Message: " + e.getMessage(), - context); - } finally { - processFinally(context); - } - - log.trace("All collections were successfully read."); - return collections.toArray(new org.dspace.rest.common.FilteredCollection[0]); - } - - /** - * Return instance of collection with passed id. You can add more properties - * through expand parameter. - * - * @param collection_id Id of collection in DSpace. - * @param expand String in which is what you want to add to returned instance - * of collection. Options are: "all", "parentCommunityList", - * "parentCommunity", "topCommunity", "items", "license" and "logo". - * If you want to use multiple options, it must be separated by commas. - * @param limit Limit value for items in list in collection. Default value is - * 100. - * @param offset Offset of start index in list of items of collection. Default - * value is 0. - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param filters Comma separated list of Item Filters to use to evaluate against - * the items in a collection - * @param headers If you want to access the collection as the user logged into the - * context. The value of the "rest-dspace-token" header must be set - * to the token received from the login method response. - * @param request Servlet's HTTP request object. - * @param servletContext Context of the servlet container. - * @return Return instance of collection. It can also return status code - * NOT_FOUND(404) if id of collection is incorrect or status code - * UNATHORIZED(401) if user has no permission to read collection. - * @throws WebApplicationException It is thrown when was problem with database reading - * (SQLException) or problem with creating - * context(ContextException). It is thrown by NOT_FOUND and - * UNATHORIZED status codes, too. - */ - @GET - @Path("/{collection_id}") - @Produces( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public org.dspace.rest.common.FilteredCollection getCollection(@PathParam("collection_id") String collection_id, - @QueryParam("expand") String expand, - @QueryParam("limit") @DefaultValue("1000") Integer - limit, - @QueryParam("offset") @DefaultValue("0") Integer - offset, - @QueryParam("userIP") String user_ip, - @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, - @QueryParam("filters") @DefaultValue("is_item") - String filters, - @Context HttpHeaders headers, - @Context HttpServletRequest request, - @Context ServletContext servletContext) { - org.dspace.core.Context context = null; - FilteredCollection retColl = new org.dspace.rest.common.FilteredCollection(); - try { - context = createContext(); - - org.dspace.content.Collection collection = collectionService.findByIdOrLegacyId(context, collection_id); - if (authorizeService.authorizeActionBoolean(context, collection, org.dspace.core.Constants.READ)) { - writeStats(collection, UsageEvent.Action.VIEW, user_ip, - user_agent, xforwardedfor, headers, request, context); - retColl = new org.dspace.rest.common.FilteredCollection( - collection, servletContext, filters, expand, context, limit, offset); - } else { - throw new WebApplicationException(Response.Status.UNAUTHORIZED); - } - context.complete(); - } catch (SQLException e) { - processException(e.getMessage(), context); - } catch (ContextException e) { - processException(String.format("Could not read collection %s. %s", collection_id, e.getMessage()), - context); - } finally { - processFinally(context); - } - return retColl; - } -} diff --git a/dspace-rest/src/main/java/org/dspace/rest/FilteredItemsResource.java b/dspace-rest/src/main/java/org/dspace/rest/FilteredItemsResource.java deleted file mode 100644 index 0f4331adc5..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/FilteredItemsResource.java +++ /dev/null @@ -1,217 +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.rest; - -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 javax.servlet.ServletContext; -import javax.servlet.http.HttpServletRequest; -import javax.ws.rs.DefaultValue; -import javax.ws.rs.GET; -import javax.ws.rs.Path; -import javax.ws.rs.Produces; -import javax.ws.rs.QueryParam; -import javax.ws.rs.WebApplicationException; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.HttpHeaders; -import javax.ws.rs.core.MediaType; - -import org.apache.logging.log4j.Logger; -import org.dspace.authorize.AuthorizeException; -import org.dspace.content.Item; -import org.dspace.content.MetadataField; -import org.dspace.content.factory.ContentServiceFactory; -import org.dspace.content.service.CollectionService; -import org.dspace.content.service.ItemService; -import org.dspace.content.service.MetadataFieldService; -import org.dspace.content.service.MetadataSchemaService; -import org.dspace.content.service.SiteService; -import org.dspace.rest.common.ItemFilter; -import org.dspace.rest.common.ItemFilterQuery; -import org.dspace.rest.exceptions.ContextException; -import org.dspace.rest.filter.ItemFilterSet; -import org.dspace.services.ConfigurationService; -import org.dspace.services.factory.DSpaceServicesFactory; -import org.dspace.usage.UsageEvent; - -/* - * This class retrieves items by a constructed metadata query evaluated against a set of Item Filters. - * - * @author Terry Brady, Georgetown University - */ -@Path("/filtered-items") -public class FilteredItemsResource extends Resource { - protected ItemService itemService = ContentServiceFactory.getInstance().getItemService(); - protected MetadataFieldService metadataFieldService = ContentServiceFactory.getInstance().getMetadataFieldService(); - protected MetadataSchemaService metadataSchemaService = ContentServiceFactory.getInstance() - .getMetadataSchemaService(); - protected CollectionService collectionService = ContentServiceFactory.getInstance().getCollectionService(); - protected SiteService siteService = ContentServiceFactory.getInstance().getSiteService(); - protected ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); - - private static Logger log = org.apache.logging.log4j.LogManager.getLogger(FilteredItemsResource.class); - - /** - * Return instance of collection with passed id. You can add more properties - * through expand parameter. - * - * @param expand String in which is what you want to add to returned instance - * of collection. Options are: "all", "parentCommunityList", - * "parentCommunity", "items", "license" and "logo". If you want - * to use multiple options, it must be separated by commas. - * @param limit Limit value for items in list in collection. Default value is - * 100. - * @param offset Offset of start index in list of items of collection. Default - * value is 0. - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param filters Comma separated list of Item Filters to use to evaluate against - * the items in a collection - * @param query_field List of metadata fields to evaluate in a metadata query. - * Each list value is used in conjunction with a query_op and query_field. - * @param query_op List of metadata operators to use in a metadata query. - * Each list value is used in conjunction with a query_field and query_field. - * @param query_val List of metadata values to evaluate in a metadata query. - * Each list value is used in conjunction with a query_value and query_op. - * @param collSel List of collections to query. - * @param headers If you want to access the collection as the user logged into the - * context. The value of the "rest-dspace-token" header must be set - * to the token received from the login method response. - * @param request Servlet's HTTP request object. - * @param servletContext Context of the servlet container. - * @return Return instance of collection. It can also return status code - * NOT_FOUND(404) if id of collection is incorrect or status code - * UNATHORIZED(401) if user has no permission to read collection. - * @throws WebApplicationException It is thrown when was problem with database reading - * (SQLException) or problem with creating - * context(ContextException). It is thrown by NOT_FOUND and - * UNATHORIZED status codes, too. - */ - @GET - @Produces( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public org.dspace.rest.common.ItemFilter getItemQuery(@QueryParam("expand") String expand, - @QueryParam("limit") @DefaultValue("100") Integer limit, - @QueryParam("offset") @DefaultValue("0") Integer offset, - @QueryParam("userIP") String user_ip, - @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, - @QueryParam("filters") @DefaultValue("is_item,all_filters") - String filters, - @QueryParam("query_field[]") @DefaultValue("dc.title") - List query_field, - @QueryParam("query_op[]") @DefaultValue("exists") - List query_op, - @QueryParam("query_val[]") @DefaultValue("") List - query_val, - @QueryParam("collSel[]") @DefaultValue("") List - collSel, - @Context HttpHeaders headers, - @Context HttpServletRequest request, - @Context ServletContext servletContext) { - org.dspace.core.Context context = null; - ItemFilterSet itemFilterSet = new ItemFilterSet(filters, true); - ItemFilter result = itemFilterSet.getAllFiltersFilter(); - try { - context = createContext(); - - int index = Math.min(query_field.size(), Math.min(query_op.size(), query_val.size())); - List itemFilterQueries = new ArrayList(); - for (int i = 0; i < index; i++) { - itemFilterQueries.add(new ItemFilterQuery(query_field.get(i), query_op.get(i), query_val.get(i))); - } - - String regexClause = configurationService.getProperty("rest.regex-clause"); - if (regexClause == null) { - regexClause = ""; - } - - List uuids = getUuidsFromStrings(collSel); - List> listFieldList = getMetadataFieldsList(context, query_field); - - Iterator childItems = itemService - .findByMetadataQuery(context, listFieldList, query_op, query_val, uuids, regexClause, offset, limit); - - int count = itemFilterSet.processSaveItems(context, servletContext, childItems, true, expand); - writeStats(siteService.findSite(context), UsageEvent.Action.VIEW, user_ip, user_agent, xforwardedfor, - headers, request, context); - result.annotateQuery(query_field, query_op, query_val); - result.setUnfilteredItemCount(count); - context.complete(); - } catch (IOException e) { - processException(e.getMessage(), context); - } catch (SQLException e) { - processException(e.getMessage(), context); - } catch (AuthorizeException e) { - processException(e.getMessage(), context); - } catch (ContextException e) { - processException("Unauthorized filtered item query. " + e.getMessage(), context); - } finally { - processFinally(context); - } - return result; - } - - private List> getMetadataFieldsList(org.dspace.core.Context context, List query_field) - throws SQLException { - List> listFieldList = new ArrayList>(); - for (String s : query_field) { - ArrayList fields = new ArrayList(); - listFieldList.add(fields); - if (s.equals("*")) { - continue; - } - String schema = ""; - String element = ""; - String qualifier = null; - String[] parts = s.split("\\."); - if (parts.length > 0) { - schema = parts[0]; - } - if (parts.length > 1) { - element = parts[1]; - } - if (parts.length > 2) { - qualifier = parts[2]; - } - - if (Item.ANY.equals(qualifier)) { - for (MetadataField mf : metadataFieldService - .findFieldsByElementNameUnqualified(context, schema, element)) { - fields.add(mf); - } - } else { - MetadataField mf = metadataFieldService.findByElement(context, schema, element, qualifier); - if (mf != null) { - fields.add(mf); - } - } - } - return listFieldList; - } - - private List getUuidsFromStrings(List collSel) { - List uuids = new ArrayList(); - for (String s : collSel) { - try { - uuids.add(UUID.fromString(s)); - } catch (IllegalArgumentException e) { - log.warn("Invalid collection UUID: " + s); - } - } - return uuids; - } - -} diff --git a/dspace-rest/src/main/java/org/dspace/rest/FiltersResource.java b/dspace-rest/src/main/java/org/dspace/rest/FiltersResource.java deleted file mode 100644 index bff755f2de..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/FiltersResource.java +++ /dev/null @@ -1,60 +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.rest; - -import javax.servlet.http.HttpServletRequest; -import javax.ws.rs.GET; -import javax.ws.rs.Path; -import javax.ws.rs.Produces; -import javax.ws.rs.QueryParam; -import javax.ws.rs.WebApplicationException; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.HttpHeaders; -import javax.ws.rs.core.MediaType; - -import org.apache.logging.log4j.Logger; -import org.dspace.rest.common.ItemFilter; - -/** - * Class which provides read methods over the metadata registry. - * - * @author Terry Brady, Georgetown University - */ -@Path("/filters") -public class FiltersResource { - private static Logger log = org.apache.logging.log4j.LogManager.getLogger(FiltersResource.class); - - /** - * Return all Use Case Item Filters in DSpace. - * - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the collection as the user logged into the - * context. The value of the "rest-dspace-token" header must be set - * to the token received from the login method response. - * @param request Servlet's HTTP request object. - * @return Return array of metadata schemas. - * @throws WebApplicationException It can be caused by creating context or while was problem - * with reading community from database(SQLException). - */ - @GET - @Produces( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public ItemFilter[] getFilters(@QueryParam("userIP") String user_ip, @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, @Context HttpHeaders headers, - @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Reading all Item Filters."); - return ItemFilter.getItemFilters(ItemFilter.ALL, false).toArray(new ItemFilter[0]); - } - -} diff --git a/dspace-rest/src/main/java/org/dspace/rest/HandleResource.java b/dspace-rest/src/main/java/org/dspace/rest/HandleResource.java deleted file mode 100644 index 51436a1c00..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/HandleResource.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.rest; - -import java.sql.SQLException; -import javax.ws.rs.GET; -import javax.ws.rs.Path; -import javax.ws.rs.PathParam; -import javax.ws.rs.Produces; -import javax.ws.rs.QueryParam; -import javax.ws.rs.WebApplicationException; -import javax.ws.rs.core.HttpHeaders; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; - -import org.apache.logging.log4j.Logger; -import org.dspace.authorize.factory.AuthorizeServiceFactory; -import org.dspace.authorize.service.AuthorizeService; -import org.dspace.content.factory.ContentServiceFactory; -import org.dspace.content.service.DSpaceObjectService; -import org.dspace.core.Constants; -import org.dspace.handle.factory.HandleServiceFactory; -import org.dspace.handle.service.HandleService; -import org.dspace.rest.common.Collection; -import org.dspace.rest.common.Community; -import org.dspace.rest.common.DSpaceObject; -import org.dspace.rest.common.Item; -import org.dspace.rest.exceptions.ContextException; - -/** - * Created with IntelliJ IDEA. - * User: peterdietz - * Date: 10/7/13 - * Time: 1:54 PM - * To change this template use File | Settings | File Templates. - */ -@Path("/handle") -public class HandleResource extends Resource { - protected HandleService handleService = HandleServiceFactory.getInstance().getHandleService(); - protected AuthorizeService authorizeService = AuthorizeServiceFactory.getInstance().getAuthorizeService(); - - private static Logger log = org.apache.logging.log4j.LogManager.getLogger(HandleResource.class); - - @GET - @Path("/{prefix}/{suffix}") - @Produces( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public org.dspace.rest.common.DSpaceObject getObject(@PathParam("prefix") String prefix, - @PathParam("suffix") String suffix, - @QueryParam("expand") String expand, - @javax.ws.rs.core.Context HttpHeaders headers) { - DSpaceObject dSpaceObject = new DSpaceObject(); - org.dspace.core.Context context = null; - - try { - context = createContext(); - - org.dspace.content.DSpaceObject dso = handleService.resolveToObject(context, prefix + "/" + suffix); - - if (dso == null) { - throw new WebApplicationException(Response.Status.NOT_FOUND); - } - DSpaceObjectService dSpaceObjectService = ContentServiceFactory.getInstance().getDSpaceObjectService(dso); - log.info("DSO Lookup by handle: [" + prefix + "] / [" + suffix + "] got result of: " + dSpaceObjectService - .getTypeText(dso) + "_" + dso.getID()); - - if (authorizeService.authorizeActionBoolean(context, dso, org.dspace.core.Constants.READ)) { - switch (dso.getType()) { - case Constants.COMMUNITY: - dSpaceObject = new Community((org.dspace.content.Community) dso, servletContext, expand, - context); - break; - case Constants.COLLECTION: - dSpaceObject = new Collection((org.dspace.content.Collection) dso, servletContext, expand, - context, null, null); - break; - case Constants.ITEM: - dSpaceObject = new Item((org.dspace.content.Item) dso, servletContext, expand, context); - break; - default: - dSpaceObject = new DSpaceObject(dso, servletContext); - break; - } - } else { - throw new WebApplicationException(Response.Status.UNAUTHORIZED); - } - - context.complete(); - - } catch (SQLException e) { - log.error(e.getMessage()); - throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR); - } catch (ContextException e) { - processException( - "Could not read handle(prefix=" + prefix + "), (suffix=" + suffix + ") ContextException. Message:" + e - .getMessage(), - context); - } finally { - processFinally(context); - } - - return dSpaceObject; - - } -} diff --git a/dspace-rest/src/main/java/org/dspace/rest/HierarchyResource.java b/dspace-rest/src/main/java/org/dspace/rest/HierarchyResource.java deleted file mode 100644 index b2ffc559b0..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/HierarchyResource.java +++ /dev/null @@ -1,140 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.rest; - -import java.io.UnsupportedEncodingException; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.List; -import javax.servlet.http.HttpServletRequest; -import javax.ws.rs.GET; -import javax.ws.rs.Path; -import javax.ws.rs.Produces; -import javax.ws.rs.QueryParam; -import javax.ws.rs.WebApplicationException; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.HttpHeaders; -import javax.ws.rs.core.MediaType; - -import org.apache.logging.log4j.Logger; -import org.dspace.authorize.factory.AuthorizeServiceFactory; -import org.dspace.authorize.service.AuthorizeService; -import org.dspace.content.Collection; -import org.dspace.content.Community; -import org.dspace.content.Site; -import org.dspace.content.factory.ContentServiceFactory; -import org.dspace.content.service.CommunityService; -import org.dspace.content.service.SiteService; -import org.dspace.rest.common.HierarchyCollection; -import org.dspace.rest.common.HierarchyCommunity; -import org.dspace.rest.common.HierarchySite; -import org.dspace.services.ConfigurationService; -import org.dspace.services.factory.DSpaceServicesFactory; - - -/* - * This class retrieves the community hierarchy in an optimized format. - * - * @author Terry Brady, Georgetown University - */ -@Path("/hierarchy") -@Produces( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) -public class HierarchyResource extends Resource { - private static Logger log = org.apache.logging.log4j.LogManager.getLogger(HierarchyResource.class); - protected SiteService siteService = ContentServiceFactory.getInstance().getSiteService(); - protected CommunityService communityService = ContentServiceFactory.getInstance().getCommunityService(); - protected AuthorizeService authorizeService = AuthorizeServiceFactory.getInstance().getAuthorizeService(); - protected ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); - - /** - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the collection as the user logged into the - * context. The value of the "rest-dspace-token" header must be set - * to the token received from the login method response. - * @param request Servlet's HTTP request object. - * @return Return instance of collection. It can also return status code - * NOT_FOUND(404) if id of collection is incorrect or status code - * @throws UnsupportedEncodingException The Character Encoding is not supported. - * @throws WebApplicationException It is thrown when was problem with database reading - * (SQLException) or problem with creating - * context(ContextException). It is thrown by NOT_FOUND and - * UNATHORIZED status codes, too. - */ - @GET - @Produces( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public HierarchySite getHierarchy( - @QueryParam("userAgent") String user_agent, @QueryParam("xforwardedfor") String xforwardedfor, - @Context HttpHeaders headers, @Context HttpServletRequest request) - throws UnsupportedEncodingException, WebApplicationException { - - org.dspace.core.Context context = null; - HierarchySite repo = new HierarchySite(); - - try { - context = createContext(); - - Site site = siteService.findSite(context); - repo.setId(site.getID().toString()); - repo.setName(site.getName()); - repo.setHandle(site.getHandle()); - List dspaceCommunities = communityService.findAllTop(context); - processCommunity(context, repo, dspaceCommunities); - } catch (Exception e) { - processException(e.getMessage(), context); - } finally { - if (context != null) { - try { - context.complete(); - } catch (SQLException e) { - log.error(e.getMessage() + " occurred while trying to close"); - } - } - } - return repo; - } - - - private void processCommunity(org.dspace.core.Context context, HierarchyCommunity parent, - List communities) throws SQLException { - if (communities == null) { - return; - } - if (communities.size() == 0) { - return; - } - List parentComms = new ArrayList(); - parent.setCommunities(parentComms); - for (Community comm : communities) { - if (!authorizeService.authorizeActionBoolean(context, comm, org.dspace.core.Constants.READ)) { - continue; - } - HierarchyCommunity mycomm = new HierarchyCommunity(comm.getID().toString(), comm.getName(), - comm.getHandle()); - parentComms.add(mycomm); - List colls = comm.getCollections(); - if (colls.size() > 0) { - List myColls = new ArrayList(); - mycomm.setCollections(myColls); - for (Collection coll : colls) { - if (!authorizeService.authorizeActionBoolean(context, coll, org.dspace.core.Constants.READ)) { - continue; - } - HierarchyCollection mycoll = new HierarchyCollection(coll.getID().toString(), coll.getName(), - coll.getHandle()); - myColls.add(mycoll); - } - } - processCommunity(context, mycomm, comm.getSubcommunities()); - } - - } -} diff --git a/dspace-rest/src/main/java/org/dspace/rest/ItemsResource.java b/dspace-rest/src/main/java/org/dspace/rest/ItemsResource.java deleted file mode 100644 index 615aacac21..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/ItemsResource.java +++ /dev/null @@ -1,1007 +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.rest; - -import java.io.IOException; -import java.io.InputStream; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Date; -import java.util.Iterator; -import java.util.List; -import javax.servlet.http.HttpServletRequest; -import javax.ws.rs.Consumes; -import javax.ws.rs.DELETE; -import javax.ws.rs.DefaultValue; -import javax.ws.rs.GET; -import javax.ws.rs.POST; -import javax.ws.rs.PUT; -import javax.ws.rs.Path; -import javax.ws.rs.PathParam; -import javax.ws.rs.Produces; -import javax.ws.rs.QueryParam; -import javax.ws.rs.WebApplicationException; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.HttpHeaders; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; -import javax.ws.rs.core.Response.Status; - -import org.apache.logging.log4j.Logger; -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.Bundle; -import org.dspace.content.factory.ContentServiceFactory; -import org.dspace.content.service.BitstreamFormatService; -import org.dspace.content.service.BitstreamService; -import org.dspace.content.service.BundleService; -import org.dspace.content.service.CollectionService; -import org.dspace.content.service.ItemService; -import org.dspace.eperson.factory.EPersonServiceFactory; -import org.dspace.eperson.service.GroupService; -import org.dspace.rest.common.Bitstream; -import org.dspace.rest.common.Item; -import org.dspace.rest.common.MetadataEntry; -import org.dspace.rest.exceptions.ContextException; -import org.dspace.usage.UsageEvent; - -/** - * Class which provide all CRUD methods over items. - * - * @author Rostislav Novak (Computing and Information Centre, CTU in Prague) - */ -// Every DSpace class used without namespace is from package org.dspace.rest.common.*. Otherwise namespace is defined. -@SuppressWarnings("deprecation") -@Path("/items") -public class ItemsResource extends Resource { - protected CollectionService collectionService = ContentServiceFactory.getInstance().getCollectionService(); - protected ItemService itemService = ContentServiceFactory.getInstance().getItemService(); - protected AuthorizeService authorizeService = AuthorizeServiceFactory.getInstance().getAuthorizeService(); - protected BitstreamService bitstreamService = ContentServiceFactory.getInstance().getBitstreamService(); - protected BitstreamFormatService bitstreamFormatService = ContentServiceFactory.getInstance() - .getBitstreamFormatService(); - protected BundleService bundleService = ContentServiceFactory.getInstance().getBundleService(); - protected ResourcePolicyService resourcePolicyService = AuthorizeServiceFactory.getInstance() - .getResourcePolicyService(); - protected GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); - - private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(ItemsResource.class); - - /** - * Return item properties without metadata and bitstreams. You can add - * additional properties by parameter expand. - * - * @param itemId Id of item in DSpace. - * @param expand String which define, what additional properties will be in - * returned item. Options are separeted by commas and are: "all", - * "metadata", "parentCollection", "parentCollectionList", - * "parentCommunityList" and "bitstreams". - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the item as the user logged into the context. - * The value of the "rest-dspace-token" header must be set with passed - * token from login method. - * @param request Servlet's HTTP request object. - * @return If user is allowed to read item, it returns item. Otherwise is - * thrown WebApplicationException with response status - * UNAUTHORIZED(401) or NOT_FOUND(404) if was id incorrect. - * @throws WebApplicationException This exception can be throw by NOT_FOUND(bad id of item), - * UNAUTHORIZED, SQLException if wasproblem with reading from - * database and ContextException, if there was problem with - * creating context of DSpace. - */ - @GET - @Path("/{item_id}") - @Produces( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public Item getItem(@PathParam("item_id") String itemId, @QueryParam("expand") String expand, - @QueryParam("userIP") String user_ip, @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, @Context HttpHeaders headers, - @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Reading item(id=" + itemId + ")."); - org.dspace.core.Context context = null; - Item item = null; - - try { - context = createContext(); - org.dspace.content.Item dspaceItem = findItem(context, itemId, org.dspace.core.Constants.READ); - - writeStats(dspaceItem, UsageEvent.Action.VIEW, user_ip, user_agent, xforwardedfor, headers, request, - context); - - item = new Item(dspaceItem, servletContext, expand, context); - context.complete(); - log.trace("Item(id=" + itemId + ") was successfully read."); - - } catch (SQLException e) { - processException("Could not read item(id=" + itemId + "), SQLException. Message: " + e, context); - } catch (ContextException e) { - processException("Could not read item(id=" + itemId + "), ContextException. Message: " + e.getMessage(), - context); - } finally { - processFinally(context); - } - - return item; - } - - /** - * It returns an array of items in DSpace. You can define how many items in - * list will be and from which index will start. Items in list are sorted by - * handle, not by id. - * - * @param expand String which define, what additional properties will be in - * returned item. Options are separeted by commas and are: "all", - * "metadata", "parentCollection", "parentCollectionList", - * "parentCommunityList" and "bitstreams". - * @param limit How many items in array will be. Default value is 100. - * @param offset On which index will array start. Default value is 0. - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the item as the user logged into the context. - * The value of the "rest-dspace-token" header must be set with passed - * token from login method. - * @param request Servlet's HTTP request object. - * @return Return array of items, on which has logged user into context - * permission. - * @throws WebApplicationException It can be thrown by SQLException, when was problem with - * reading items from database or ContextException, when was - * problem with creating context of DSpace. - */ - @GET - @Produces( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public Item[] getItems(@QueryParam("expand") String expand, @QueryParam("limit") @DefaultValue("100") Integer limit, - @QueryParam("offset") @DefaultValue("0") Integer offset, - @QueryParam("userIP") String user_ip, - @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, - @Context HttpHeaders headers, @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Reading items.(offset=" + offset + ",limit=" + limit + ")."); - org.dspace.core.Context context = null; - List items = null; - - try { - context = createContext(); - - Iterator dspaceItems = itemService.findAllUnfiltered(context); - items = new ArrayList(); - - if (!((limit != null) && (limit >= 0) && (offset != null) && (offset >= 0))) { - log.warn("Paging was badly set, using default values."); - limit = 100; - offset = 0; - } - - for (int i = 0; (dspaceItems.hasNext()) && (i < (limit + offset)); i++) { - org.dspace.content.Item dspaceItem = dspaceItems.next(); - if (i >= offset) { - if (itemService.isItemListedForUser(context, dspaceItem)) { - items.add(new Item(dspaceItem, servletContext, expand, context)); - writeStats(dspaceItem, UsageEvent.Action.VIEW, user_ip, user_agent, xforwardedfor, - headers, request, context); - } - } - } - context.complete(); - } catch (SQLException e) { - processException("Something went wrong while reading items from database. Message: " + e, context); - } catch (ContextException e) { - processException("Something went wrong while reading items, ContextException. Message: " + e.getMessage(), - context); - } finally { - processFinally(context); - } - - log.trace("Items were successfully read."); - return items.toArray(new Item[0]); - } - - /** - * Returns item metadata in list. - * - * @param itemId Id of item in DSpace. - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the item as the user logged into the context. - * The value of the "rest-dspace-token" header must be set with passed - * token from login method. - * @param request Servlet's HTTP request object. - * @return Return list of metadata fields if was everything ok. Otherwise it - * throw WebApplication exception with response code NOT_FOUND(404) - * or UNAUTHORIZED(401). - * @throws WebApplicationException It can be thrown by two exceptions: SQLException if was - * problem wtih reading item from database and ContextException, - * if was problem with creating context of DSpace. And can be - * thrown by NOT_FOUND and UNAUTHORIZED too. - */ - @GET - @Path("/{item_id}/metadata") - @Produces( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public MetadataEntry[] getItemMetadata(@PathParam("item_id") String itemId, @QueryParam("userIP") String user_ip, - @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, - @Context HttpHeaders headers, @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Reading item(id=" + itemId + ") metadata."); - org.dspace.core.Context context = null; - List metadata = null; - - try { - context = createContext(); - org.dspace.content.Item dspaceItem = findItem(context, itemId, org.dspace.core.Constants.READ); - - writeStats(dspaceItem, UsageEvent.Action.VIEW, user_ip, user_agent, xforwardedfor, headers, request, - context); - - metadata = new org.dspace.rest.common.Item(dspaceItem, servletContext, "metadata", context).getMetadata(); - context.complete(); - } catch (SQLException e) { - processException("Could not read item(id=" + itemId + "), SQLException. Message: " + e, context); - } catch (ContextException e) { - processException("Could not read item(id=" + itemId + "), ContextException. Message: " + e.getMessage(), - context); - } finally { - processFinally(context); - } - - log.trace("Item(id=" + itemId + ") metadata were successfully read."); - return metadata.toArray(new MetadataEntry[0]); - } - - /** - * Return array of bitstreams in item. It can be paged. - * - * @param itemId Id of item in DSpace. - * @param limit How many items will be in array. - * @param offset On which index will start array. - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the item as the user logged into the context. - * The value of the "rest-dspace-token" header must be set with passed - * token from login method. - * @param request Servlet's HTTP request object. - * @return Return paged array of bitstreams in item. - * @throws WebApplicationException It can be throw by NOT_FOUND, UNAUTHORIZED, SQLException if - * was problem with reading from database and ContextException - * if was problem with creating context of DSpace. - */ - @GET - @Path("/{item_id}/bitstreams") - @Produces( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public Bitstream[] getItemBitstreams(@PathParam("item_id") String itemId, - @QueryParam("limit") @DefaultValue("20") Integer limit, - @QueryParam("offset") @DefaultValue("0") Integer offset, - @QueryParam("userIP") String user_ip, - @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, - @Context HttpHeaders headers, @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Reading item(id=" + itemId + ") bitstreams.(offset=" + offset + ",limit=" + limit + ")"); - org.dspace.core.Context context = null; - List bitstreams = null; - try { - context = createContext(); - org.dspace.content.Item dspaceItem = findItem(context, itemId, org.dspace.core.Constants.READ); - - writeStats(dspaceItem, UsageEvent.Action.VIEW, user_ip, user_agent, xforwardedfor, headers, request, - context); - - List itemBitstreams = new Item(dspaceItem, servletContext, "bitstreams", context) - .getBitstreams(); - - if ((offset + limit) > (itemBitstreams.size() - offset)) { - bitstreams = itemBitstreams.subList(offset, itemBitstreams.size()); - } else { - bitstreams = itemBitstreams.subList(offset, offset + limit); - } - context.complete(); - } catch (SQLException e) { - processException("Could not read item(id=" + itemId + ") bitstreams, SQLExcpetion. Message: " + e, context); - } catch (ContextException e) { - processException( - "Could not read item(id=" + itemId + ") bitstreams, ContextException. Message: " + e.getMessage(), - context); - } finally { - processFinally(context); - } - - log.trace("Item(id=" + itemId + ") bitstreams were successfully read."); - return bitstreams.toArray(new Bitstream[0]); - } - - /** - * Adding metadata fields to item. If metadata key is in item, it will be - * added, NOT REPLACED! - * - * @param itemId Id of item in DSpace. - * @param metadata List of metadata fields, which will be added into item. - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the item as the user logged into the context. - * The value of the "rest-dspace-token" header must be set with passed - * token from login method. - * @param request Servlet's HTTP request object. - * @return It returns status code OK(200) if all was ok. UNAUTHORIZED(401) - * if user is not allowed to write to item. NOT_FOUND(404) if id of - * item is incorrect. - * @throws WebApplicationException It is throw by these exceptions: SQLException, if was problem - * with reading from database or writing to database. - * AuthorizeException, if was problem with authorization to item - * fields. ContextException, if was problem with creating - * context of DSpace. - */ - @POST - @Path("/{item_id}/metadata") - @Consumes( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public Response addItemMetadata(@PathParam("item_id") String itemId, - List metadata, - @QueryParam("userIP") String user_ip, @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, @Context HttpHeaders headers, - @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Adding metadata to item(id=" + itemId + ")."); - org.dspace.core.Context context = null; - - try { - context = createContext(); - org.dspace.content.Item dspaceItem = findItem(context, itemId, org.dspace.core.Constants.WRITE); - - writeStats(dspaceItem, UsageEvent.Action.UPDATE, user_ip, user_agent, xforwardedfor, headers, request, - context); - - for (MetadataEntry entry : metadata) { - // TODO Test with Java split - String data[] = mySplit(entry.getKey()); // Done by my split, because of java split was not function. - if ((data.length >= 2) && (data.length <= 3)) { - itemService.addMetadata(context, dspaceItem, data[0], data[1], data[2], entry.getLanguage(), - entry.getValue()); - } - } - context.complete(); - - } catch (SQLException e) { - processException("Could not write metadata to item(id=" + itemId + "), SQLException. Message: " + e, - context); - } catch (ContextException e) { - processException( - "Could not write metadata to item(id=" + itemId + "), ContextException. Message: " + e.getMessage(), - context); - } finally { - processFinally(context); - } - - log.info("Metadata to item(id=" + itemId + ") were successfully added."); - return Response.status(Status.OK).build(); - } - - /** - * Create bitstream in item. - * - * @param name Btstream name to set. - * @param description Btstream description to set. - * @param groupId ResourcePolicy group (allowed to READ). - * @param year ResourcePolicy start date year. - * @param month ResourcePolicy start date month. - * @param day ResourcePolicy start date day. - * @param itemId Id of item in DSpace. - * @param inputStream Data of bitstream in inputStream. - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the item as the user logged into the context. - * The value of the "rest-dspace-token" header must be set with passed - * token from login method. - * @param request Servlet's HTTP request object. - * @return Returns bitstream with status code OK(200). If id of item is - * invalid , it returns status code NOT_FOUND(404). If user is not - * allowed to write to item, UNAUTHORIZED(401). - * @throws WebApplicationException It is thrown by these exceptions: SQLException, when was - * problem with reading/writing from/to database. - * AuthorizeException, when was problem with authorization to - * item and add bitstream to item. IOException, when was problem - * with creating file or reading from inpustream. - * ContextException. When was problem with creating context of - * DSpace. - */ - // TODO Add option to add bitstream by URI.(for very big files) - @POST - @Path("/{item_id}/bitstreams") - public Bitstream addItemBitstream(@PathParam("item_id") String itemId, InputStream inputStream, - @QueryParam("name") String name, @QueryParam("description") String description, - @QueryParam("groupId") String groupId, @QueryParam("year") Integer year, - @QueryParam("month") Integer month, - @QueryParam("day") Integer day, @QueryParam("userIP") String user_ip, - @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, @Context HttpHeaders headers, - @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Adding bitstream to item(id=" + itemId + ")."); - org.dspace.core.Context context = null; - Bitstream bitstream = null; - - try { - context = createContext(); - org.dspace.content.Item dspaceItem = findItem(context, itemId, org.dspace.core.Constants.WRITE); - - writeStats(dspaceItem, UsageEvent.Action.UPDATE, user_ip, user_agent, xforwardedfor, headers, request, - context); - - // Is better to add bitstream to ORIGINAL bundle or to item own? - log.trace("Creating bitstream in item."); - org.dspace.content.Bundle bundle = null; - org.dspace.content.Bitstream dspaceBitstream = null; - List bundles = itemService.getBundles(dspaceItem, org.dspace.core.Constants.CONTENT_BUNDLE_NAME); - - if (bundles != null && bundles.size() != 0) { - bundle = bundles.get(0); // There should be only one bundle ORIGINAL. - } - if (bundle == null) { - log.trace("Creating bundle in item."); - dspaceBitstream = itemService.createSingleBitstream(context, inputStream, dspaceItem); - } else { - log.trace("Getting bundle from item."); - dspaceBitstream = bitstreamService.create(context, bundle, inputStream); - } - - dspaceBitstream.setSource(context, "DSpace REST API"); - - // Set bitstream name and description - if (name != null) { - if (BitstreamResource.getMimeType(name) == null) { - dspaceBitstream.setFormat(context, bitstreamFormatService.findUnknown(context)); - } else { - bitstreamService.setFormat(context, dspaceBitstream, bitstreamFormatService - .findByMIMEType(context, BitstreamResource.getMimeType(name))); - } - - dspaceBitstream.setName(context, name); - } - if (description != null) { - dspaceBitstream.setDescription(context, description); - } - - // Create policy for bitstream - if (groupId != null) { - bundles = dspaceBitstream.getBundles(); - for (Bundle dspaceBundle : bundles) { - List bitstreamsPolicies = bundleService - .getBitstreamPolicies(context, dspaceBundle); - - // Remove default bitstream policies - List policiesToRemove = new ArrayList(); - for (org.dspace.authorize.ResourcePolicy policy : bitstreamsPolicies) { - if (policy.getdSpaceObject().getID().equals(dspaceBitstream.getID())) { - policiesToRemove.add(policy); - } - } - for (org.dspace.authorize.ResourcePolicy policy : policiesToRemove) { - bitstreamsPolicies.remove(policy); - } - - org.dspace.authorize.ResourcePolicy dspacePolicy = resourcePolicyService.create(context); - dspacePolicy.setAction(org.dspace.core.Constants.READ); - dspacePolicy.setGroup(groupService.findByIdOrLegacyId(context, groupId)); - dspacePolicy.setdSpaceObject(dspaceBitstream); - if ((year != null) || (month != null) || (day != null)) { - Date date = new Date(); - if (year != null) { - date.setYear(year - 1900); - } - if (month != null) { - date.setMonth(month - 1); - } - if (day != null) { - date.setDate(day); - } - date.setHours(0); - date.setMinutes(0); - date.setSeconds(0); - dspacePolicy.setStartDate(date); - } - - resourcePolicyService.update(context, dspacePolicy); - - bitstreamService.updateLastModified(context, dspaceBitstream); - } - } - - dspaceBitstream = bitstreamService.find(context, dspaceBitstream.getID()); - bitstream = new Bitstream(dspaceBitstream, servletContext, "", context); - - context.complete(); - - } catch (SQLException e) { - processException("Could not create bitstream in item(id=" + itemId + "), SQLException. Message: " + e, - context); - } catch (AuthorizeException e) { - processException("Could not create bitstream in item(id=" + itemId + "), AuthorizeException. Message: " + e, - context); - } catch (IOException e) { - processException("Could not create bitstream in item(id=" + itemId + "), IOException Message: " + e, - context); - } catch (ContextException e) { - processException( - "Could not create bitstream in item(id=" + itemId + "), ContextException Message: " + e.getMessage(), - context); - } finally { - processFinally(context); - } - - log.info("Bitstream(id=" + bitstream.getUUID() + ") was successfully added into item(id=" + itemId + ")."); - return bitstream; - } - - /** - * Replace all metadata in item with new passed metadata. - * - * @param itemId Id of item in DSpace. - * @param metadata List of metadata fields, which will replace old metadata in - * item. - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the item as the user logged into the context. - * The value of the "rest-dspace-token" header must be set with passed - * token from login method. - * @param request Servlet's HTTP request object. - * @return It returns status code: OK(200). NOT_FOUND(404) if item was not - * found, UNAUTHORIZED(401) if user is not allowed to write to item. - * @throws WebApplicationException It is thrown by: SQLException, when was problem with database - * reading or writting, AuthorizeException when was problem with - * authorization to item and metadata fields. And - * ContextException, when was problem with creating context of - * DSpace. - */ - @PUT - @Path("/{item_id}/metadata") - @Consumes( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public Response updateItemMetadata(@PathParam("item_id") String itemId, MetadataEntry[] metadata, - @QueryParam("userIP") String user_ip, @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, @Context HttpHeaders headers, - @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Updating metadata in item(id=" + itemId + ")."); - org.dspace.core.Context context = null; - - try { - context = createContext(); - org.dspace.content.Item dspaceItem = findItem(context, itemId, org.dspace.core.Constants.WRITE); - - writeStats(dspaceItem, UsageEvent.Action.UPDATE, user_ip, user_agent, xforwardedfor, headers, request, - context); - - log.trace("Deleting original metadata from item."); - for (MetadataEntry entry : metadata) { - String data[] = mySplit(entry.getKey()); - if ((data.length >= 2) && (data.length <= 3)) { - itemService - .clearMetadata(context, dspaceItem, data[0], data[1], data[2], org.dspace.content.Item.ANY); - } - } - - log.trace("Adding new metadata to item."); - for (MetadataEntry entry : metadata) { - String data[] = mySplit(entry.getKey()); - if ((data.length >= 2) && (data.length <= 3)) { - itemService.addMetadata(context, dspaceItem, data[0], data[1], data[2], entry.getLanguage(), - entry.getValue()); - } - } - //Update the item to ensure that all the events get fired. - itemService.update(context, dspaceItem); - - context.complete(); - - } catch (SQLException e) { - processException("Could not update metadata in item(id=" + itemId + "), SQLException. Message: " + e, - context); - } catch (ContextException e) { - processException( - "Could not update metadata in item(id=" + itemId + "), ContextException. Message: " + e.getMessage(), - context); - } catch (AuthorizeException e) { - processException( - "Could not update metadata in item(id=" + itemId + "), AuthorizeException. Message: " + e.getMessage(), - context); - } finally { - processFinally(context); - } - - log.info("Metadata of item(id=" + itemId + ") were successfully updated."); - return Response.status(Status.OK).build(); - } - - /** - * Delete item from DSpace. It delete bitstreams only from item bundle. - * - * @param itemId Id of item which will be deleted. - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the item as the user logged into the context. - * The value of the "rest-dspace-token" header must be set with passed - * token from login method. - * @param request Servlet's HTTP request object. - * @return It returns status code: OK(200). NOT_FOUND(404) if item was not - * found, UNAUTHORIZED(401) if user is not allowed to delete item - * metadata. - * @throws WebApplicationException It can be thrown by: SQLException, when was problem with - * database reading. AuthorizeException, when was problem with - * authorization to item.(read and delete) IOException, when was - * problem with deleting bitstream file. ContextException, when - * was problem with creating context of DSpace. - */ - @DELETE - @Path("/{item_id}") - public Response deleteItem(@PathParam("item_id") String itemId, @QueryParam("userIP") String user_ip, - @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, - @Context HttpHeaders headers, @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Deleting item(id=" + itemId + ")."); - org.dspace.core.Context context = null; - - try { - context = createContext(); - org.dspace.content.Item dspaceItem = findItem(context, itemId, org.dspace.core.Constants.DELETE); - - writeStats(dspaceItem, UsageEvent.Action.REMOVE, user_ip, user_agent, xforwardedfor, headers, request, - context); - - log.trace("Deleting item."); - itemService.delete(context, dspaceItem); - context.complete(); - - } catch (SQLException e) { - processException("Could not delete item(id=" + itemId + "), SQLException. Message: " + e, context); - } catch (AuthorizeException e) { - processException("Could not delete item(id=" + itemId + "), AuthorizeException. Message: " + e, context); - throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR); - } catch (IOException e) { - processException("Could not delete item(id=" + itemId + "), IOException. Message: " + e, context); - } catch (ContextException e) { - processException("Could not delete item(id=" + itemId + "), ContextException. Message: " + e.getMessage(), - context); - } finally { - processFinally(context); - } - - log.info("Item(id=" + itemId + ") was successfully deleted."); - return Response.status(Status.OK).build(); - } - - /** - * Delete all item metadata. - * - * @param itemId Id of item in DSpace. - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the item as the user logged into the context. - * The value of the "rest-dspace-token" header must be set with passed - * token from login method. - * @param request Servlet's HTTP request object. - * @return It returns status code: OK(200). NOT_FOUND(404) if item was not - * found, UNAUTHORIZED(401) if user is not allowed to delete item - * metadata. - * @throws WebApplicationException Thrown by three exceptions. SQLException, when there was - * a problem reading item from database or editing metadata - * fields. AuthorizeException, when there was a problem with - * authorization to item. And ContextException, when there was a problem - * with creating a DSpace context. - */ - @DELETE - @Path("/{item_id}/metadata") - public Response deleteItemMetadata(@PathParam("item_id") String itemId, @QueryParam("userIP") String user_ip, - @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, - @Context HttpHeaders headers, @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Deleting metadata in item(id=" + itemId + ")."); - org.dspace.core.Context context = null; - - try { - context = createContext(); - org.dspace.content.Item dspaceItem = findItem(context, itemId, org.dspace.core.Constants.WRITE); - - writeStats(dspaceItem, UsageEvent.Action.UPDATE, user_ip, user_agent, xforwardedfor, headers, request, - context); - - log.trace("Deleting metadata."); - // TODO Rewrite without deprecated object. Leave there only generated metadata. - - String valueAccessioned = itemService - .getMetadataFirstValue(dspaceItem, "dc", "date", "accessioned", org.dspace.content.Item.ANY); - String valueAvailable = itemService - .getMetadataFirstValue(dspaceItem, "dc", "date", "available", org.dspace.content.Item.ANY); - String valueURI = itemService - .getMetadataFirstValue(dspaceItem, "dc", "identifier", "uri", org.dspace.content.Item.ANY); - String valueProvenance = itemService - .getMetadataFirstValue(dspaceItem, "dc", "description", "provenance", org.dspace.content.Item.ANY); - - itemService.clearMetadata(context, dspaceItem, org.dspace.content.Item.ANY, org.dspace.content.Item.ANY, - org.dspace.content.Item.ANY, - org.dspace.content.Item.ANY); - - // Add their generated metadata - itemService.addMetadata(context, dspaceItem, "dc", "date", "accessioned", null, valueAccessioned); - itemService.addMetadata(context, dspaceItem, "dc", "date", "available", null, valueAvailable); - itemService.addMetadata(context, dspaceItem, "dc", "identifier", "uri", null, valueURI); - itemService.addMetadata(context, dspaceItem, "dc", "description", "provenance", null, valueProvenance); - - context.complete(); - } catch (SQLException e) { - processException("Could not delete item(id=" + itemId + "), SQLException. Message: " + e, context); - } catch (ContextException e) { - processException("Could not delete item(id=" + itemId + "), ContextException. Message:" + e.getMessage(), - context); - } finally { - processFinally(context); - } - - log.info("Item(id=" + itemId + ") metadata were successfully deleted."); - return Response.status(Status.OK).build(); - } - - /** - * Delete bitstream from item bundle. - * - * @param itemId Id of item in DSpace. - * @param bitstreamId Id of bitstream, which will be deleted from bundle. - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the item as the user logged into the context. - * The value of the "rest-dspace-token" header must be set with passed - * token from login method. - * @param request Servlet's HTTP request object. - * @return Return status code OK(200) if is all ok. NOT_FOUND(404) if item - * or bitstream was not found. UNAUTHORIZED(401) if user is not - * allowed to delete bitstream. - * @throws WebApplicationException It is thrown, when: Was problem with edditting database, - * SQLException. Or problem with authorization to item, bundle - * or bitstream, AuthorizeException. When was problem with - * deleting file IOException. Or problem with creating context - * of DSpace, ContextException. - */ - @DELETE - @Path("/{item_id}/bitstreams/{bitstream_id}") - public Response deleteItemBitstream(@PathParam("item_id") String itemId, - @PathParam("bitstream_id") String bitstreamId, - @QueryParam("userIP") String user_ip, - @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, @Context HttpHeaders headers, - @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Deleting bitstream in item(id=" + itemId + ")."); - org.dspace.core.Context context = null; - - try { - context = createContext(); - org.dspace.content.Item item = findItem(context, itemId, org.dspace.core.Constants.WRITE); - - org.dspace.content.Bitstream bitstream = bitstreamService.findByIdOrLegacyId(context, bitstreamId); - if (bitstream == null) { - context.abort(); - log.warn("Bitstream(id=" + bitstreamId + ") was not found."); - return Response.status(Status.NOT_FOUND).build(); - } else if (!authorizeService.authorizeActionBoolean(context, bitstream, org.dspace.core.Constants.DELETE)) { - context.abort(); - log.error("User(" + context.getCurrentUser() - .getEmail() + ") is not allowed to delete bitstream(id=" + bitstreamId + - ")."); - return Response.status(Status.UNAUTHORIZED).build(); - } - - writeStats(item, UsageEvent.Action.UPDATE, user_ip, user_agent, xforwardedfor, headers, request, context); - writeStats(bitstream, UsageEvent.Action.REMOVE, user_ip, user_agent, xforwardedfor, headers, - request, context); - - log.trace("Deleting bitstream..."); - bitstreamService.delete(context, bitstream); - - context.complete(); - - } catch (SQLException e) { - processException("Could not delete bitstream(id=" + bitstreamId + "), SQLException. Message: " + e, - context); - } catch (AuthorizeException e) { - processException("Could not delete bitstream(id=" + bitstreamId + "), AuthorizeException. Message: " + e, - context); - } catch (IOException e) { - processException("Could not delete bitstream(id=" + bitstreamId + "), IOException. Message: " + e, context); - } catch (ContextException e) { - processException( - "Could not delete bitstream(id=" + bitstreamId + "), ContextException. Message:" + e.getMessage(), - context); - } finally { - processFinally(context); - } - - log.info("Bitstream(id=" + bitstreamId + ") from item(id=" + itemId + ") was successfuly deleted ."); - return Response.status(Status.OK).build(); - } - - /** - * Find items by one metadata field. - * - * @param metadataEntry Metadata field to search by. - * @param expand String which define, what additional properties will be in - * returned item. Options are separeted by commas and are: "all", - * "metadata", "parentCollection", "parentCollectionList", - * "parentCommunityList" and "bitstreams". - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the item as the user logged into context, - * header "rest-dspace-token" must be set to token value retrieved - * from the login method. - * @param request Servlet's HTTP request object. - * @return Return array of found items. - * @throws WebApplicationException Can be thrown: SQLException - problem with - * database reading. AuthorizeException - problem with - * authorization to item. IOException - problem with - * reading from metadata field. ContextException - - * problem with creating DSpace context. - */ - @POST - @Path("/find-by-metadata-field") - @Produces( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public Item[] findItemsByMetadataField(MetadataEntry metadataEntry, @QueryParam("expand") String expand, - @QueryParam("userIP") String user_ip, - @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, - @Context HttpHeaders headers, @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Looking for item with metadata(key=" + metadataEntry.getKey() + ",value=" + metadataEntry.getValue() - + ", language=" + metadataEntry.getLanguage() + ")."); - org.dspace.core.Context context = null; - - List items = new ArrayList(); - String[] metadata = mySplit(metadataEntry.getKey()); - - // Must used own style. - if ((metadata.length < 2) || (metadata.length > 3)) { - log.error("Finding failed, bad metadata key."); - throw new WebApplicationException(Response.Status.NOT_FOUND); - } - - try { - context = createContext(); - - Iterator itemIterator = itemService - .findByMetadataField(context, metadataEntry.getSchema(), - metadataEntry.getElement(), metadataEntry.getQualifier(), - metadataEntry.getValue()); - - while (itemIterator.hasNext()) { - org.dspace.content.Item dspaceItem = itemIterator.next(); - //Only return items that are available for the current user - if (itemService.isItemListedForUser(context, dspaceItem)) { - Item item = new Item(dspaceItem, servletContext, expand, context); - writeStats(dspaceItem, UsageEvent.Action.VIEW, user_ip, user_agent, xforwardedfor, headers, - request, context); - items.add(item); - } - } - - context.complete(); - } catch (SQLException e) { - processException("Something went wrong while finding item. SQLException, Message: " + e, context); - } catch (ContextException e) { - processException("Context error:" + e.getMessage(), context); - } catch (AuthorizeException e) { - processException("Authorize error:" + e.getMessage(), context); - } catch (IOException e) { - processException("IO error:" + e.getMessage(), context); - } finally { - processFinally(context); - } - - if (items.size() == 0) { - log.info("Items not found."); - } else { - log.info("Items were found."); - } - - return items.toArray(new Item[0]); - } - - /** - * Find item from DSpace database. It is encapsulation of method - * org.dspace.content.Item.find with checking if item exist and if user - * logged into context has permission to do passed action. - * - * @param context Context of actual logged user. - * @param id Id of item in DSpace. - * @param action Constant from org.dspace.core.Constants. - * @return It returns DSpace item. - * @throws WebApplicationException Is thrown when item with passed id is not exists and if user - * has no permission to do passed action. - */ - private org.dspace.content.Item findItem(org.dspace.core.Context context, String id, int action) - throws WebApplicationException { - org.dspace.content.Item item = null; - try { - item = itemService.findByIdOrLegacyId(context, id); - - if (item == null) { - context.abort(); - log.warn("Item(id=" + id + ") was not found!"); - throw new WebApplicationException(Response.Status.NOT_FOUND); - } else if (!authorizeService.authorizeActionBoolean(context, item, action)) { - context.abort(); - if (context.getCurrentUser() != null) { - log.error("User(" + context.getCurrentUser().getEmail() + ") has not permission to " - + getActionString(action) + " item!"); - } else { - log.error("User(anonymous) has not permission to " + getActionString(action) + " item!"); - } - throw new WebApplicationException(Response.Status.UNAUTHORIZED); - } - - } catch (SQLException e) { - processException("Something get wrong while finding item(id=" + id + "). SQLException, Message: " + e, - context); - } - return item; - } -} diff --git a/dspace-rest/src/main/java/org/dspace/rest/MetadataRegistryResource.java b/dspace-rest/src/main/java/org/dspace/rest/MetadataRegistryResource.java deleted file mode 100644 index 79e655e63d..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/MetadataRegistryResource.java +++ /dev/null @@ -1,738 +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.rest; - -import java.io.IOException; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.List; -import javax.servlet.http.HttpServletRequest; -import javax.ws.rs.Consumes; -import javax.ws.rs.DELETE; -import javax.ws.rs.DefaultValue; -import javax.ws.rs.GET; -import javax.ws.rs.POST; -import javax.ws.rs.PUT; -import javax.ws.rs.Path; -import javax.ws.rs.PathParam; -import javax.ws.rs.Produces; -import javax.ws.rs.QueryParam; -import javax.ws.rs.WebApplicationException; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.HttpHeaders; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; - -import org.apache.logging.log4j.Logger; -import org.dspace.authorize.AuthorizeException; -import org.dspace.authorize.factory.AuthorizeServiceFactory; -import org.dspace.authorize.service.AuthorizeService; -import org.dspace.content.NonUniqueMetadataException; -import org.dspace.content.factory.ContentServiceFactory; -import org.dspace.content.service.MetadataFieldService; -import org.dspace.content.service.MetadataSchemaService; -import org.dspace.content.service.SiteService; -import org.dspace.rest.common.MetadataField; -import org.dspace.rest.common.MetadataSchema; -import org.dspace.rest.exceptions.ContextException; -import org.dspace.usage.UsageEvent; - -/** - * Class which provides read methods over the metadata registry. - * - * @author Terry Brady, Georgetown University - * - * GET /registries/schema - Return the list of schemas in the registry - * GET /registries/schema/{schema_prefix} - Returns the specified schema - * GET /registries/schema/{schema_prefix}/metadata-fields/{element} - Returns the metadata field within a schema - * with an unqualified element name - * GET /registries/schema/{schema_prefix}/metadata-fields/{element}/{qualifier} - Returns the metadata field - * within a schema with a qualified element name - * POST /registries/schema/ - Add a schema to the schema registry - * POST /registries/schema/{schema_prefix}/metadata-fields - Add a metadata field to the specified schema - * GET /registries/metadata-fields/{field_id} - Return the specified metadata field - * PUT /registries/metadata-fields/{field_id} - Update the specified metadata field - * DELETE /registries/metadata-fields/{field_id} - Delete the specified metadata field from the metadata field registry - * DELETE /registries/schema/{schema_id} - Delete the specified schema from the schema registry - * - * Note: intentionally not providing since there is no date to update other than the namespace - * PUT /registries/schema/{schema_id} - */ -@Path("/registries") -public class MetadataRegistryResource extends Resource { - protected AuthorizeService authorizeService = AuthorizeServiceFactory.getInstance().getAuthorizeService(); - protected MetadataFieldService metadataFieldService = ContentServiceFactory.getInstance().getMetadataFieldService(); - protected MetadataSchemaService metadataSchemaService = ContentServiceFactory.getInstance() - .getMetadataSchemaService(); - protected SiteService siteService = ContentServiceFactory.getInstance().getSiteService(); - private static Logger log = org.apache.logging.log4j.LogManager.getLogger(MetadataRegistryResource.class); - - /** - * Return all metadata registry items in DSpace. - * - * @param expand String in which is what you want to add to returned instance - * of metadata schema. Options are: "all", "fields". Default value "fields". - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the metadata schema as the user logged into the - * context. The value of the "rest-dspace-token" header must be set - * to the token received from the login method response. - * @param request Servlet's HTTP request object. - * @return Return array of metadata schemas. - * @throws WebApplicationException It can be caused by creating context or while was problem - * with reading schema from database(SQLException). - */ - @GET - @Path("/schema") - @Produces( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public MetadataSchema[] getSchemas(@QueryParam("expand") @DefaultValue("fields") String expand, - @QueryParam("userIP") String user_ip, @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, @Context HttpHeaders headers, - @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Reading all metadata schemas."); - org.dspace.core.Context context = null; - ArrayList metadataSchemas = null; - - try { - context = createContext(); - - List schemas = metadataSchemaService.findAll(context); - metadataSchemas = new ArrayList(); - for (org.dspace.content.MetadataSchema schema : schemas) { - metadataSchemas.add(new MetadataSchema(schema, expand, context)); - } - - context.complete(); - } catch (SQLException e) { - processException("Could not read metadata schemas, SQLException. Message:" + e, context); - } catch (ContextException e) { - processException("Could not read metadata schemas, ContextException. Message:" + e.getMessage(), context); - } finally { - processFinally(context); - } - - log.trace("All metadata schemas successfully read."); - return metadataSchemas.toArray(new MetadataSchema[0]); - } - - /** - * Returns metadata schema with basic properties. If you want more, use expand - * parameter or method for metadata fields. - * - * @param schemaPrefix Prefix for schema in DSpace. - * @param expand String in which is what you want to add to returned instance - * of metadata schema. Options are: "all", "fields". Default value "fields". - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the metadata schema as the user logged into the - * context. The value of the "rest-dspace-token" header must be set - * to the token received from the login method response. - * @param request Servlet's HTTP request object. - * @return Return instance of org.dspace.rest.common.MetadataSchema. - * @throws WebApplicationException Thrown if there was a problem with creating context or problem - * with database reading. Also if id/prefix of schema is incorrect - * or logged user into context has no permission to read. - */ - @GET - @Path("/schema/{schema_prefix}") - @Produces( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public MetadataSchema getSchema(@PathParam("schema_prefix") String schemaPrefix, - @QueryParam("expand") @DefaultValue("fields") String expand, - @QueryParam("userIP") String user_ip, @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, @Context HttpHeaders headers, - @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Reading metadata schemas."); - org.dspace.core.Context context = null; - MetadataSchema metadataSchema = null; - - try { - context = createContext(); - - org.dspace.content.MetadataSchema schema = metadataSchemaService.find(context, schemaPrefix); - metadataSchema = new MetadataSchema(schema, expand, context); - if (schema == null) { - processException(String.format("Schema not found for index %s", schemaPrefix), context); - } - - context.complete(); - } catch (SQLException e) { - processException("Could not read metadata schema, SQLException. Message:" + e, context); - } catch (ContextException e) { - processException("Could not read metadata schema, ContextException. Message:" + e.getMessage(), context); - } finally { - processFinally(context); - } - - log.trace("Metadata schemas successfully read."); - return metadataSchema; - } - - /** - * Returns metadata field with basic properties. - * - * @param schemaPrefix Prefix for schema in DSpace. - * @param element Unqualified element name for field in the metadata registry. - * @param expand String in which is what you want to add to returned instance - * of the metadata field. Options are: "all", "parentSchema". Default value "". - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the community as the user logged into the - * context. The value of the "rest-dspace-token" header must be set - * to the token received from the login method response. - * @param request Servlet's HTTP request object. - * @return Return instance of org.dspace.rest.common.MetadataField. - * @throws WebApplicationException Thrown if there was a problem with creating context or problem - * with database reading. Also if id of field is incorrect - * or logged user into context has no permission to read. - */ - @GET - @Path("/schema/{schema_prefix}/metadata-fields/{element}") - @Produces( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public MetadataField getMetadataFieldUnqualified(@PathParam("schema_prefix") String schemaPrefix, - @PathParam("element") String element, - @QueryParam("expand") String expand, - @QueryParam("userIP") String user_ip, - @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, - @Context HttpHeaders headers, @Context HttpServletRequest request) - throws WebApplicationException { - return getMetadataFieldQualified(schemaPrefix, element, "", expand, user_ip, user_agent, xforwardedfor, headers, - request); - } - - /** - * Returns metadata field with basic properties. - * - * @param schemaPrefix Prefix for schema in DSpace. - * @param element Element name for field in the metadata registry. - * @param qualifier Element name qualifier for field in the metadata registry. - * @param expand String in which is what you want to add to returned instance - * of the metadata field. Options are: "all", "parentSchema". Default value "". - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the community as the user logged into the - * context. The value of the "rest-dspace-token" header must be set - * to the token received from the login method response. - * @param request Servlet's HTTP request object. - * @return Return instance of org.dspace.rest.common.MetadataField. - * @throws WebApplicationException Thrown if there was a problem with creating context or problem - * with database reading. Also if id of field is incorrect - * or logged user into context has no permission to read. - */ - @GET - @Path("/schema/{schema_prefix}/metadata-fields/{element}/{qualifier}") - @Produces( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public MetadataField getMetadataFieldQualified(@PathParam("schema_prefix") String schemaPrefix, - @PathParam("element") String element, - @PathParam("qualifier") @DefaultValue("") String qualifier, - @QueryParam("expand") String expand, - @QueryParam("userIP") String user_ip, - @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, - @Context HttpHeaders headers, @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Reading metadata field."); - org.dspace.core.Context context = null; - MetadataField metadataField = null; - - try { - context = createContext(); - - org.dspace.content.MetadataSchema schema = metadataSchemaService.find(context, schemaPrefix); - - if (schema == null) { - log.error(String.format("Schema not found for prefix %s", schemaPrefix)); - throw new WebApplicationException(Response.Status.NOT_FOUND); - } - - org.dspace.content.MetadataField field = metadataFieldService - .findByElement(context, schema, element, qualifier); - if (field == null) { - log.error(String.format("Field %s.%s.%s not found", schemaPrefix, element, qualifier)); - throw new WebApplicationException(Response.Status.NOT_FOUND); - } - metadataField = new MetadataField(schema, field, expand, context); - - context.complete(); - } catch (SQLException e) { - processException("Could not read metadata field, SQLException. Message:" + e, context); - } catch (ContextException e) { - processException("Could not read metadata field, ContextException. Message:" + e.getMessage(), context); - } finally { - processFinally(context); - } - - log.trace("Metadata field successfully read."); - return metadataField; - } - - /** - * Returns metadata field with basic properties. - * - * @param fieldId Id of metadata field in DSpace. - * @param expand String in which is what you want to add to returned instance - * of the metadata field. Options are: "all", "parentSchema". Default value "parentSchema". - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the community as the user logged into the - * context. The value of the "rest-dspace-token" header must be set - * to the token received from the login method response. - * @param request Servlet's HTTP request object. - * @return Return instance of org.dspace.rest.common.MetadataField. - * @throws WebApplicationException Thrown if there was a problem with creating context or problem - * with database reading. Also if id of field is incorrect - * or logged user into context has no permission to read. - */ - @GET - @Path("/metadata-fields/{field_id}") - @Produces( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public MetadataField getMetadataField(@PathParam("field_id") Integer fieldId, - @QueryParam("expand") @DefaultValue("parentSchema") String expand, - @QueryParam("userIP") String user_ip, - @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, - @Context HttpHeaders headers, @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Reading metadata field."); - org.dspace.core.Context context = null; - MetadataField metadataField = null; - - try { - context = createContext(); - - org.dspace.content.MetadataField field = metadataFieldService.find(context, fieldId); - if (field == null) { - log.error(String.format("Metadata Field %d not found", fieldId)); - throw new WebApplicationException(Response.Status.NOT_FOUND); - } - org.dspace.content.MetadataSchema schema = field.getMetadataSchema(); - if (schema == null) { - log.error(String.format("Parent Schema not found for Metadata Field %d not found", fieldId)); - throw new WebApplicationException(Response.Status.NOT_FOUND); - } - metadataField = new MetadataField(schema, field, expand, context); - - context.complete(); - } catch (SQLException e) { - processException("Could not read metadata field, SQLException. Message:" + e, context); - } catch (ContextException e) { - processException("Could not read metadata field, ContextException. Message:" + e.getMessage(), context); - } finally { - processFinally(context); - } - - log.trace("Metadata field successfully read."); - return metadataField; - } - - /** - * Create schema in the schema registry. Creating a schema is restricted to admin users. - * - * @param schema Schema that will be added to the metadata registry. - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the schema as the user logged into the - * context. The value of the "rest-dspace-token" header must be set - * to the token received from the login method response. - * @param request Servlet's HTTP request object. - * @return Return response 200 if was everything all right. Otherwise 400 - * when id of community was incorrect or 401 if was problem with - * permission to write into collection. - * Returns the schema (schemaId), if was all ok. - * @throws WebApplicationException It can be thrown by SQLException, AuthorizeException and - * ContextException. - */ - @POST - @Path("/schema") - @Consumes( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public MetadataSchema createSchema(MetadataSchema schema, @QueryParam("userIP") String user_ip, - @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, - @Context HttpHeaders headers, @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Creating a schema."); - org.dspace.core.Context context = null; - MetadataSchema retSchema = null; - - try { - context = createContext(); - - if (!authorizeService.isAdmin(context)) { - context.abort(); - String user = "anonymous"; - if (context.getCurrentUser() != null) { - user = context.getCurrentUser().getEmail(); - } - log.error("User(" + user + ") does not have permission to create a metadata schema!"); - throw new WebApplicationException(Response.Status.UNAUTHORIZED); - } - - log.debug(String.format("Admin user creating schema with namespace %s and prefix %s", schema.getNamespace(), - schema.getPrefix())); - - org.dspace.content.MetadataSchema dspaceSchema = metadataSchemaService - .create(context, schema.getPrefix(), schema.getNamespace()); - log.debug("Creating return object."); - retSchema = new MetadataSchema(dspaceSchema, "", context); - - writeStats(siteService.findSite(context), UsageEvent.Action.CREATE, user_ip, user_agent, xforwardedfor, - headers, request, context); - - context.complete(); - log.info("Schema created" + retSchema.getPrefix()); - - } catch (SQLException e) { - processException("Could not create new metadata schema, SQLException. Message: " + e, context); - } catch (ContextException e) { - processException("Could not create new metadata schema, ContextException. Message: " + e.getMessage(), - context); - } catch (AuthorizeException e) { - processException("Could not create new metadata schema, AuthorizeException. Message: " + e.getMessage(), - context); - } catch (NonUniqueMetadataException e) { - processException( - "Could not create new metadata schema, NonUniqueMetadataException. Message: " + e.getMessage(), - context); - } catch (Exception e) { - processException("Could not create new metadata schema, Exception. Class: " + e.getClass(), context); - } finally { - processFinally(context); - } - - return retSchema; - } - - - /** - * Create a new metadata field within a schema. - * Creating a metadata field is restricted to admin users. - * - * @param schemaPrefix Prefix for schema in DSpace. - * @param field Field that will be added to the metadata registry for a schema. - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the schema as the user logged into the - * context. The value of the "rest-dspace-token" header must be set - * to the token received from the login method response. - * @param request Servlet's HTTP request object. - * @return Return response 200 if was everything all right. Otherwise 400 - * when id of community was incorrect or 401 if was problem with - * permission to write into collection. - * Returns the field (with fieldId), if was all ok. - * @throws WebApplicationException It can be thrown by SQLException, AuthorizeException and - * ContextException. - */ - @POST - @Path("/schema/{schema_prefix}/metadata-fields") - @Consumes( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public MetadataField createMetadataField(@PathParam("schema_prefix") String schemaPrefix, - MetadataField field, @QueryParam("userIP") String user_ip, - @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, - @Context HttpHeaders headers, @Context HttpServletRequest request) - throws WebApplicationException { - - log.info(String.format("Creating metadataField within schema %s.", schemaPrefix)); - org.dspace.core.Context context = null; - MetadataField retField = null; - - try { - context = createContext(); - - if (!authorizeService.isAdmin(context)) { - context.abort(); - String user = "anonymous"; - if (context.getCurrentUser() != null) { - user = context.getCurrentUser().getEmail(); - } - log.error("User(" + user + ") does not have permission to create a metadata field!"); - throw new WebApplicationException(Response.Status.UNAUTHORIZED); - } - - org.dspace.content.MetadataSchema schema = metadataSchemaService.find(context, schemaPrefix); - if (schema == null) { - log.error(String.format("Schema not found for prefix %s", schemaPrefix)); - throw new WebApplicationException(Response.Status.NOT_FOUND); - } - org.dspace.content.MetadataField dspaceField = metadataFieldService - .create(context, schema, field.getElement(), field.getQualifier(), field.getDescription()); - writeStats(siteService.findSite(context), UsageEvent.Action.CREATE, user_ip, user_agent, xforwardedfor, - headers, request, context); - - retField = new MetadataField(schema, dspaceField, "", context); - context.complete(); - log.info("Metadata field created within schema" + retField.getName()); - } catch (SQLException e) { - processException("Could not create new metadata field, SQLException. Message: " + e, context); - } catch (ContextException e) { - processException("Could not create new metadata field, ContextException. Message: " + e.getMessage(), - context); - } catch (AuthorizeException e) { - processException("Could not create new metadata field, AuthorizeException. Message: " + e.getMessage(), - context); - } catch (NonUniqueMetadataException e) { - processException( - "Could not create new metadata field, NonUniqueMetadataException. Message: " + e.getMessage(), context); - } catch (Exception e) { - processException("Could not create new metadata field, Exception. Message: " + e.getMessage(), context); - } finally { - processFinally(context); - } - - return retField; - } - - //@PUT - //@Path("/schema/{schema_prefix}") - //Assumption - there are no meaningful fields to update for a schema - - /** - * Update metadata field. Replace all information about community except the id and the containing schema. - * - * @param fieldId Id of the field in the DSpace metdata registry. - * @param field Instance of the metadata field which will replace actual metadata field in - * DSpace. - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the metadata field as the user logged into the - * context. The value of the "rest-dspace-token" header must be set - * to the token received from the login method response. - * @param request Servlet's HTTP request object. - * @return Response 200 if was all ok. Otherwise 400 if was id incorrect or - * 401 if logged user has no permission to update the metadata field. - * @throws WebApplicationException Thrown if there was a problem with creating context or problem - * with database reading or writing. Or problem with writing to - * community caused by authorization. - */ - @PUT - @Path("/metadata-fields/{field_id}") - @Consumes( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public Response updateMetadataField(@PathParam("field_id") Integer fieldId, MetadataField field, - @QueryParam("userIP") String user_ip, - @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, @Context HttpHeaders headers, - @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Updating metadata field(id=" + fieldId + ")."); - org.dspace.core.Context context = null; - - try { - context = createContext(); - - org.dspace.content.MetadataField dspaceField = metadataFieldService.find(context, fieldId); - if (field == null) { - log.error(String.format("Metadata Field %d not found", fieldId)); - throw new WebApplicationException(Response.Status.NOT_FOUND); - } - - writeStats(siteService.findSite(context), UsageEvent.Action.UPDATE, user_ip, user_agent, xforwardedfor, - headers, request, context); - - dspaceField.setElement(field.getElement()); - dspaceField.setQualifier(field.getQualifier()); - dspaceField.setScopeNote(field.getDescription()); - metadataFieldService.update(context, dspaceField); - - context.complete(); - - } catch (SQLException e) { - processException("Could not update metadata field(id=" + fieldId + "), AuthorizeException. Message:" + e, - context); - } catch (ContextException e) { - processException("Could not update metadata field(id=" + fieldId + "), ContextException Message:" + e, - context); - } catch (AuthorizeException e) { - processException("Could not update metadata field(id=" + fieldId + "), AuthorizeException. Message:" + e, - context); - } catch (NonUniqueMetadataException e) { - processException( - "Could not update metadata field(id=" + fieldId + "), NonUniqueMetadataException. Message:" + e, - context); - } catch (IOException e) { - processException("Could not update metadata field(id=" + fieldId + "), IOException. Message:" + e, context); - } finally { - processFinally(context); - } - - log.info("Metadata Field(id=" + fieldId + ") has been successfully updated."); - return Response.ok().build(); - } - - /** - * Delete metadata field from the DSpace metadata registry - * - * @param fieldId Id of the metadata field in DSpace. - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the metadata field as the user logged into the - * context. The value of the "rest-dspace-token" header must be set - * to the token received from the login method response. - * @param request Servlet's HTTP request object. - * @return Return response code OK(200) if was everything all right. - * Otherwise return NOT_FOUND(404) if was id of metadata field is incorrect. - * Or (UNAUTHORIZED)401 if was problem with permission to metadata field. - * @throws WebApplicationException Thrown if there was a problem with creating context or problem - * with database reading or deleting. Or problem with deleting - * metadata field caused by IOException or authorization. - */ - @DELETE - @Path("/metadata-fields/{field_id}") - public Response deleteMetadataField(@PathParam("field_id") Integer fieldId, @QueryParam("userIP") String user_ip, - @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, - @Context HttpHeaders headers, @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Deleting metadata field(id=" + fieldId + ")."); - org.dspace.core.Context context = null; - - try { - context = createContext(); - - org.dspace.content.MetadataField dspaceField = metadataFieldService.find(context, fieldId); - if (dspaceField == null) { - log.error(String.format("Metadata Field %d not found", fieldId)); - throw new WebApplicationException(Response.Status.NOT_FOUND); - } - writeStats(siteService.findSite(context), UsageEvent.Action.DELETE, user_ip, user_agent, xforwardedfor, - headers, - request, context); - - metadataFieldService.delete(context, dspaceField); - context.complete(); - - } catch (SQLException e) { - processException("Could not delete metadata field(id=" + fieldId + "), SQLException. Message:" + e, - context); - } catch (AuthorizeException e) { - processException("Could not delete metadata field(id=" + fieldId + "), AuthorizeException. Message:" + e, - context); - } catch (ContextException e) { - processException( - "Could not delete metadata field(id=" + fieldId + "), ContextException. Message:" + e.getMessage(), - context); - } finally { - processFinally(context); - } - - - log.info("Metadata field(id=" + fieldId + ") was successfully deleted."); - return Response.status(Response.Status.OK).build(); - } - - /** - * Delete metadata schema from the DSpace metadata registry - * - * @param schemaId Id of the metadata schema in DSpace. - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the metadata schema as the user logged into the - * context. The value of the "rest-dspace-token" header must be set - * to the token received from the login method response. - * @param request Servlet's HTTP request object. - * @return Return response code OK(200) if was everything all right. - * Otherwise return NOT_FOUND(404) if was id of metadata schema is incorrect. - * Or (UNAUTHORIZED)401 if was problem with permission to metadata schema. - * @throws WebApplicationException Thrown if there was a problem with creating context or problem - * with database reading or deleting. Or problem with deleting - * metadata schema caused by IOException or authorization. - */ - @DELETE - @Path("/schema/{schema_id}") - public Response deleteSchema(@PathParam("schema_id") Integer schemaId, @QueryParam("userIP") String user_ip, - @QueryParam("userAgent") String user_agent, - @QueryParam("xforwardedfor") String xforwardedfor, - @Context HttpHeaders headers, @Context HttpServletRequest request) - throws WebApplicationException { - - log.info("Deleting metadata schema(id=" + schemaId + ")."); - org.dspace.core.Context context = null; - - try { - context = createContext(); - - org.dspace.content.MetadataSchema dspaceSchema = metadataSchemaService.find(context, schemaId); - if (dspaceSchema == null) { - log.error(String.format("Metadata Schema %d not found", schemaId)); - throw new WebApplicationException(Response.Status.NOT_FOUND); - } - writeStats(siteService.findSite(context), UsageEvent.Action.DELETE, user_ip, user_agent, xforwardedfor, - headers, - request, context); - - metadataSchemaService.delete(context, dspaceSchema); - context.complete(); - - } catch (SQLException e) { - processException("Could not delete metadata schema(id=" + schemaId + "), SQLException. Message:" + e, - context); - } catch (AuthorizeException e) { - processException("Could not delete metadata schema(id=" + schemaId + "), AuthorizeException. Message:" + e, - context); - } catch (ContextException e) { - processException( - "Could not delete metadata schema(id=" + schemaId + "), ContextException. Message:" + e.getMessage(), - context); - } finally { - processFinally(context); - } - - - log.info("Metadata schema(id=" + schemaId + ") was successfully deleted."); - return Response.status(Response.Status.OK).build(); - } - -} diff --git a/dspace-rest/src/main/java/org/dspace/rest/Resource.java b/dspace-rest/src/main/java/org/dspace/rest/Resource.java deleted file mode 100644 index 7a7624fef0..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/Resource.java +++ /dev/null @@ -1,212 +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.rest; - -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Collection; -import javax.servlet.ServletContext; -import javax.servlet.http.HttpServletRequest; -import javax.ws.rs.WebApplicationException; -import javax.ws.rs.core.HttpHeaders; -import javax.ws.rs.core.Response; - -import org.apache.logging.log4j.Logger; -import org.dspace.content.DSpaceObject; -import org.dspace.core.Context; -import org.dspace.eperson.factory.EPersonServiceFactory; -import org.dspace.rest.exceptions.ContextException; -import org.dspace.services.factory.DSpaceServicesFactory; -import org.dspace.usage.UsageEvent; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.core.context.SecurityContextHolder; - -/** - * Superclass of all resource classes in REST API. It has methods for creating - * context, write statistics, processsing exceptions, splitting a key of - * metadata, string representation of action and method for getting the logged - * in user from the token in request header. - * - * @author Rostislav Novak (Computing and Information Centre, CTU in Prague) - */ -public class Resource { - - @javax.ws.rs.core.Context - public ServletContext servletContext; - - private static Logger log = org.apache.logging.log4j.LogManager.getLogger(Resource.class); - - private static final boolean writeStatistics; - - static { - writeStatistics = DSpaceServicesFactory.getInstance().getConfigurationService() - .getBooleanProperty("rest.stats", false); - } - - /** - * Create context to work with DSpace database. It can create context - * with or without a logged in user (retrieved from SecurityContextHolder). Throws - * WebApplicationException caused by: SQLException if there was a problem - * with reading from database. Throws AuthorizeException if there was - * a problem with authorization to read from the database. Throws Exception - * if there was a problem creating context. - * - * @return Newly created context with the logged in user unless the specified user was null. - * If user is null, create the context without a logged in user. - * @throws ContextException Thrown in case of a problem creating context. Can be caused by - * SQLException error in creating context or finding the user to - * log in. Can be caused by AuthorizeException if there was a - * problem authorizing the found user. - * @throws SQLException An exception that provides information on a database access error or other errors. - */ - protected static org.dspace.core.Context createContext() throws ContextException, SQLException { - org.dspace.core.Context context = new org.dspace.core.Context(); - //context.getDBConnection().setAutoCommit(false); // Disable autocommit. - - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - if (authentication != null) { - Collection specialGroups = (Collection) authentication - .getAuthorities(); - for (SimpleGrantedAuthority grantedAuthority : specialGroups) { - context.setSpecialGroup(EPersonServiceFactory.getInstance().getGroupService() - .findByName(context, grantedAuthority.getAuthority()) - .getID()); - } - context.setCurrentUser( - EPersonServiceFactory.getInstance().getEPersonService().findByEmail(context, authentication.getName())); - } - - return context; - } - - /** - * Records a statistics event about an object used via REST API. - * - * @param dspaceObject DSpace object on which a request was performed. - * @param action Action that was performed. - * @param user_ip User's IP address. - * @param user_agent User agent string (specifies browser used and its version). - * @param xforwardedfor When accessed via a reverse proxy, the application sees the proxy's IP as the - * source of the request. The proxy may be configured to add the - * "X-Forwarded-For" HTTP header containing the original IP of the client - * so that the reverse-proxied application can get the client's IP. - * @param headers If you want to access the item as the user logged into the - * context. The header "rest-dspace-token" with the token passed - * from the login method must be set. - * @param request Servlet's HTTP request object. - * @param context Context which must be aborted. - */ - protected void writeStats(DSpaceObject dspaceObject, UsageEvent.Action action, - String user_ip, String user_agent, String xforwardedfor, HttpHeaders headers, - HttpServletRequest request, Context context) { - if (!writeStatistics) { - return; - } - - if ((user_ip == null) || (user_ip.length() == 0)) { - DSpaceServicesFactory.getInstance().getEventService() - .fireEvent(new UsageEvent(action, request, context, dspaceObject)); - } else { - DSpaceServicesFactory.getInstance().getEventService().fireEvent( - new UsageEvent(action, user_ip, user_agent, xforwardedfor, context, dspaceObject)); - } - - log.debug("fired event"); - } - - /** - * Process exception, print message to logger error stream and abort DSpace - * context. - * - * @param message Message, which will be printed to error stream. - * @param context Context which must be aborted. - * @throws WebApplicationException This exception is throw for user of REST api. - */ - protected static void processException(String message, org.dspace.core.Context context) - throws WebApplicationException { - if ((context != null) && (context.isValid())) { - context.abort(); - } - log.error(message); - throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR); - } - - /** - * Process finally statement. It will print message to logger error stream - * and abort DSpace context, if was not properly ended. - * - * @param context Context which must be aborted. - * @throws WebApplicationException This exception is thrown for user of REST API. - */ - protected void processFinally(org.dspace.core.Context context) throws WebApplicationException { - if ((context != null) && (context.isValid())) { - context.abort(); - log.error("Something get wrong. Aborting context in finally statement."); - throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR); - } - } - - /** - * Split string with regex ".". - * - * @param key String which will be splitted. - * @return String array filed with separated string. - */ - protected String[] mySplit(String key) { - ArrayList list = new ArrayList(); - int prev = 0; - for (int i = 0; i < key.length(); i++) { - if (key.charAt(i) == '.') { - list.add(key.substring(prev, i)); - prev = i + 1; - } else if (i + 1 == key.length()) { - list.add(key.substring(prev, i + 1)); - } - } - - if (list.size() == 2) { - list.add(null); - } - - return list.toArray(new String[0]); - } - - /** - * Return string representation of values - * org.dspace.core.Constants.{READ,WRITE,DELETE}. - * - * @param action Constant from org.dspace.core.Constants.* - * @return String representation. read or write or delete. - */ - protected String getActionString(int action) { - String actionStr; - switch (action) { - case org.dspace.core.Constants.READ: - actionStr = "read"; - break; - case org.dspace.core.Constants.WRITE: - actionStr = "write"; - break; - case org.dspace.core.Constants.DELETE: - actionStr = "delete"; - break; - case org.dspace.core.Constants.REMOVE: - actionStr = "remove"; - break; - case org.dspace.core.Constants.ADD: - actionStr = "add"; - break; - default: - actionStr = "(?action?)"; - break; - } - return actionStr; - } - -} diff --git a/dspace-rest/src/main/java/org/dspace/rest/RestIndex.java b/dspace-rest/src/main/java/org/dspace/rest/RestIndex.java deleted file mode 100644 index 26b1150229..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/RestIndex.java +++ /dev/null @@ -1,301 +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.rest; - -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.sql.SQLException; -import java.util.Iterator; -import javax.servlet.ServletContext; -import javax.ws.rs.Consumes; -import javax.ws.rs.GET; -import javax.ws.rs.POST; -import javax.ws.rs.Path; -import javax.ws.rs.Produces; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.HttpHeaders; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; - -import org.apache.commons.lang3.StringUtils; -import org.apache.logging.log4j.Logger; -import org.dspace.authenticate.AuthenticationMethod; -import org.dspace.authenticate.ShibAuthentication; -import org.dspace.authenticate.factory.AuthenticateServiceFactory; -import org.dspace.authenticate.service.AuthenticationService; -import org.dspace.eperson.EPerson; -import org.dspace.eperson.factory.EPersonServiceFactory; -import org.dspace.eperson.service.EPersonService; -import org.dspace.rest.common.Status; -import org.dspace.rest.exceptions.ContextException; -import org.dspace.utils.DSpace; - -/** - * Root of RESTful api. It provides login and logout. Also have method for - * printing every method which is provides by RESTful api. - * - * @author Rostislav Novak (Computing and Information Centre, CTU in Prague) - */ -@Path("/") -public class RestIndex { - protected EPersonService epersonService = EPersonServiceFactory.getInstance().getEPersonService(); - private static Logger log = org.apache.logging.log4j.LogManager.getLogger(RestIndex.class); - - /** - * Return html page with information about REST api. It contains methods all - * methods provide by REST api. - * - * @param servletContext Context of the servlet container. - * @return HTML page which has information about all methods of REST API. - */ - @GET - @Produces(MediaType.TEXT_HTML) - public String sayHtmlHello(@Context ServletContext servletContext) { - // TODO Better graphics, add arguments to all methods. (limit, offset, item and so on) - return "DSpace REST - index" + - "" - + "

    DSpace REST API (Deprecated)

    " + - "This REST API is deprecated and will be removed in v8." + - " Please use the new Server API webapp instead.
    " + - "Server path: " + servletContext.getContextPath() + - "

    Index

    " + - "
      " + - "
    • GET / - Return this page.
    • " + - "
    • GET /test - Return the string \"REST api is running\" for testing purposes.
    • " + - "
    • POST /login - Method for logging into the DSpace RESTful API. You must post the parameters \"email\"" + - " and \"password\". Example: \"email=test@dspace&password=pass\". Returns a JSESSIONID cookie which can " + - "be used for future authenticated requests.
    • " + - "
    • POST /logout - Method for logging out of the DSpace RESTful API. The request must include the " + - "\"rest-dspace-token\" token
    • header." + - "
    " + - "

    Communities

    " + - "
      " + - "
    • GET /communities - Return an array of all communities in DSpace.
    • " + - "
    • GET /communities/top-communities - Returns an array of all top-leve communities in DSpace.
    • " + - "
    • GET /communities/{communityId} - Returns a community with the specified ID.
    • " + - "
    • GET /communities/{communityId}/collections - Returns an array of collections of the specified " + - "community.
    • " + - "
    • GET /communities/{communityId}/communities - Returns an array of subcommunities of the specified " + - "community.
    • " + - "
    • POST /communities - Create a new top-level community. You must post a community.
    • " + - "
    • POST /communities/{communityId}/collections - Create a new collection in the specified community. " + - "You must post a collection.
    • " + - "
    • POST /communities/{communityId}/communities - Create a new subcommunity in the specified community. " + - "You must post a community.
    • " + - "
    • PUT /communities/{communityId} - Update the specified community.
    • " + - "
    • DELETE /communities/{communityId} - Delete the specified community.
    • " + - "
    • DELETE /communities/{communityId}/collections/{collectionId} - Delete the specified collection in " + - "the specified community.
    • " + - "
    • DELETE /communities/{communityId}/communities/{communityId2} - Delete the specified subcommunity " + - "(communityId2) in the specified community (communityId).
    • " + - "
    " + - "

    Collections

    " + - "
      " + - "
    • GET /collections - Return all DSpace collections in array.
    • " + - "
    • GET /collections/{collectionId} - Return a collection with the specified ID.
    • " + - "
    • GET /collections/{collectionId}/items - Return all items of the specified collection.
    • " + - "
    • POST /collections/{collectionId}/items - Create an item in the specified collection. You must post " + - "an item.
    • " + - "
    • POST /collections/find-collection - Find a collection by name.
    • " + - "
    • PUT /collections/{collectionId}
    • - Update the specified collection. You must post a collection." + - "
    • DELETE /collections/{collectionId} - Delete the specified collection from DSpace.
    • " + - "
    • DELETE /collections/{collectionId}/items/{itemId} - Delete the specified item (itemId) in the " + - "specified collection (collectionId).
    • " + - "
    " + - "

    Items

    " + - "
      " + - "
    • GET /items - Return a list of items.
    • " + - "
    • GET /items/{item id} - Return the specified item.
    • " + - "
    • GET /items/{item id}/metadata - Return metadata of the specified item.
    • " + - "
    • GET /items/{item id}/bitstreams - Return bitstreams of the specified item.
    • " + - "
    • POST /items/find-by-metadata-field - Find items by the specified metadata value.
    • " + - "
    • POST /items/{item id}/metadata - Add metadata to the specified item.
    • " + - "
    • POST /items/{item id}/bitstreams - Add a bitstream to the specified item.
    • " + - "
    • PUT /items/{item id}/metadata - Update metadata in the specified item.
    • " + - "
    • DELETE /items/{item id} - Delete the specified item.
    • " + - "
    • DELETE /items/{item id}/metadata - Clear metadata of the specified item.
    • " + - "
    • DELETE /items/{item id}/bitstreams/{bitstream id} - Delete the specified bitstream of the specified " + - "item.
    • " + - "
    " + - "

    Bitstreams

    " + - "
      " + - "
    • GET /bitstreams - Return all bitstreams in DSpace.
    • " + - "
    • GET /bitstreams/{bitstream id} - Return the specified bitstream.
    • " + - "
    • GET /bitstreams/{bitstream id}/policy - Return policies of the specified bitstream.
    • " + - "
    • GET /bitstreams/{bitstream id}/retrieve - Return the contents of the specified bitstream.
    • " + - "
    • POST /bitstreams/{bitstream id}/policy - Add a policy to the specified bitstream.
    • " + - "
    • PUT /bitstreams/{bitstream id}/data - Update the contents of the specified bitstream.
    • " + - "
    • PUT /bitstreams/{bitstream id} - Update metadata of the specified bitstream.
    • " + - "
    • DELETE /bitstreams/{bitstream id} - Delete the specified bitstream from DSpace.
    • " + - "
    • DELETE /bitstreams/{bitstream id}/policy/{policy_id} - Delete the specified bitstream policy.
    • " + - "
    " + - "

    Hierarchy

    " + - "
      " + - "
    • GET /hierarchy - Return hierarchy of communities and collections in tree form. Each object is " + - "minimally populated (name, handle, id) for efficient retrieval.
    • " + - "
    " + - "

    Metadata and Schema Registry

    " + - "
      " + - "
    • GET /registries/schema - Return the list of metadata schemas in the registry
    • " + - "
    • GET /registries/schema/{schema_prefix} - Returns the specified metadata schema
    • " + - "
    • GET /registries/schema/{schema_prefix}/metadata-fields/{element} - Returns the metadata field within" + - " a schema with an unqualified element name
    • " + - "
    • GET /registries/schema/{schema_prefix}/metadata-fields/{element}/{qualifier} - Returns the metadata " + - "field within a schema with a qualified element name
    • " + - "
    • POST /registries/schema/ - Add a schema to the schema registry
    • " + - "
    • POST /registries/schema/{schema_prefix}/metadata-fields - Add a metadata field to the specified " + - "schema
    • " + - "
    • GET /registries/metadata-fields/{field_id} - Return the specified metadata field
    • " + - "
    • PUT /registries/metadata-fields/{field_id} - Update the specified metadata field
    • " + - "
    • DELETE /registries/metadata-fields/{field_id} - Delete the specified metadata field from the " + - "metadata field registry
    • " + - "
    • DELETE /registries/schema/{schema_id} - Delete the specified schema from the schema registry
    • " + - "
    " + - "

    Query/Reporting Tools

    " + - "
      " + - "
    • GET /reports - Return a list of report tools built on the rest api
    • " + - "
    • GET /reports/{nickname} - Return a redirect to a specific report
    • " + - "
    • GET /filters - Return a list of use case filters available for quality control reporting
    • " + - "
    • GET /filtered-collections - Return collections and item counts based on pre-defined filters
    • " + - "
    • GET /filtered-collections/{collection_id} - Return items and item counts for a collection based on " + - "pre-defined filters
    • " + - "
    • GET /filtered-items - Retrieve a set of items based on a metadata query and a set of filters
    • " + - "
    " + - " "; - } - - /** - * Method only for testing whether the REST API is running. - * - * @return String "REST api is running." - */ - @GET - @Path("/test") - public String test() { - return "REST api is running."; - } - - /** - * Method to login a user into REST API. - * - * @return Returns response code OK and a token. Otherwise returns response - * code FORBIDDEN(403). - */ - @POST - @Path("/login") - @Consumes( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public Response login() { - //If you can get here, you are authenticated, the actual login is handled by spring security - return Response.ok().build(); - } - - @GET - @Path("/shibboleth-login") - @Consumes( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public Response shibbolethLogin() { - //If you can get here, you are authenticated, the actual login is handled by spring security - return Response.ok().build(); - } - - @GET - @Path("/login-shibboleth") - @Consumes( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public Response shibbolethLoginEndPoint() { - org.dspace.core.Context context = null; - try { - context = Resource.createContext(); - AuthenticationService authenticationService = AuthenticateServiceFactory.getInstance() - .getAuthenticationService(); - Iterator authenticationMethodIterator = authenticationService - .authenticationMethodIterator(); - while (authenticationMethodIterator.hasNext()) { - AuthenticationMethod authenticationMethod = authenticationMethodIterator.next(); - if (authenticationMethod instanceof ShibAuthentication) { - //TODO: Perhaps look for a better way of handling this ? - org.dspace.services.model.Request currentRequest = new DSpace().getRequestService() - .getCurrentRequest(); - String loginPageURL = authenticationMethod - .loginPageURL(context, currentRequest.getHttpServletRequest(), - currentRequest.getHttpServletResponse()); - if (StringUtils.isNotBlank(loginPageURL)) { - currentRequest.getHttpServletResponse().sendRedirect(loginPageURL); - } - } - } - context.abort(); - } catch (ContextException | SQLException | IOException e) { - Resource.processException("Shibboleth endpoint error: " + e.getMessage(), context); - } finally { - if (context != null && context.isValid()) { - context.abort(); - } - - } - return Response.ok().build(); - } - - /** - * Method to logout a user from DSpace REST API. Removes the token and user from - * TokenHolder. - * - * @param headers Request header which contains the header named - * "rest-dspace-token" containing the token as value. - * @return Return response OK, otherwise BAD_REQUEST, if there was a problem with - * logout or the token is incorrect. - */ - @POST - @Path("/logout") - @Consumes( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public Response logout(@Context HttpHeaders headers) { - //If you can get here, you are logged out, this actual logout is handled by spring security - return Response.ok().build(); - } - - /** - * Method to check current status of the service and logged in user. - * - * okay: true | false - * authenticated: true | false - * epersonEMAIL: user@example.com - * epersonNAME: John Doe - * - * @param headers Request header which contains the header named - * "rest-dspace-token" containing the token as value. - * @return status the Status object with information about REST API - * @throws UnsupportedEncodingException The Character Encoding is not supported. - */ - @GET - @Path("/status") - @Consumes( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public Status status(@Context HttpHeaders headers) - throws UnsupportedEncodingException { - org.dspace.core.Context context = null; - - try { - context = Resource.createContext(); - EPerson ePerson = context.getCurrentUser(); - - if (ePerson != null) { - //DB EPerson needed since token won't have full info, need context - EPerson dbEPerson = epersonService.findByEmail(context, ePerson.getEmail()); - - Status status = new Status(dbEPerson.getEmail(), dbEPerson.getFullName()); - return status; - } - } catch (ContextException e) { - Resource.processException("Status context error: " + e.getMessage(), context); - } catch (SQLException e) { - Resource.processException("Status eperson db lookup error: " + e.getMessage(), context); - } finally { - context.abort(); - } - - //fallback status, unauth - return new Status(); - } -} diff --git a/dspace-rest/src/main/java/org/dspace/rest/RestReports.java b/dspace-rest/src/main/java/org/dspace/rest/RestReports.java deleted file mode 100644 index 4af556b6f8..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/RestReports.java +++ /dev/null @@ -1,86 +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.rest; - -import java.net.URI; -import java.util.ArrayList; -import java.util.List; -import javax.ws.rs.GET; -import javax.ws.rs.Path; -import javax.ws.rs.PathParam; -import javax.ws.rs.Produces; -import javax.ws.rs.WebApplicationException; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; -import javax.ws.rs.core.UriInfo; - -import org.apache.logging.log4j.Logger; -import org.dspace.rest.common.Report; -import org.dspace.services.ConfigurationService; -import org.dspace.services.factory.DSpaceServicesFactory; - - -/** - * Root of RESTful api. It provides login and logout. Also have method for - * printing every method which is provides by RESTful api. - * - * @author Terry Brady, Georgetown University - */ -@Path("/reports") -public class RestReports { - private static Logger log = org.apache.logging.log4j.LogManager.getLogger(RestReports.class); - - protected ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); - public static final String REST_RPT_URL = "rest.report-url."; - - /** - * Return html page with information about REST api. It contains methods all - * methods provide by REST api. - * - * @return HTML page which has information about all methods of REST api. - */ - @GET - @Produces(MediaType.APPLICATION_XML) - public Report[] reportIndex() - throws WebApplicationException { - ArrayList reports = new ArrayList(); - List propNames = configurationService.getPropertyKeys("rest"); - for (String propName : propNames) { - if (propName.startsWith(REST_RPT_URL)) { - String nickname = propName.substring(REST_RPT_URL.length()); - String url = configurationService.getProperty(propName); - reports.add(new Report(nickname, url)); - } - } - return reports.toArray(new Report[0]); - } - - @Path("/{report_nickname}") - @GET - public Response customReport(@PathParam("report_nickname") String report_nickname, @Context UriInfo uriInfo) - throws WebApplicationException { - URI uri = null; - if (!report_nickname.isEmpty()) { - log.info(String.format("Seeking report %s", report_nickname)); - String url = configurationService.getProperty(REST_RPT_URL + report_nickname); - - log.info(String.format("URL for report %s found: [%s]", report_nickname, url)); - if (!url.isEmpty()) { - uri = uriInfo.getBaseUriBuilder().path(url).build(""); - log.info(String.format("URI for report %s", uri)); - } - } - - if (uri != null) { - return Response.temporaryRedirect(uri).build(); - } - - return Response.noContent().build(); - } -} diff --git a/dspace-rest/src/main/java/org/dspace/rest/authentication/DSpaceAuthenticationProvider.java b/dspace-rest/src/main/java/org/dspace/rest/authentication/DSpaceAuthenticationProvider.java deleted file mode 100644 index eac4c40111..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/authentication/DSpaceAuthenticationProvider.java +++ /dev/null @@ -1,129 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.rest.authentication; - -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.List; -import javax.servlet.http.HttpServletRequest; - -import org.apache.commons.lang3.StringUtils; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.dspace.authenticate.AuthenticationMethod; -import org.dspace.authenticate.factory.AuthenticateServiceFactory; -import org.dspace.authenticate.service.AuthenticationService; -import org.dspace.core.Context; -import org.dspace.core.LogHelper; -import org.dspace.eperson.EPerson; -import org.dspace.eperson.Group; -import org.dspace.utils.DSpace; -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.authentication.BadCredentialsException; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.core.authority.SimpleGrantedAuthority; - -/** - * The core authentication & authorization provider, this provider is called when logging in & will process - * - * @author Roeland Dillen (roeland at atmire dot com) - * @author kevinvandevelde at atmire.com - * - * FIXME This provider handles both the authorization as well as the authentication, - * due to the way that the DSpace authentication is implemented there is currently no other way to do this. - */ -public class DSpaceAuthenticationProvider implements AuthenticationProvider { - - private static final Logger log = LogManager.getLogger(); - - protected AuthenticationService authenticationService = AuthenticateServiceFactory.getInstance() - .getAuthenticationService(); - - @Override - public Authentication authenticate(Authentication authentication) throws AuthenticationException { - Context context = null; - - try { - context = new Context(); - String name = authentication.getName(); - String password = authentication.getCredentials().toString(); - HttpServletRequest httpServletRequest = new DSpace().getRequestService().getCurrentRequest() - .getHttpServletRequest(); - List grantedAuthorities = new ArrayList<>(); - - - int implicitStatus = authenticationService - .authenticateImplicit(context, null, null, null, httpServletRequest); - - if (implicitStatus == AuthenticationMethod.SUCCESS) { - log.info(LogHelper.getHeader(context, "login", "type=implicit")); - addSpecialGroupsToGrantedAuthorityList(context, httpServletRequest, grantedAuthorities); - return createAuthenticationToken(password, context, grantedAuthorities); - - } else { - int authenticateResult = authenticationService - .authenticate(context, name, password, null, httpServletRequest); - if (AuthenticationMethod.SUCCESS == authenticateResult) { - addSpecialGroupsToGrantedAuthorityList(context, httpServletRequest, grantedAuthorities); - - log.info(LogHelper.getHeader(context, "login", "type=explicit")); - - return createAuthenticationToken(password, context, grantedAuthorities); - - } else { - log.info(LogHelper.getHeader(context, "failed_login", - "email=" + name + ", result=" + authenticateResult)); - throw new BadCredentialsException("Login failed"); - } - } - } catch (BadCredentialsException e) { - throw e; - } catch (Exception e) { - log.error("Error while authenticating in the rest api", e); - } finally { - if (context != null && context.isValid()) { - try { - context.complete(); - } catch (SQLException e) { - log.error(e.getMessage() + " occurred while trying to close", e); - } - } - } - - return null; - } - - protected void addSpecialGroupsToGrantedAuthorityList(Context context, HttpServletRequest httpServletRequest, - List grantedAuthorities) - throws SQLException { - List groups = authenticationService.getSpecialGroups(context, httpServletRequest); - for (Group group : groups) { - grantedAuthorities.add(new SimpleGrantedAuthority(group.getName())); - } - } - - private Authentication createAuthenticationToken(final String password, final Context context, - final List grantedAuthorities) { - EPerson ePerson = context.getCurrentUser(); - if (ePerson != null && StringUtils.isNotBlank(ePerson.getEmail())) { - return new UsernamePasswordAuthenticationToken(ePerson.getEmail(), password, grantedAuthorities); - - } else { - log.info(LogHelper.getHeader(context, "failed_login", - "No eperson with an non-blank e-mail address found")); - throw new BadCredentialsException("Login failed"); - } - } - - @Override - public boolean supports(Class authentication) { - return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication)); - } -} \ No newline at end of file diff --git a/dspace-rest/src/main/java/org/dspace/rest/authentication/NoRedirectAuthenticationLoginSuccessHandler.java b/dspace-rest/src/main/java/org/dspace/rest/authentication/NoRedirectAuthenticationLoginSuccessHandler.java deleted file mode 100644 index af146f27b7..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/authentication/NoRedirectAuthenticationLoginSuccessHandler.java +++ /dev/null @@ -1,41 +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.rest.authentication; - -import java.io.IOException; -import javax.annotation.PostConstruct; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.springframework.security.web.RedirectStrategy; -import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; - -/** - * @author kevinvandevelde at atmire.com - * - * Spring redirects to the home page after a successfull login. This success handles ensures that this is NOT the case. - */ -public class NoRedirectAuthenticationLoginSuccessHandler extends SimpleUrlAuthenticationSuccessHandler { - - @PostConstruct - public void afterPropertiesSet() { - setRedirectStrategy(new NoRedirectStrategy()); - } - - protected class NoRedirectStrategy implements RedirectStrategy { - - @Override - public void sendRedirect(HttpServletRequest request, - HttpServletResponse response, String url) throws IOException { - // no redirect - - } - - } - -} diff --git a/dspace-rest/src/main/java/org/dspace/rest/authentication/NoRedirectAuthenticationLogoutSuccessHandler.java b/dspace-rest/src/main/java/org/dspace/rest/authentication/NoRedirectAuthenticationLogoutSuccessHandler.java deleted file mode 100644 index db28f2e388..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/authentication/NoRedirectAuthenticationLogoutSuccessHandler.java +++ /dev/null @@ -1,39 +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.rest.authentication; - -import java.io.IOException; -import javax.annotation.PostConstruct; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.springframework.security.web.RedirectStrategy; -import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler; - -/** - * @author kevinvandevelde at atmire.com - * - * Spring redirects to the home page after a successfull logout. This success handles ensures that this is NOT the case. - */ -public class NoRedirectAuthenticationLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler { - @PostConstruct - public void afterPropertiesSet() { - setRedirectStrategy(new NoRedirectStrategy()); - } - - protected class NoRedirectStrategy implements RedirectStrategy { - - @Override - public void sendRedirect(HttpServletRequest request, - HttpServletResponse response, String url) throws IOException { - // no redirect - - } - - } -} diff --git a/dspace-rest/src/main/java/org/dspace/rest/common/Bitstream.java b/dspace-rest/src/main/java/org/dspace/rest/common/Bitstream.java deleted file mode 100644 index 7eb198990e..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/common/Bitstream.java +++ /dev/null @@ -1,199 +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.rest.common; - -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import javax.servlet.ServletContext; -import javax.xml.bind.annotation.XmlRootElement; - -import org.apache.logging.log4j.Logger; -import org.dspace.content.Bundle; -import org.dspace.content.factory.ContentServiceFactory; -import org.dspace.content.service.BitstreamService; -import org.dspace.content.service.BundleService; -import org.dspace.core.Constants; -import org.dspace.core.Context; -import org.dspace.utils.DSpace; - -/** - * Created with IntelliJ IDEA. - * User: peterdietz - * Date: 9/21/13 - * Time: 12:54 AM - * To change this template use File | Settings | File Templates. - */ -@XmlRootElement(name = "bitstream") -public class Bitstream extends DSpaceObject { - protected BitstreamService bitstreamService = ContentServiceFactory.getInstance().getBitstreamService(); - protected BundleService bundleService = ContentServiceFactory.getInstance().getBundleService(); - - Logger log = org.apache.logging.log4j.LogManager.getLogger(Bitstream.class); - - private String bundleName; - private String description; - private String format; - private String mimeType; - private Long sizeBytes; - private DSpaceObject parentObject; - private String retrieveLink; - private CheckSum checkSum; - private Integer sequenceId; - - private ResourcePolicy[] policies = null; - - public Bitstream() { - - } - - public Bitstream(org.dspace.content.Bitstream bitstream, ServletContext servletContext, String expand, - Context context) - throws SQLException { - super(bitstream, servletContext); - setup(bitstream, servletContext, expand, context); - } - - public void setup(org.dspace.content.Bitstream bitstream, ServletContext servletContext, String expand, - Context context) - throws SQLException { - List expandFields = new ArrayList(); - if (expand != null) { - expandFields = Arrays.asList(expand.split(",")); - } - - //A logo bitstream might not have a bundle... - if (bitstream.getBundles() != null && !bitstream.getBundles().isEmpty()) { - if (bitstreamService.getParentObject(context, bitstream).getType() == Constants.ITEM) { - bundleName = bitstream.getBundles().get(0).getName(); - } - } - - description = bitstream.getDescription(); - format = bitstreamService.getFormatDescription(context, bitstream); - sizeBytes = bitstream.getSizeBytes(); - String path = new DSpace().getRequestService().getCurrentRequest().getHttpServletRequest().getContextPath(); - retrieveLink = path + "/bitstreams/" + bitstream.getID() + "/retrieve"; - mimeType = bitstreamService.getFormat(context, bitstream).getMIMEType(); - sequenceId = bitstream.getSequenceID(); - CheckSum checkSum = new CheckSum(); - checkSum.setCheckSumAlgorith(bitstream.getChecksumAlgorithm()); - checkSum.setValue(bitstream.getChecksum()); - this.setCheckSum(checkSum); - - if (expandFields.contains("parent") || expandFields.contains("all")) { - parentObject = new DSpaceObject(bitstreamService.getParentObject(context, bitstream), servletContext); - } else { - this.addExpand("parent"); - } - - if (expandFields.contains("policies") || expandFields.contains("all")) { - // Find policies without context. - List tempPolicies = new ArrayList(); - List bundles = bitstream.getBundles(); - for (Bundle bundle : bundles) { - List bitstreamsPolicies = bundleService - .getBitstreamPolicies(context, bundle); - for (org.dspace.authorize.ResourcePolicy policy : bitstreamsPolicies) { - if (policy.getdSpaceObject().equals(bitstream)) { - tempPolicies.add(new ResourcePolicy(policy)); - } - } - } - - policies = tempPolicies.toArray(new ResourcePolicy[0]); - } else { - this.addExpand("policies"); - } - - if (!expandFields.contains("all")) { - this.addExpand("all"); - } - } - - public Integer getSequenceId() { - return sequenceId; - } - - public void setSequenceId(Integer sequenceId) { - this.sequenceId = sequenceId; - } - - public String getBundleName() { - return bundleName; - } - - public void setBundleName(String bundleName) { - this.bundleName = bundleName; - } - - public void setDescription(String description) { - this.description = description; - } - - public void setFormat(String format) { - this.format = format; - } - - public void setMimeType(String mimeType) { - this.mimeType = mimeType; - } - - public void setSizeBytes(Long sizeBytes) { - this.sizeBytes = sizeBytes; - } - - public void setParentObject(DSpaceObject parentObject) { - this.parentObject = parentObject; - } - - public void setRetrieveLink(String retrieveLink) { - this.retrieveLink = retrieveLink; - } - - public String getDescription() { - return description; - } - - public String getFormat() { - return format; - } - - public String getMimeType() { - return mimeType; - } - - public Long getSizeBytes() { - return sizeBytes; - } - - public String getRetrieveLink() { - return retrieveLink; - } - - public DSpaceObject getParentObject() { - return parentObject; - } - - public CheckSum getCheckSum() { - return checkSum; - } - - public void setCheckSum(CheckSum checkSum) { - this.checkSum = checkSum; - } - - public ResourcePolicy[] getPolicies() { - return policies; - } - - public void setPolicies(ResourcePolicy[] policies) { - this.policies = policies; - } -} diff --git a/dspace-rest/src/main/java/org/dspace/rest/common/CheckSum.java b/dspace-rest/src/main/java/org/dspace/rest/common/CheckSum.java deleted file mode 100644 index 2db36ae9a0..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/common/CheckSum.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.rest.common; - -import javax.xml.bind.annotation.XmlAttribute; -import javax.xml.bind.annotation.XmlType; -import javax.xml.bind.annotation.XmlValue; - -@XmlType -public class CheckSum { - String checkSumAlgorithm; - String value; - - public CheckSum() { - } - - @XmlAttribute(name = "checkSumAlgorithm") - public String getCheckSumAlgorith() { - return checkSumAlgorithm; - } - - public void setCheckSumAlgorith(String checkSumAlgorith) { - this.checkSumAlgorithm = checkSumAlgorith; - } - - @XmlValue - public String getValue() { - return value; - } - - public void setValue(String value) { - this.value = value; - } -} diff --git a/dspace-rest/src/main/java/org/dspace/rest/common/Collection.java b/dspace-rest/src/main/java/org/dspace/rest/common/Collection.java deleted file mode 100644 index be6e698b4d..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/common/Collection.java +++ /dev/null @@ -1,225 +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.rest.common; - -import static org.dspace.content.service.DSpaceObjectService.MD_COPYRIGHT_TEXT; -import static org.dspace.content.service.DSpaceObjectService.MD_INTRODUCTORY_TEXT; -import static org.dspace.content.service.DSpaceObjectService.MD_SHORT_DESCRIPTION; -import static org.dspace.content.service.DSpaceObjectService.MD_SIDEBAR_TEXT; - -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Iterator; -import java.util.List; -import javax.servlet.ServletContext; -import javax.ws.rs.WebApplicationException; -import javax.xml.bind.annotation.XmlRootElement; - -import org.apache.logging.log4j.Logger; -import org.dspace.content.factory.ContentServiceFactory; -import org.dspace.content.service.CollectionService; -import org.dspace.content.service.CommunityService; -import org.dspace.content.service.ItemService; -import org.dspace.core.Context; - -/** - * Created with IntelliJ IDEA. - * User: peterdietz - * Date: 5/22/13 - * Time: 9:41 AM - */ -@XmlRootElement(name = "collection") -public class Collection extends DSpaceObject { - protected CommunityService communityService = ContentServiceFactory.getInstance().getCommunityService(); - protected CollectionService collectionService = ContentServiceFactory.getInstance().getCollectionService(); - protected ItemService itemService = ContentServiceFactory.getInstance().getItemService(); - - Logger log = org.apache.logging.log4j.LogManager.getLogger(Collection.class); - - //Relationships - private Bitstream logo; - private Community parentCommunity; - private List parentCommunityList = new ArrayList<>(); - - private List items = new ArrayList<>(); - - //Collection-Metadata - private String license; - private String copyrightText; - private String introductoryText; - private String shortDescription; - private String sidebarText; - - //Calculated - private Integer numberItems; - - public Collection() { - } - - public Collection(org.dspace.content.Collection collection, ServletContext servletContext, String expand, - Context context, Integer limit, Integer offset) - throws SQLException, WebApplicationException { - super(collection, servletContext); - setup(collection, servletContext, expand, context, limit, offset); - } - - private void setup(org.dspace.content.Collection collection, ServletContext servletContext, String expand, - Context context, Integer limit, Integer offset) - throws SQLException { - List expandFields = new ArrayList<>(); - if (expand != null) { - expandFields = Arrays.asList(expand.split(",")); - } - - this.setCopyrightText(collectionService.getMetadataFirstValue(collection, - MD_COPYRIGHT_TEXT, org.dspace.content.Item.ANY)); - this.setIntroductoryText(collectionService.getMetadataFirstValue(collection, - MD_INTRODUCTORY_TEXT, org.dspace.content.Item.ANY)); - this.setShortDescription(collectionService.getMetadataFirstValue(collection, - MD_SHORT_DESCRIPTION, org.dspace.content.Item.ANY)); - this.setSidebarText(collectionService.getMetadataFirstValue(collection, - MD_SIDEBAR_TEXT, org.dspace.content.Item.ANY)); - - if (expandFields.contains("parentCommunityList") || expandFields.contains("all")) { - List parentCommunities = communityService.getAllParents(context, collection); - for (org.dspace.content.Community parentCommunity : parentCommunities) { - this.addParentCommunityList(new Community(parentCommunity, servletContext, null, context)); - } - } else { - this.addExpand("parentCommunityList"); - } - - if (expandFields.contains("parentCommunity") | expandFields.contains("all")) { - org.dspace.content.Community parentCommunity = - (org.dspace.content.Community) collectionService - .getParentObject(context, collection); - this.setParentCommunity(new Community( - parentCommunity, servletContext, null, context)); - } else { - this.addExpand("parentCommunity"); - } - - //TODO: Item paging. limit, offset/page - if (expandFields.contains("items") || expandFields.contains("all")) { - Iterator childItems = - itemService.findByCollection(context, collection, limit, offset); - - items = new ArrayList<>(); - while (childItems.hasNext()) { - org.dspace.content.Item item = childItems.next(); - - if (itemService.isItemListedForUser(context, item)) { - items.add(new Item(item, servletContext, null, context)); - } - } - } else { - this.addExpand("items"); - } - - if (expandFields.contains("license") || expandFields.contains("all")) { - setLicense(collectionService.getLicense(collection)); - } else { - this.addExpand("license"); - } - - if (expandFields.contains("logo") || expandFields.contains("all")) { - if (collection.getLogo() != null) { - this.logo = new Bitstream(collection.getLogo(), servletContext, null, context); - } - } else { - this.addExpand("logo"); - } - - if (!expandFields.contains("all")) { - this.addExpand("all"); - } - - this.setNumberItems(itemService.countItems(context, collection)); - } - - public Bitstream getLogo() { - return logo; - } - - public Integer getNumberItems() { - return numberItems; - } - - public void setNumberItems(Integer numberItems) { - this.numberItems = numberItems; - } - - public Community getParentCommunity() { - return parentCommunity; - } - - public void setParentCommunity(Community parentCommunity) { - this.parentCommunity = parentCommunity; - } - - public List getItems() { - return items; - } - - public void setItems(List items) { - this.items = items; - } - - public void setParentCommunityList(List parentCommunityList) { - this.parentCommunityList = parentCommunityList; - } - - public List getParentCommunityList() { - return parentCommunityList; - } - - public void addParentCommunityList(Community parentCommunity) { - this.parentCommunityList.add(parentCommunity); - } - - public String getLicense() { - return license; - } - - public void setLicense(String license) { - this.license = license; - } - - public String getCopyrightText() { - return copyrightText; - } - - public void setCopyrightText(String copyrightText) { - this.copyrightText = copyrightText; - } - - public String getIntroductoryText() { - return introductoryText; - } - - public void setIntroductoryText(String introductoryText) { - this.introductoryText = introductoryText; - } - - public String getShortDescription() { - return shortDescription; - } - - public void setShortDescription(String shortDescription) { - this.shortDescription = shortDescription; - } - - public String getSidebarText() { - return sidebarText; - } - - public void setSidebarText(String sidebarText) { - this.sidebarText = sidebarText; - } -} diff --git a/dspace-rest/src/main/java/org/dspace/rest/common/Community.java b/dspace-rest/src/main/java/org/dspace/rest/common/Community.java deleted file mode 100644 index e6e4716eab..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/common/Community.java +++ /dev/null @@ -1,217 +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.rest.common; - -import static org.dspace.content.service.DSpaceObjectService.MD_COPYRIGHT_TEXT; -import static org.dspace.content.service.DSpaceObjectService.MD_INTRODUCTORY_TEXT; -import static org.dspace.content.service.DSpaceObjectService.MD_SHORT_DESCRIPTION; -import static org.dspace.content.service.DSpaceObjectService.MD_SIDEBAR_TEXT; - -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import javax.servlet.ServletContext; -import javax.ws.rs.WebApplicationException; -import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlRootElement; - -import org.apache.logging.log4j.Logger; -import org.dspace.authorize.factory.AuthorizeServiceFactory; -import org.dspace.authorize.service.AuthorizeService; -import org.dspace.content.factory.ContentServiceFactory; -import org.dspace.content.service.CommunityService; -import org.dspace.content.service.ItemService; -import org.dspace.core.Context; - -/** - * Created with IntelliJ IDEA. - * User: peterdietz - * Date: 5/22/13 - * Time: 9:41 AM - * To change this template use File | Settings | File Templates. - */ -@XmlRootElement(name = "community") -public class Community extends DSpaceObject { - protected CommunityService communityService = ContentServiceFactory.getInstance().getCommunityService(); - protected ItemService itemService = ContentServiceFactory.getInstance().getItemService(); - protected AuthorizeService authorizeService = AuthorizeServiceFactory.getInstance().getAuthorizeService(); - - private static Logger log = org.apache.logging.log4j.LogManager.getLogger(Community.class); - - //Exandable relationships - private Bitstream logo; - - private Community parentCommunity; - - private String copyrightText; - private String introductoryText; - private String shortDescription; - private String sidebarText; - private Integer countItems; - - private List subcommunities = new ArrayList<>(); - - private List collections = new ArrayList<>(); - - public Community() { - } - - public Community(org.dspace.content.Community community, ServletContext servletContext, String expand, - Context context) - throws SQLException, WebApplicationException { - super(community, servletContext); - setup(community, servletContext, expand, context); - } - - private void setup(org.dspace.content.Community community, ServletContext servletContext, String expand, - Context context) - throws SQLException { - List expandFields = new ArrayList<>(); - if (expand != null) { - expandFields = Arrays.asList(expand.split(",")); - } - - this.setCopyrightText(communityService.getMetadataFirstValue(community, - MD_COPYRIGHT_TEXT, org.dspace.content.Item.ANY)); - this.setIntroductoryText(communityService.getMetadataFirstValue(community, - MD_INTRODUCTORY_TEXT, org.dspace.content.Item.ANY)); - this.setShortDescription(communityService.getMetadataFirstValue(community, - MD_SHORT_DESCRIPTION, org.dspace.content.Item.ANY)); - this.setSidebarText(communityService.getMetadataFirstValue(community, - MD_SIDEBAR_TEXT, org.dspace.content.Item.ANY)); - this.setCountItems(itemService.countItems(context, community)); - - if (expandFields.contains("parentCommunity") || expandFields.contains("all")) { - org.dspace.content.Community parentCommunity = (org.dspace.content.Community) communityService - .getParentObject(context, community); - if (parentCommunity != null) { - setParentCommunity(new Community(parentCommunity, servletContext, null, context)); - } - } else { - this.addExpand("parentCommunity"); - } - - if (expandFields.contains("collections") || expandFields.contains("all")) { - List collections = community.getCollections(); - List restCollections = new ArrayList<>(); - - for (org.dspace.content.Collection collection : collections) { - if (authorizeService.authorizeActionBoolean(context, collection, org.dspace.core.Constants.READ)) { - restCollections.add(new Collection(collection, servletContext, null, context, null, null)); - } else { - log.info("Omitted restricted collection: " + collection.getID() + " _ " + collection.getName()); - } - } - setCollections(restCollections); - } else { - this.addExpand("collections"); - } - - if (expandFields.contains("subCommunities") || expandFields.contains("all")) { - List communities = community.getSubcommunities(); - subcommunities = new ArrayList<>(); - for (org.dspace.content.Community subCommunity : communities) { - if (authorizeService.authorizeActionBoolean(context, subCommunity, org.dspace.core.Constants.READ)) { - subcommunities.add(new Community(subCommunity, servletContext, null, context)); - } else { - log.info( - "Omitted restricted subCommunity: " + subCommunity.getID() + " _ " + subCommunity.getName()); - } - } - } else { - this.addExpand("subCommunities"); - } - - if (expandFields.contains("logo") || expandFields.contains("all")) { - if (community.getLogo() != null) { - logo = new Bitstream(community.getLogo(), servletContext, null, context); - } - } else { - this.addExpand("logo"); - } - - if (!expandFields.contains("all")) { - this.addExpand("all"); - } - } - - public List getCollections() { - return collections; - } - - public void setCollections(List collections) { - this.collections = collections; - } - - public Integer getCountItems() { - return countItems; - } - - public void setCountItems(Integer countItems) { - this.countItems = countItems; - } - - public String getSidebarText() { - return sidebarText; - } - - public void setSidebarText(String sidebarText) { - this.sidebarText = sidebarText; - } - - public String getShortDescription() { - return shortDescription; - } - - public void setShortDescription(String shortDescription) { - this.shortDescription = shortDescription; - } - - public String getIntroductoryText() { - return introductoryText; - } - - public void setIntroductoryText(String introductoryText) { - this.introductoryText = introductoryText; - } - - public String getCopyrightText() { - return copyrightText; - } - - public void setCopyrightText(String copyrightText) { - this.copyrightText = copyrightText; - } - - public Community getParentCommunity() { - return parentCommunity; - } - - public void setParentCommunity(Community parentCommunity) { - this.parentCommunity = parentCommunity; - } - - public Bitstream getLogo() { - return logo; - } - - public void setLogo(Bitstream logo) { - this.logo = logo; - } - - // Renamed because of xml annotation exception with this attribute and getSubCommunities. - @XmlElement(name = "subcommunities", required = true) - public List getSubcommunities() { - return subcommunities; - } - - public void setSubcommunities(List subcommunities) { - this.subcommunities = subcommunities; - } -} diff --git a/dspace-rest/src/main/java/org/dspace/rest/common/DSpaceObject.java b/dspace-rest/src/main/java/org/dspace/rest/common/DSpaceObject.java deleted file mode 100644 index 08df254336..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/common/DSpaceObject.java +++ /dev/null @@ -1,107 +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.rest.common; - -import java.util.ArrayList; -import java.util.List; -import javax.servlet.ServletContext; -import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlRootElement; - -import org.atteo.evo.inflector.English; -import org.dspace.content.factory.ContentServiceFactory; -import org.dspace.content.service.DSpaceObjectService; - -/** - * Created with IntelliJ IDEA. - * User: peterdietz - * Date: 10/7/13 - * Time: 12:11 PM - * To change this template use File | Settings | File Templates. - */ -@XmlRootElement(name = "dspaceobject") -public class DSpaceObject { - - private String uuid; - - private String name; - private String handle; - private String type; - - @XmlElement(name = "link", required = true) - private String link; - - @XmlElement(required = true) - private ArrayList expand = new ArrayList(); - - public DSpaceObject() { - - } - - public DSpaceObject(org.dspace.content.DSpaceObject dso, ServletContext servletContext) { - setUUID(dso.getID().toString()); - setName(dso.getName()); - setHandle(dso.getHandle()); - DSpaceObjectService dspaceObjectService = ContentServiceFactory.getInstance().getDSpaceObjectService(dso); - setType(dspaceObjectService.getTypeText(dso).toLowerCase()); - link = createLink(servletContext); - } - - public String getName() { - return this.name; - } - - public void setName(String name) { - this.name = name; - } - - public String getHandle() { - return handle; - } - - public void setHandle(String handle) { - this.handle = handle; - } - - public String getLink() { - return link; - } - - public String getType() { - return this.type; - } - - public void setType(String type) { - this.type = type; - } - - - public List getExpand() { - return expand; - } - - public void setExpand(ArrayList expand) { - this.expand = expand; - } - - public void addExpand(String expandableAttribute) { - this.expand.add(expandableAttribute); - } - - public String getUUID() { - return uuid; - } - - public void setUUID(String uuid) { - this.uuid = uuid; - } - - private String createLink(ServletContext context) { - return context.getContextPath() + "/" + English.plural(getType()) + "/" + getUUID(); - } -} diff --git a/dspace-rest/src/main/java/org/dspace/rest/common/FilteredCollection.java b/dspace-rest/src/main/java/org/dspace/rest/common/FilteredCollection.java deleted file mode 100644 index c7ff0ef9b3..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/common/FilteredCollection.java +++ /dev/null @@ -1,191 +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.rest.common; - -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Iterator; -import java.util.List; -import javax.servlet.ServletContext; -import javax.ws.rs.WebApplicationException; -import javax.xml.bind.annotation.XmlRootElement; - -import org.apache.logging.log4j.Logger; -import org.dspace.content.factory.ContentServiceFactory; -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.rest.filter.ItemFilterSet; - -/** - * Retrieve items within a collection that match a specific set of Item Filters of interest - * - * @author Terry Brady, Georgetown University - */ -@XmlRootElement(name = "filtered-collection") -public class FilteredCollection extends DSpaceObject { - protected CommunityService communityService = ContentServiceFactory.getInstance().getCommunityService(); - protected CollectionService collectionService = ContentServiceFactory.getInstance().getCollectionService(); - protected ItemService itemService = ContentServiceFactory.getInstance().getItemService(); - Logger log = org.apache.logging.log4j.LogManager.getLogger(FilteredCollection.class); - - //Relationships - private Community parentCommunity; - private Community topCommunity; - private List parentCommunityList = new ArrayList(); - - private List items = new ArrayList(); - - private List itemFilters = new ArrayList(); - - //Calculated - private Integer numberItems; - private Integer numberItemsProcessed; - - public FilteredCollection() { - } - - /** - * Evaluate a collection against of set of Item Filters - * - * @param collection DSpace Collection to evaluate - * @param servletContext Context of the servlet container. - * @param filters String representing a list of filters - * @param expand String in which is what you want to add to returned instance - * of collection. Options are: "all", "parentCommunityList", - * "parentCommunity", "items", "license" and "logo". If you want - * to use multiple options, it must be separated by commas. - * @param context The relevant DSpace Context. - * @param limit Limit value for items in list in collection. Default value is 100. - * @param offset Offset of start index in list of items of collection. Default - * value is 0. - * @throws SQLException An exception that provides information on a database access error or other - * errors. - * @throws WebApplicationException Runtime exception for applications. - */ - public FilteredCollection(org.dspace.content.Collection collection, ServletContext servletContext, String filters, - String expand, Context context, Integer limit, Integer offset) - throws SQLException, WebApplicationException { - super(collection, servletContext); - setup(collection, servletContext, expand, context, limit, offset, filters); - } - - private void setup(org.dspace.content.Collection collection, ServletContext servletContext, String expand, - Context context, Integer limit, Integer offset, String filters) throws SQLException { - List expandFields = new ArrayList(); - if (expand != null) { - expandFields = Arrays.asList(expand.split(",")); - } - - if (expandFields.contains("parentCommunityList") || expandFields.contains("all")) { - List parentCommunities = communityService.getAllParents(context, collection); - List parentCommunityList = new ArrayList(); - for (org.dspace.content.Community parentCommunity : parentCommunities) { - parentCommunityList.add(new Community(parentCommunity, servletContext, null, context)); - } - this.setParentCommunityList(parentCommunityList); - } else { - this.addExpand("parentCommunityList"); - } - - if (expandFields.contains("parentCommunity") | expandFields.contains("all")) { - org.dspace.content.Community parentCommunity = collection.getCommunities().get(0); - this.setParentCommunity(new Community(parentCommunity, servletContext, null, context)); - } else { - this.addExpand("parentCommunity"); - } - - if (expandFields.contains("topCommunity") | expandFields.contains("all")) { - List parentCommunities = communityService.getAllParents(context, collection); - if (parentCommunities.size() > 0) { - org.dspace.content.Community topCommunity = parentCommunities.get(parentCommunities.size() - 1); - this.setTopCommunity(new Community(topCommunity, servletContext, null, context)); - } - } else { - this.addExpand("topCommunity"); - } - - - boolean reportItems = expandFields.contains("items") || expandFields.contains("all"); - ItemFilterSet itemFilterSet = new ItemFilterSet(filters, reportItems); - this.setItemFilters(itemFilterSet.getItemFilters()); - - this.setNumberItemsProcessed(0); - if (itemFilters.size() > 0) { - Iterator childItems = itemService - .findAllByCollection(context, collection, limit, offset); - int numProc = itemFilterSet - .processSaveItems(context, servletContext, childItems, items, reportItems, expand); - this.setNumberItemsProcessed(numProc); - } - - if (!expandFields.contains("all")) { - this.addExpand("all"); - } - this.setNumberItems(itemService.countAllItems(context, collection)); - } - - public Integer getNumberItems() { - return numberItems; - } - - public void setNumberItems(Integer numberItems) { - this.numberItems = numberItems; - } - - public Integer getNumberItemsProcessed() { - return numberItemsProcessed; - } - - public void setNumberItemsProcessed(Integer numberItemsProcessed) { - this.numberItemsProcessed = numberItemsProcessed; - } - - public Community getParentCommunity() { - return parentCommunity; - } - - public void setParentCommunity(Community parentCommunity) { - this.parentCommunity = parentCommunity; - } - - public Community getTopCommunity() { - return topCommunity; - } - - public void setTopCommunity(Community topCommunity) { - this.topCommunity = topCommunity; - } - - - public List getItems() { - return items; - } - - public void setItems(List items) { - this.items = items; - } - - public void setParentCommunityList(List parentCommunityList) { - this.parentCommunityList = parentCommunityList; - } - - public List getParentCommunityList() { - return parentCommunityList; - } - - public List getItemFilters() { - return itemFilters; - } - - public void setItemFilters(List itemFilters) { - this.itemFilters = itemFilters; - } -} diff --git a/dspace-rest/src/main/java/org/dspace/rest/common/HierarchyCollection.java b/dspace-rest/src/main/java/org/dspace/rest/common/HierarchyCollection.java deleted file mode 100644 index 6c40faf62b..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/common/HierarchyCollection.java +++ /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/ - */ -package org.dspace.rest.common; - -import javax.xml.bind.annotation.XmlRootElement; - -/** - * Used to handle/determine status of REST API. - * Mainly to know your authentication status - */ -@XmlRootElement(name = "collection") -public class HierarchyCollection extends HierarchyObject { - public HierarchyCollection() { - } - - public HierarchyCollection(String id, String name, String handle) { - super(id, name, handle); - } -} diff --git a/dspace-rest/src/main/java/org/dspace/rest/common/HierarchyCommunity.java b/dspace-rest/src/main/java/org/dspace/rest/common/HierarchyCommunity.java deleted file mode 100644 index 3618608e3e..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/common/HierarchyCommunity.java +++ /dev/null @@ -1,44 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.rest.common; - -import java.util.ArrayList; -import java.util.List; -import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlRootElement; - -@XmlRootElement(name = "community") -public class HierarchyCommunity extends HierarchyObject { - private List communities = new ArrayList(); - private List collections = new ArrayList(); - - public HierarchyCommunity() { - } - - public HierarchyCommunity(String id, String name, String handle) { - super(id, name, handle); - } - - @XmlElement(name = "community") - public List getCommunities() { - return communities; - } - - public void setCommunities(List communities) { - this.communities = communities; - } - - @XmlElement(name = "collection") - public List getCollections() { - return collections; - } - - public void setCollections(List collections) { - this.collections = collections; - } -} diff --git a/dspace-rest/src/main/java/org/dspace/rest/common/HierarchyObject.java b/dspace-rest/src/main/java/org/dspace/rest/common/HierarchyObject.java deleted file mode 100644 index 0074eeea6a..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/common/HierarchyObject.java +++ /dev/null @@ -1,51 +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.rest.common; - -import javax.xml.bind.annotation.XmlRootElement; - -@XmlRootElement(name = "object") -public class HierarchyObject { - //id may be a numeric id or a uuid depending on the version of DSpace - private String id; - private String name; - private String handle; - - public HierarchyObject() { - } - - public HierarchyObject(String id, String name, String handle) { - setId(id); - setName(name); - setHandle(handle); - } - - 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 String getHandle() { - return handle; - } - - public void setHandle(String handle) { - this.handle = handle; - } -} diff --git a/dspace-rest/src/main/java/org/dspace/rest/common/HierarchySite.java b/dspace-rest/src/main/java/org/dspace/rest/common/HierarchySite.java deleted file mode 100644 index 5eb2cc523c..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/common/HierarchySite.java +++ /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/ - */ -package org.dspace.rest.common; - -import javax.xml.bind.annotation.XmlRootElement; - -/** - * Used to handle/determine status of REST API. - * Mainly to know your authentication status - */ -@XmlRootElement(name = "site") -public class HierarchySite extends HierarchyCommunity { - public HierarchySite() { - } - - public HierarchySite(String id, String name, String handle) { - super(id, name, handle); - } -} diff --git a/dspace-rest/src/main/java/org/dspace/rest/common/Item.java b/dspace-rest/src/main/java/org/dspace/rest/common/Item.java deleted file mode 100644 index 3794153b7d..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/common/Item.java +++ /dev/null @@ -1,219 +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.rest.common; - -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import javax.servlet.ServletContext; -import javax.ws.rs.WebApplicationException; -import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlRootElement; - -import org.apache.logging.log4j.Logger; -import org.dspace.app.util.factory.UtilServiceFactory; -import org.dspace.app.util.service.MetadataExposureService; -import org.dspace.authorize.factory.AuthorizeServiceFactory; -import org.dspace.authorize.service.AuthorizeService; -import org.dspace.content.Bundle; -import org.dspace.content.MetadataField; -import org.dspace.content.MetadataValue; -import org.dspace.content.factory.ContentServiceFactory; -import org.dspace.content.service.ItemService; -import org.dspace.core.Context; - -/** - * Created with IntelliJ IDEA. - * User: peterdietz - * Date: 9/19/13 - * Time: 4:50 PM - * To change this template use File | Settings | File Templates. - */ -@SuppressWarnings("deprecation") -@XmlRootElement(name = "item") -public class Item extends DSpaceObject { - protected ItemService itemService = ContentServiceFactory.getInstance().getItemService(); - protected MetadataExposureService metadataExposureService = UtilServiceFactory.getInstance() - .getMetadataExposureService(); - protected AuthorizeService authorizeService = AuthorizeServiceFactory.getInstance().getAuthorizeService(); - - Logger log = org.apache.logging.log4j.LogManager.getLogger(Item.class); - - String isArchived; - String isWithdrawn; - String lastModified; - - Collection parentCollection; - List parentCollectionList; - List parentCommunityList; - List metadata; - List bitstreams; - - public Item() { - } - - public Item(org.dspace.content.Item item, ServletContext servletContext, String expand, Context context) - throws SQLException, WebApplicationException { - super(item, servletContext); - setup(item, servletContext, expand, context); - } - - private void setup(org.dspace.content.Item item, ServletContext servletContext, String expand, Context context) - throws SQLException { - List expandFields = new ArrayList(); - if (expand != null) { - expandFields = Arrays.asList(expand.split(",")); - } - - if (expandFields.contains("metadata") || expandFields.contains("all")) { - metadata = new ArrayList(); - List metadataValues = itemService.getMetadata( - item, org.dspace.content.Item.ANY, org.dspace.content.Item.ANY, - org.dspace.content.Item.ANY, org.dspace.content.Item.ANY); - - for (MetadataValue metadataValue : metadataValues) { - MetadataField metadataField = metadataValue.getMetadataField(); - if (!metadataExposureService.isHidden(context, - metadataField.getMetadataSchema().getName(), - metadataField.getElement(), - metadataField.getQualifier())) { - metadata.add(new MetadataEntry(metadataField.toString('.'), - metadataValue.getValue(), metadataValue.getLanguage())); - } - } - } else { - this.addExpand("metadata"); - } - - this.setArchived(Boolean.toString(item.isArchived())); - this.setWithdrawn(Boolean.toString(item.isWithdrawn())); - this.setLastModified(item.getLastModified().toString()); - - if (expandFields.contains("parentCollection") || expandFields.contains("all")) { - if (item.getOwningCollection() != null) { - this.parentCollection = new Collection(item.getOwningCollection(), - servletContext, null, context, null, null); - } else { - this.addExpand("parentCollection"); - } - } else { - this.addExpand("parentCollection"); - } - - if (expandFields.contains("parentCollectionList") || expandFields.contains("all")) { - this.parentCollectionList = new ArrayList(); - List collections = item.getCollections(); - for (org.dspace.content.Collection collection : collections) { - this.parentCollectionList.add(new Collection(collection, - servletContext, null, context, null, null)); - } - } else { - this.addExpand("parentCollectionList"); - } - - if (expandFields.contains("parentCommunityList") || expandFields.contains("all")) { - this.parentCommunityList = new ArrayList(); - List communities = itemService.getCommunities(context, item); - - for (org.dspace.content.Community community : communities) { - this.parentCommunityList.add(new Community(community, servletContext, null, context)); - } - } else { - this.addExpand("parentCommunityList"); - } - - //TODO: paging - offset, limit - if (expandFields.contains("bitstreams") || expandFields.contains("all")) { - bitstreams = new ArrayList(); - - List bundles = item.getBundles(); - for (Bundle bundle : bundles) { - - List itemBitstreams = bundle.getBitstreams(); - for (org.dspace.content.Bitstream itemBitstream : itemBitstreams) { - if (authorizeService - .authorizeActionBoolean(context, itemBitstream, org.dspace.core.Constants.READ)) { - bitstreams.add(new Bitstream(itemBitstream, servletContext, null, context)); - } - } - } - } else { - this.addExpand("bitstreams"); - } - - if (!expandFields.contains("all")) { - this.addExpand("all"); - } - } - - public String getArchived() { - return isArchived; - } - - public void setArchived(String archived) { - isArchived = archived; - } - - public String getWithdrawn() { - return isWithdrawn; - } - - public void setWithdrawn(String withdrawn) { - isWithdrawn = withdrawn; - } - - public String getLastModified() { - return lastModified; - } - - public void setLastModified(String lastModified) { - this.lastModified = lastModified; - } - - public Collection getParentCollection() { - return parentCollection; - } - - public List getParentCollectionList() { - return parentCollectionList; - } - - public List getMetadata() { - return metadata; - } - - public List getBitstreams() { - return bitstreams; - } - - public List getParentCommunityList() { - return parentCommunityList; - } - - public void setParentCollection(Collection parentCollection) { - this.parentCollection = parentCollection; - } - - public void setParentCollectionList(List parentCollectionList) { - this.parentCollectionList = parentCollectionList; - } - - public void setParentCommunityList(List parentCommunityList) { - this.parentCommunityList = parentCommunityList; - } - - @XmlElement(required = true) - public void setMetadata(List metadata) { - this.metadata = metadata; - } - - public void setBitstreams(List bitstreams) { - this.bitstreams = bitstreams; - } -} diff --git a/dspace-rest/src/main/java/org/dspace/rest/common/ItemFilter.java b/dspace-rest/src/main/java/org/dspace/rest/common/ItemFilter.java deleted file mode 100644 index bc5bd13134..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/common/ItemFilter.java +++ /dev/null @@ -1,274 +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.rest.common; - -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.LinkedHashMap; -import java.util.List; -import javax.ws.rs.WebApplicationException; -import javax.xml.bind.annotation.XmlAttribute; -import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlRootElement; - -import org.apache.logging.log4j.Logger; -import org.dspace.core.Context; -import org.dspace.core.factory.CoreServiceFactory; -import org.dspace.rest.filter.ItemFilterDefs; -import org.dspace.rest.filter.ItemFilterList; -import org.dspace.rest.filter.ItemFilterTest; - - -/** - * Use Case Item Filters that match a specific set of criteria. - * - * @author Terry Brady, Georgetown University - */ -@XmlRootElement(name = "item-filter") -public class ItemFilter { - static Logger log = org.apache.logging.log4j.LogManager.getLogger(ItemFilter.class); - - private ItemFilterTest itemFilterTest = null; - private String filterName = ""; - private String title; - private String description; - private String category; - private String queryAnnotation; - private List items = new ArrayList(); - private List itemFilterQueries = new ArrayList(); - private List metadata = new ArrayList(); - private Integer itemCount; - private Integer unfilteredItemCount; - private boolean saveItems = false; - - public ItemFilter() { - } - - public static final String ALL_FILTERS = "all_filters"; - public static final String ALL = "all"; - - public static List getItemFilters(String filters, boolean saveItems) { - LinkedHashMap availableTests = new LinkedHashMap(); - for (ItemFilterList plugobj : - (ItemFilterList[]) CoreServiceFactory.getInstance() - .getPluginService().getPluginSequence(ItemFilterList.class)) { - for (ItemFilterTest defFilter : plugobj.getFilters()) { - availableTests.put(defFilter.getName(), defFilter); - } - } - List itemFilters = new ArrayList(); - ItemFilter allFilters = new ItemFilter(ItemFilter.ALL_FILTERS, "Matches all specified filters", - "This filter includes all items that matched ALL specified filters", - ItemFilterDefs.CAT_ITEM, saveItems); - - if (filters.equals(ALL)) { - for (ItemFilterTest itemFilterDef : availableTests.values()) { - itemFilters.add(new ItemFilter(itemFilterDef, saveItems)); - } - itemFilters.add(allFilters); - } else { - for (String filter : Arrays.asList(filters.split(","))) { - if (filter.equals(ItemFilter.ALL_FILTERS)) { - continue; - } - - ItemFilterTest itemFilterDef; - itemFilterDef = availableTests.get(filter); - if (itemFilterDef == null) { - continue; - } - itemFilters.add(new ItemFilter(itemFilterDef, saveItems)); - } - itemFilters.add(allFilters); - } - return itemFilters; - } - - public static ItemFilter getAllFiltersFilter(List itemFilters) { - for (ItemFilter itemFilter : itemFilters) { - if (itemFilter.getFilterName().equals(ALL_FILTERS)) { - itemFilter.initCount(); - return itemFilter; - } - } - return null; - } - - public ItemFilter(ItemFilterTest itemFilterTest, boolean saveItems) - throws WebApplicationException { - this.itemFilterTest = itemFilterTest; - this.saveItems = saveItems; - setup(itemFilterTest.getName(), itemFilterTest.getTitle(), - itemFilterTest.getDescription(), itemFilterTest.getCategory()); - } - - public ItemFilter(String name, String title, String description, String category, boolean saveItems) - throws WebApplicationException { - this.saveItems = saveItems; - setup(name, title, description, category); - } - - private void setup(String name, String title, String description, String category) { - this.setFilterName(name); - this.setTitle(title); - this.setDescription(description); - this.setCategory(category); - } - - private void initCount() { - if (itemCount == null) { - itemCount = 0; - } - if (unfilteredItemCount == null) { - unfilteredItemCount = 0; - } - } - - public boolean hasItemTest() { - return itemFilterTest != null; - } - - public void addItem(org.dspace.rest.common.Item restItem) { - initCount(); - if (saveItems) { - items.add(restItem); - } - itemCount++; - } - - public boolean testItem(Context context, org.dspace.content.Item item, org.dspace.rest.common.Item restItem) { - initCount(); - if (itemFilterTest == null) { - return false; - } - if (itemFilterTest.testItem(context, item)) { - addItem(restItem); - return true; - } - return false; - } - - @XmlAttribute(name = "filter-name") - public String getFilterName() { - return filterName; - } - - public void setFilterName(String name) { - this.filterName = name; - } - - @XmlAttribute(name = "title") - public String getTitle() { - return title; - } - - public void setTitle(String title) { - this.title = title; - } - - @XmlAttribute(name = "category") - public String getCategory() { - return category; - } - - public void setCategory(String category) { - this.category = category; - } - - @XmlAttribute(name = "description") - public String getDescription() { - return description; - } - - public void setDescription(String description) { - this.description = description; - } - - @XmlAttribute(name = "query-annotation") - public String getQueryAnnotation() { - return queryAnnotation; - } - - public void annotateQuery(List query_field, List query_op, List query_val) - throws SQLException { - int index = Math.min(query_field.size(), Math.min(query_op.size(), query_val.size())); - StringBuilder sb = new StringBuilder(); - - for (int i = 0; i < index; i++) { - if (!sb.toString().isEmpty()) { - sb.append(" and "); - } - sb.append("("); - sb.append(query_field.get(i)); - sb.append(" "); - sb.append(query_op.get(i)); - sb.append(" "); - sb.append(query_val.get(i)); - sb.append(")"); - } - setQueryAnnotation(sb.toString()); - } - - public void setQueryAnnotation(String queryAnnotation) { - this.queryAnnotation = queryAnnotation; - } - - @XmlAttribute(name = "item-count") - public Integer getItemCount() { - return itemCount; - } - - public void setItemCount(Integer itemCount) { - this.itemCount = itemCount; - } - - @XmlAttribute(name = "unfiltered-item-count") - public Integer getUnfilteredItemCount() { - return unfilteredItemCount; - } - - public void setUnfilteredItemCount(Integer unfilteredItemCount) { - this.unfilteredItemCount = unfilteredItemCount; - } - - public List getItems() { - return items; - } - - public void setItems(List items) { - this.items = items; - } - - public List getItemFilterQueries() { - return itemFilterQueries; - } - - public void setItemFilterQueries(List itemFilterQueries) { - this.itemFilterQueries = itemFilterQueries; - } - - public void initMetadataList(List show_fields) { - if (show_fields != null) { - List returnFields = new ArrayList(); - for (String field : show_fields) { - returnFields.add(new MetadataEntry(field, null, null)); - } - setMetadata(returnFields); - } - } - - public List getMetadata() { - return metadata; - } - - @XmlElement(required = true) - public void setMetadata(List metadata) { - this.metadata = metadata; - } -} diff --git a/dspace-rest/src/main/java/org/dspace/rest/common/ItemFilterQuery.java b/dspace-rest/src/main/java/org/dspace/rest/common/ItemFilterQuery.java deleted file mode 100644 index 6f56e2b44c..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/common/ItemFilterQuery.java +++ /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/ - */ -package org.dspace.rest.common; - -import javax.ws.rs.WebApplicationException; -import javax.xml.bind.annotation.XmlAttribute; -import javax.xml.bind.annotation.XmlRootElement; - -import org.apache.logging.log4j.Logger; - -/** - * Metadata Query for DSpace Items using the REST API - * - * @author Terry Brady, Georgetown University - */ -@XmlRootElement(name = "item-filter-query") -public class ItemFilterQuery { - Logger log = org.apache.logging.log4j.LogManager.getLogger(ItemFilterQuery.class); - - private String field = ""; - private String operation = ""; - private String value = ""; - - public ItemFilterQuery() { - } - - /** - * Construct a metadata query for DSpace items - * - * @param field Name of the metadata field to query - * @param operation Operation to perform on a metadata field - * @param value Query value. - * @throws WebApplicationException Runtime exception for applications. - */ - public ItemFilterQuery(String field, String operation, String value) throws WebApplicationException { - setup(field, operation, value); - } - - private void setup(String field, String operation, String value) { - this.setField(field); - this.setOperation(operation); - this.setValue(value); - } - - @XmlAttribute(name = "field") - public String getField() { - return field; - } - - public void setField(String field) { - this.field = field; - } - - @XmlAttribute(name = "operation") - public String getOperation() { - return operation; - } - - public void setOperation(String operation) { - this.operation = operation; - } - - @XmlAttribute(name = "value") - public String getValue() { - return value; - } - - public void setValue(String value) { - this.value = value; - } - -} diff --git a/dspace-rest/src/main/java/org/dspace/rest/common/MetadataEntry.java b/dspace-rest/src/main/java/org/dspace/rest/common/MetadataEntry.java deleted file mode 100644 index 27f31cec9c..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/common/MetadataEntry.java +++ /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/ - */ -package org.dspace.rest.common; - -import java.util.regex.Pattern; -import javax.xml.bind.annotation.XmlRootElement; - -/** - * @author peterdietz, Rostislav Novak (Computing and Information Centre, CTU in - * Prague) - */ -@XmlRootElement(name = "metadataentry") -public class MetadataEntry { - String key; - - String value; - - String language; - - public MetadataEntry() { - } - - public MetadataEntry(String key, String value, String language) { - this.key = key; - this.value = value; - this.language = language; - } - - public String getValue() { - return value; - } - - public void setValue(String value) { - this.value = value; - } - - public String getKey() { - return key; - } - - public void setKey(String key) { - this.key = key; - } - - public String getLanguage() { - return language; - } - - public void setLanguage(String language) { - this.language = language; - } - - public String getSchema() { - String[] fieldPieces = key.split(Pattern.quote(".")); - return fieldPieces[0]; - } - - public String getElement() { - String[] fieldPieces = key.split(Pattern.quote(".")); - return fieldPieces[1]; - } - - public String getQualifier() { - String[] fieldPieces = key.split(Pattern.quote(".")); - if (fieldPieces.length == 3) { - return fieldPieces[2]; - } else { - return null; - } - } - -} diff --git a/dspace-rest/src/main/java/org/dspace/rest/common/MetadataField.java b/dspace-rest/src/main/java/org/dspace/rest/common/MetadataField.java deleted file mode 100644 index 3688b5b8ca..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/common/MetadataField.java +++ /dev/null @@ -1,132 +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.rest.common; - -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import javax.ws.rs.WebApplicationException; -import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlRootElement; - -import org.dspace.core.Context; - -/** - * Metadata field representation - * - * @author Terry Brady, Georgetown University. - */ -@XmlRootElement(name = "field") -public class MetadataField { - private int fieldId; - private String name; - private String element; - private String qualifier; - private String description; - - private MetadataSchema parentSchema; - - @XmlElement(required = true) - private ArrayList expand = new ArrayList(); - - public MetadataField() { - } - - public MetadataField(org.dspace.content.MetadataSchema schema, org.dspace.content.MetadataField field, - String expand, Context context) throws SQLException, WebApplicationException { - setup(schema, field, expand, context); - } - - private void setup(org.dspace.content.MetadataSchema schema, org.dspace.content.MetadataField field, String expand, - Context context) throws SQLException { - List expandFields = new ArrayList(); - if (expand != null) { - expandFields = Arrays.asList(expand.split(",")); - } - StringBuilder sb = new StringBuilder(); - sb.append(schema.getName()); - sb.append("."); - sb.append(field.getElement()); - if (field.getQualifier() != null) { - sb.append("."); - sb.append(field.getQualifier()); - } - - this.setName(sb.toString()); - this.setFieldId(field.getID()); - this.setElement(field.getElement()); - this.setQualifier(field.getQualifier()); - this.setDescription(field.getScopeNote()); - - if (expandFields.contains("parentSchema") || expandFields.contains("all")) { - this.addExpand("parentSchema"); - parentSchema = new MetadataSchema(schema, "", context); - } - } - - public void setParentSchema(MetadataSchema schema) { - this.parentSchema = schema; - } - - public MetadataSchema getParentSchema() { - return this.parentSchema; - } - - public void setFieldId(int fieldId) { - this.fieldId = fieldId; - } - - public void setName(String name) { - this.name = name; - } - - public void setElement(String element) { - this.element = element; - } - - public void setQualifier(String qualifier) { - this.qualifier = qualifier; - } - - public void setDescription(String description) { - this.description = description; - } - - public int getFieldId() { - return fieldId; - } - - public String getName() { - return name; - } - - public String getQualifier() { - return qualifier; - } - - public String getElement() { - return element; - } - - public String getDescription() { - return description; - } - - public List getExpand() { - return expand; - } - - public void setExpand(ArrayList expand) { - this.expand = expand; - } - - public void addExpand(String expandableAttribute) { - this.expand.add(expandableAttribute); - } -} diff --git a/dspace-rest/src/main/java/org/dspace/rest/common/MetadataSchema.java b/dspace-rest/src/main/java/org/dspace/rest/common/MetadataSchema.java deleted file mode 100644 index 4b1e29fea2..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/common/MetadataSchema.java +++ /dev/null @@ -1,105 +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.rest.common; - -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import javax.ws.rs.WebApplicationException; -import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlRootElement; - -import org.dspace.content.factory.ContentServiceFactory; -import org.dspace.content.service.MetadataFieldService; -import org.dspace.core.Context; - -/** - * Metadata schema representation - * - * @author Terry Brady, Georgetown University. - */ -@XmlRootElement(name = "schema") -public class MetadataSchema { - protected MetadataFieldService metadataFieldService = ContentServiceFactory.getInstance().getMetadataFieldService(); - - private int schemaID; - private String prefix; - private String namespace; - - @XmlElement(required = true) - private ArrayList expand = new ArrayList(); - - @XmlElement(name = "fields", required = true) - private List fields = new ArrayList(); - - public MetadataSchema() { - } - - public MetadataSchema(org.dspace.content.MetadataSchema schema, String expand, Context context) - throws SQLException, WebApplicationException { - setup(schema, expand, context); - } - - private void setup(org.dspace.content.MetadataSchema schema, String expand, Context context) throws SQLException { - List expandFields = new ArrayList(); - if (expand != null) { - expandFields = Arrays.asList(expand.split(",")); - } - this.setSchemaID(schema.getID()); - this.setPrefix(schema.getName()); - this.setNamespace(schema.getNamespace()); - if (expandFields.contains("fields") || expandFields.contains("all")) { - List fields = metadataFieldService.findAllInSchema(context, schema); - this.addExpand("fields"); - for (org.dspace.content.MetadataField field : fields) { - this.fields.add(new MetadataField(schema, field, "", context)); - } - } - } - - public void setPrefix(String prefix) { - this.prefix = prefix; - } - - public void setNamespace(String namespace) { - this.namespace = namespace; - } - - public String getPrefix() { - return prefix; - } - - public String getNamespace() { - return namespace; - } - - public int getSchemaID() { - return this.schemaID; - } - - public void setSchemaID(int schemaID) { - this.schemaID = schemaID; - } - - public List getMetadataFields() { - return fields; - } - - public List getExpand() { - return expand; - } - - public void setExpand(ArrayList expand) { - this.expand = expand; - } - - public void addExpand(String expandableAttribute) { - this.expand.add(expandableAttribute); - } -} diff --git a/dspace-rest/src/main/java/org/dspace/rest/common/Report.java b/dspace-rest/src/main/java/org/dspace/rest/common/Report.java deleted file mode 100644 index dcaf7d269e..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/common/Report.java +++ /dev/null @@ -1,47 +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.rest.common; - -import javax.xml.bind.annotation.XmlRootElement; - -/** - * Used to handle/determine status of REST API. - * Mainly to know your authentication status - */ -@XmlRootElement(name = "report") -public class Report { - private String nickname; - private String url; - - public Report() { - setNickname("na"); - setUrl(""); - } - - - public Report(String nickname, String url) { - setNickname(nickname); - setUrl(url); - } - - public String getUrl() { - return this.url; - } - - public String getNickname() { - return this.nickname; - } - - public void setUrl(String url) { - this.url = url; - } - - public void setNickname(String nickname) { - this.nickname = nickname; - } -} diff --git a/dspace-rest/src/main/java/org/dspace/rest/common/ResourcePolicy.java b/dspace-rest/src/main/java/org/dspace/rest/common/ResourcePolicy.java deleted file mode 100644 index 366bd5fc3a..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/common/ResourcePolicy.java +++ /dev/null @@ -1,195 +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.rest.common; - -import java.util.Date; -import javax.xml.bind.annotation.XmlRootElement; - -import com.fasterxml.jackson.annotation.JsonIgnore; -import org.dspace.eperson.EPerson; -import org.dspace.eperson.Group; - -@XmlRootElement(name = "resourcepolicy") -public class ResourcePolicy { - - public enum Action { - READ, WRITE, DELETE; - } - - private Integer id; - private Action action; - private String epersonId; //UUID - private String groupId; //UUID - private String resourceId; //UUID - private String resourceType; - private String rpDescription; - private String rpName; - private String rpType; - private Date startDate; - private Date endDate; - - public ResourcePolicy() { - } - - public ResourcePolicy(org.dspace.authorize.ResourcePolicy dspacePolicy) { - this.id = dspacePolicy.getID(); - - switch (dspacePolicy.getAction()) { - case org.dspace.core.Constants.READ: - this.action = Action.READ; - break; - case org.dspace.core.Constants.WRITE: - this.action = Action.WRITE; - break; - case org.dspace.core.Constants.DELETE: - this.action = Action.DELETE; - break; - default: - break; - } - - EPerson ePerson = dspacePolicy.getEPerson(); - if (ePerson != null) { - this.epersonId = ePerson.getID().toString(); - } - - Group group = dspacePolicy.getGroup(); - if (group != null) { - this.groupId = group.getID().toString(); - } - - this.resourceId = dspacePolicy.getdSpaceObject().getID().toString(); - this.rpDescription = dspacePolicy.getRpDescription(); - this.rpName = dspacePolicy.getRpName(); - this.rpType = dspacePolicy.getRpType(); - this.startDate = dspacePolicy.getStartDate(); - this.endDate = dspacePolicy.getEndDate(); - switch (dspacePolicy.getdSpaceObject().getType()) { - case org.dspace.core.Constants.BITSTREAM: - this.resourceType = "bitstream"; - break; - case org.dspace.core.Constants.ITEM: - this.resourceType = "item"; - break; - case org.dspace.core.Constants.COLLECTION: - this.resourceType = "collection"; - break; - case org.dspace.core.Constants.COMMUNITY: - this.resourceType = "community"; - break; - case org.dspace.core.Constants.BUNDLE: - this.resourceType = "bundle"; - break; - default: - this.resourceType = ""; - break; - } - } - - public Integer getId() { - return id; - } - - public void setId(Integer id) { - this.id = id; - } - - public Action getAction() { - return action; - } - - @JsonIgnore - public int getActionInt() { - switch (action) { - case READ: - return org.dspace.core.Constants.READ; - case WRITE: - return org.dspace.core.Constants.WRITE; - case DELETE: - return org.dspace.core.Constants.DELETE; - default: - return org.dspace.core.Constants.READ; - } - } - - public void setAction(Action action) { - this.action = action; - } - - public String getEpersonId() { - return epersonId; - } - - public void setEpersonId(String epersonId) { - this.epersonId = epersonId; - } - - public String getGroupId() { - return groupId; - } - - public void setGroupId(String groupId) { - this.groupId = groupId; - } - - public String getResourceId() { - return resourceId; - } - - public void setResourceId(String resourceId) { - this.resourceId = resourceId; - } - - public String getResourceType() { - return resourceType; - } - - public void setResourceType(String resourceType) { - this.resourceType = resourceType; - } - - public String getRpDescription() { - return rpDescription; - } - - public void setRpDescription(String rpDescription) { - this.rpDescription = rpDescription; - } - - public String getRpName() { - return rpName; - } - - public void setRpName(String rpName) { - this.rpName = rpName; - } - - public String getRpType() { - return rpType; - } - - public void setRpType(String rpType) { - this.rpType = rpType; - } - - public Date getStartDate() { - return startDate; - } - - public void setStartDate(Date startDate) { - this.startDate = startDate; - } - - public Date getEndDate() { - return endDate; - } - - public void setEndDate(Date endDate) { - this.endDate = endDate; - } -} diff --git a/dspace-rest/src/main/java/org/dspace/rest/common/Status.java b/dspace-rest/src/main/java/org/dspace/rest/common/Status.java deleted file mode 100644 index cdbb8210b9..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/common/Status.java +++ /dev/null @@ -1,111 +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.rest.common; - -import javax.xml.bind.annotation.XmlRootElement; - -import com.fasterxml.jackson.annotation.JsonProperty; -import org.dspace.app.util.Util; -import org.dspace.eperson.EPerson; - -/** - * Determine status of REST API - is it running, accessible and without errors?. - * Find out API version (DSpace major version) and DSpace source version. - * Find out your authentication status. - */ -@XmlRootElement(name = "status") -public class Status { - private boolean okay; - private boolean authenticated; - private String email; - private String fullname; - private String sourceVersion; - private String apiVersion; - - public Status() { - setOkay(true); - - setSourceVersion(Util.getSourceVersion()); - String[] version = Util.getSourceVersion().split("\\."); - setApiVersion(version[0]); // major version - - setAuthenticated(false); - } - - public Status(String email, String fullname) { - setOkay(true); - setAuthenticated(true); - setEmail(email); - setFullname(fullname); - } - - public Status(EPerson eperson) { - setOkay(true); - if (eperson != null) { - setAuthenticated(true); - setEmail(eperson.getEmail()); - setFullname(eperson.getFullName()); - } else { - setAuthenticated(false); - } - } - - @JsonProperty("okay") - public boolean isOkay() { - return this.okay; - } - - public void setOkay(boolean okay) { - this.okay = okay; - } - - @JsonProperty("authenticated") - public boolean isAuthenticated() { - return authenticated; - } - - public void setAuthenticated(boolean authenticated) { - this.authenticated = authenticated; - } - - @JsonProperty("email") - public String getEmail() { - return email; - } - - public void setEmail(String email) { - this.email = email; - } - - @JsonProperty("fullname") - public String getFullname() { - return fullname; - } - - public void setFullname(String fullname) { - this.fullname = fullname; - } - - @JsonProperty("sourceVersion") - public String getSourceVersion() { - return this.sourceVersion; - } - - public void setSourceVersion(String sourceVersion) { - this.sourceVersion = sourceVersion; - } - - @JsonProperty("apiVersion") - public String getApiVersion() { - return this.apiVersion; - } - - public void setApiVersion(String apiVersion) { - this.apiVersion = apiVersion; - } -} diff --git a/dspace-rest/src/main/java/org/dspace/rest/exceptions/ContextException.java b/dspace-rest/src/main/java/org/dspace/rest/exceptions/ContextException.java deleted file mode 100644 index 817b662f73..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/exceptions/ContextException.java +++ /dev/null @@ -1,31 +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.rest.exceptions; - -/** - * Simple exception which only encapsulate classic exception. This exception is - * only for exceptions caused by creating context. - * - * @author Rostislav Novak (Computing and Information Centre, CTU in Prague) - */ -public class ContextException extends Exception { - - private static final long serialVersionUID = 1L; - - Exception causedBy; - - public ContextException(String message, Exception causedBy) { - super(message); - this.causedBy = causedBy; - } - - public Exception getCausedBy() { - return causedBy; - } - -} diff --git a/dspace-rest/src/main/java/org/dspace/rest/filter/ItemFilterDefs.java b/dspace-rest/src/main/java/org/dspace/rest/filter/ItemFilterDefs.java deleted file mode 100644 index 0712ec546d..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/filter/ItemFilterDefs.java +++ /dev/null @@ -1,159 +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.rest.filter; - -import org.dspace.content.Item; -import org.dspace.core.Context; - -/** - * Define the set of use cases for filtering items of interest through the REST API. - * - * @author Terry Brady, Georgetown University - */ - -public class ItemFilterDefs implements ItemFilterList { - public static final String CAT_ITEM = "Item Property Filters"; - public static final String CAT_BASIC = "Basic Bitstream Filters"; - public static final String CAT_MIME = "Bitstream Filters by MIME Type"; - - public static final String[] MIMES_PDF = {"application/pdf"}; - public static final String[] MIMES_JPG = {"image/jpeg"}; - - - private enum EnumItemFilterDefs implements ItemFilterTest { - is_item("Is Item - always true", null, CAT_ITEM) { - public boolean testItem(Context context, Item item) { - return true; - } - }, - is_withdrawn("Withdrawn Items", null, CAT_ITEM) { - public boolean testItem(Context context, Item item) { - return item.isWithdrawn(); - } - }, - is_not_withdrawn("Available Items - Not Withdrawn", null, CAT_ITEM) { - public boolean testItem(Context context, Item item) { - return !item.isWithdrawn(); - } - }, - is_discoverable("Discoverable Items - Not Private", null, CAT_ITEM) { - public boolean testItem(Context context, Item item) { - return item.isDiscoverable(); - } - }, - is_not_discoverable("Not Discoverable - Private Item", null, CAT_ITEM) { - public boolean testItem(Context context, Item item) { - return !item.isDiscoverable(); - } - }, - has_multiple_originals("Item has Multiple Original Bitstreams", null, CAT_BASIC) { - public boolean testItem(Context context, Item item) { - return ItemFilterUtil.countOriginalBitstream(item) > 1; - } - }, - has_no_originals("Item has No Original Bitstreams", null, CAT_BASIC) { - public boolean testItem(Context context, Item item) { - return ItemFilterUtil.countOriginalBitstream(item) == 0; - } - }, - has_one_original("Item has One Original Bitstream", null, CAT_BASIC) { - public boolean testItem(Context context, Item item) { - return ItemFilterUtil.countOriginalBitstream(item) == 1; - } - }, - has_doc_original("Item has a Doc Original Bitstream (PDF, Office, Text, HTML, XML, etc)", null, CAT_MIME) { - public boolean testItem(Context context, Item item) { - return ItemFilterUtil - .countOriginalBitstreamMime(context, item, ItemFilterUtil.getDocumentMimeTypes()) > 0; - } - }, - has_image_original("Item has an Image Original Bitstream", null, CAT_MIME) { - public boolean testItem(Context context, Item item) { - return ItemFilterUtil.countOriginalBitstreamMimeStartsWith(context, item, "image") > 0; - } - }, - has_unsupp_type("Has Other Bitstream Types (not Doc or Image)", null, ItemFilterDefs.CAT_MIME) { - public boolean testItem(Context context, Item item) { - int bitCount = ItemFilterUtil.countOriginalBitstream(item); - if (bitCount == 0) { - return false; - } - int docCount = ItemFilterUtil - .countOriginalBitstreamMime(context, item, ItemFilterUtil.getDocumentMimeTypes()); - int imgCount = ItemFilterUtil.countOriginalBitstreamMimeStartsWith(context, item, "image"); - return (bitCount - docCount - imgCount) > 0; - } - }, - has_mixed_original("Item has multiple types of Original Bitstreams (Doc, Image, Other)", null, CAT_MIME) { - public boolean testItem(Context context, Item item) { - int countBit = ItemFilterUtil.countOriginalBitstream(item); - if (countBit <= 1) { - return false; - } - int countDoc = ItemFilterUtil - .countOriginalBitstreamMime(context, item, ItemFilterUtil.getDocumentMimeTypes()); - if (countDoc > 0) { - return countDoc != countBit; - } - int countImg = ItemFilterUtil.countOriginalBitstreamMimeStartsWith(context, item, "image"); - if (countImg > 0) { - return countImg != countBit; - } - return false; - } - }, - has_pdf_original("Item has a PDF Original Bitstream", null, CAT_MIME) { - public boolean testItem(Context context, Item item) { - return ItemFilterUtil.countOriginalBitstreamMime(context, item, MIMES_PDF) > 0; - } - }, - has_jpg_original("Item has JPG Original Bitstream", null, CAT_MIME) { - public boolean testItem(Context context, Item item) { - return ItemFilterUtil.countOriginalBitstreamMime(context, item, MIMES_JPG) > 0; - } - },; - - private String title = null; - private String description = null; - - private EnumItemFilterDefs(String title, String description, String category) { - this.title = title; - this.description = description; - this.category = category; - } - - private EnumItemFilterDefs() { - this(null, null, null); - } - - public String getName() { - return name(); - } - - public String getTitle() { - return title; - } - - public String getDescription() { - return description; - } - - private String category = null; - - public String getCategory() { - return category; - } - } - - public ItemFilterDefs() { - } - - public ItemFilterTest[] getFilters() { - return EnumItemFilterDefs.values(); - } -} diff --git a/dspace-rest/src/main/java/org/dspace/rest/filter/ItemFilterDefsMeta.java b/dspace-rest/src/main/java/org/dspace/rest/filter/ItemFilterDefsMeta.java deleted file mode 100644 index 96a866357d..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/filter/ItemFilterDefsMeta.java +++ /dev/null @@ -1,177 +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.rest.filter; - -import java.util.regex.Pattern; - -import org.apache.logging.log4j.Logger; -import org.dspace.content.Item; -import org.dspace.content.factory.ContentServiceFactory; -import org.dspace.content.service.ItemService; -import org.dspace.core.Context; -import org.dspace.services.factory.DSpaceServicesFactory; - -/** - * Define the set of use cases for filtering items of interest through the REST API. - * - * @author Terry Brady, Georgetown University - */ - -public class ItemFilterDefsMeta implements ItemFilterList { - protected static ItemService itemService = ContentServiceFactory.getInstance().getItemService(); - static Logger log = org.apache.logging.log4j.LogManager.getLogger(ItemFilterDefsMeta.class); - - public static final String CAT_META_GEN = "General Metadata Filters"; - public static final String CAT_META_SPEC = "Specific Metadata Filters"; - public static final String CAT_MOD = "Recently Modified"; - - private enum EnumItemFilterDefs implements ItemFilterTest { - has_no_title("Has no dc.title", null, CAT_META_SPEC) { - public boolean testItem(Context context, Item item) { - return itemService.getMetadataByMetadataString(item, "dc.title").size() == 0; - } - }, - has_no_uri("Has no dc.identifier.uri", null, CAT_META_SPEC) { - public boolean testItem(Context context, Item item) { - return itemService.getMetadataByMetadataString(item, "dc.identifier.uri").size() == 0; - } - }, - has_mult_uri("Has multiple dc.identifier.uri", null, CAT_META_SPEC) { - public boolean testItem(Context context, Item item) { - return itemService.getMetadataByMetadataString(item, "dc.identifier.uri").size() > 1; - } - }, - has_compound_subject("Has compound subject", null, CAT_META_SPEC) { - public boolean testItem(Context context, Item item) { - String regex = DSpaceServicesFactory.getInstance().getConfigurationService() - .getProperty("rest.report-regex-compound-subject"); - return ItemFilterUtil.hasMetadataMatch(item, "dc.subject.*", Pattern.compile(regex)); - } - }, - has_compound_author("Has compound author", null, CAT_META_SPEC) { - public boolean testItem(Context context, Item item) { - String regex = DSpaceServicesFactory.getInstance().getConfigurationService() - .getProperty("rest.report-regex-compound-author"); - return ItemFilterUtil - .hasMetadataMatch(item, "dc.creator,dc.contributor.author", Pattern.compile(regex)); - } - }, - has_empty_metadata("Has empty metadata", null, CAT_META_GEN) { - public boolean testItem(Context context, Item item) { - return ItemFilterUtil.hasMetadataMatch(item, "*", Pattern.compile("^\\s*$")); - } - }, - has_unbreaking_metadata("Has unbreaking metadata", null, CAT_META_GEN) { - public boolean testItem(Context context, Item item) { - String regex = DSpaceServicesFactory.getInstance().getConfigurationService() - .getProperty("rest.report-regex-unbreaking"); - return ItemFilterUtil.hasMetadataMatch(item, "*", Pattern.compile(regex)); - } - }, - has_long_metadata("Has long metadata field", null, CAT_META_GEN) { - public boolean testItem(Context context, Item item) { - String regex = DSpaceServicesFactory.getInstance().getConfigurationService() - .getProperty("rest.report-regex-long"); - return ItemFilterUtil.hasMetadataMatch(item, "*", Pattern.compile(regex)); - } - }, - has_xml_entity("Has XML entity in metadata", null, CAT_META_GEN) { - public boolean testItem(Context context, Item item) { - String regex = DSpaceServicesFactory.getInstance().getConfigurationService() - .getProperty("rest.report-regex-xml-entity"); - return ItemFilterUtil.hasMetadataMatch(item, "*", Pattern.compile(regex)); - } - }, - has_non_ascii("Has non-ascii in metadata", null, CAT_META_GEN) { - public boolean testItem(Context context, Item item) { - String regex = DSpaceServicesFactory.getInstance().getConfigurationService() - .getProperty("rest.report-regex-non-ascii"); - return ItemFilterUtil.hasMetadataMatch(item, "*", Pattern.compile(regex)); - } - }, - has_desc_url("Has url in description", null, CAT_META_SPEC) { - public boolean testItem(Context context, Item item) { - String regex = DSpaceServicesFactory.getInstance().getConfigurationService() - .getProperty("rest.report-regex-url"); - return ItemFilterUtil.hasMetadataMatch(item, "dc.description.*", Pattern.compile(regex)); - } - }, - has_fulltext_provenance("Has fulltext in provenance", null, CAT_META_SPEC) { - public boolean testItem(Context context, Item item) { - String regex = DSpaceServicesFactory.getInstance().getConfigurationService() - .getProperty("rest.report-regex-fulltext"); - return ItemFilterUtil.hasMetadataMatch(item, "dc.description.provenance", Pattern.compile(regex)); - } - }, - no_fulltext_provenance("Doesn't have fulltext in provenance", null, CAT_META_SPEC) { - public boolean testItem(Context context, Item item) { - String regex = DSpaceServicesFactory.getInstance().getConfigurationService() - .getProperty("rest.report-regex-fulltext"); - return !ItemFilterUtil.hasMetadataMatch(item, "dc.description.provenance", Pattern.compile(regex)); - } - }, - mod_last_day("Modified in last 1 day", null, CAT_MOD) { - public boolean testItem(Context context, Item item) { - return ItemFilterUtil.recentlyModified(item, 1); - } - }, - mod_last_7_days("Modified in last 7 days", null, CAT_MOD) { - public boolean testItem(Context context, Item item) { - return ItemFilterUtil.recentlyModified(item, 7); - } - }, - mod_last_30_days("Modified in last 30 days", null, CAT_MOD) { - public boolean testItem(Context context, Item item) { - return ItemFilterUtil.recentlyModified(item, 30); - } - }, - mod_last_90_days("Modified in last 60 days", null, CAT_MOD) { - public boolean testItem(Context context, Item item) { - return ItemFilterUtil.recentlyModified(item, 60); - } - },; - - private String title = null; - private String description = null; - - private EnumItemFilterDefs(String title, String description, String category) { - this.title = title; - this.description = description; - this.category = category; - } - - private EnumItemFilterDefs() { - this(null, null, null); - } - - public String getName() { - return name(); - } - - public String getTitle() { - return title; - } - - public String getDescription() { - return description; - } - - private String category = null; - - public String getCategory() { - return category; - } - } - - public ItemFilterDefsMeta() { - } - - public ItemFilterTest[] getFilters() { - return EnumItemFilterDefs.values(); - } -} diff --git a/dspace-rest/src/main/java/org/dspace/rest/filter/ItemFilterDefsMisc.java b/dspace-rest/src/main/java/org/dspace/rest/filter/ItemFilterDefsMisc.java deleted file mode 100644 index 5b5cc4b12d..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/filter/ItemFilterDefsMisc.java +++ /dev/null @@ -1,206 +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.rest.filter; - -import java.util.List; - -import org.dspace.content.Item; -import org.dspace.core.Context; -import org.dspace.rest.filter.ItemFilterUtil.BundleName; -import org.dspace.services.factory.DSpaceServicesFactory; - -/** - * Define the set of use cases for filtering items of interest through the REST API. - * - * @author Terry Brady, Georgetown University - */ - -public class ItemFilterDefsMisc implements ItemFilterList { - public static final String CAT_MISC = "Bitstream Bundle Filters"; - public static final String CAT_MIME_SUPP = "Supported MIME Type Filters"; - - private enum EnumItemFilterDefs implements ItemFilterTest { - has_only_supp_image_type("Item Image Bitstreams are Supported", null, CAT_MIME_SUPP) { - public boolean testItem(Context context, Item item) { - int imageCount = ItemFilterUtil.countOriginalBitstreamMimeStartsWith(context, item, "image/"); - if (imageCount == 0) { - return false; - } - int suppImageCount = ItemFilterUtil - .countOriginalBitstreamMime(context, item, ItemFilterUtil.getSupportedImageMimeTypes()); - return (imageCount == suppImageCount); - } - }, - has_unsupp_image_type("Item has Image Bitstream that is Unsupported", null, CAT_MIME_SUPP) { - public boolean testItem(Context context, Item item) { - int imageCount = ItemFilterUtil.countOriginalBitstreamMimeStartsWith(context, item, "image/"); - if (imageCount == 0) { - return false; - } - int suppImageCount = ItemFilterUtil - .countOriginalBitstreamMime(context, item, ItemFilterUtil.getSupportedImageMimeTypes()); - return (imageCount - suppImageCount) > 0; - } - }, - has_only_supp_doc_type("Item Document Bitstreams are Supported", null, CAT_MIME_SUPP) { - public boolean testItem(Context context, Item item) { - int docCount = ItemFilterUtil - .countOriginalBitstreamMime(context, item, ItemFilterUtil.getDocumentMimeTypes()); - if (docCount == 0) { - return false; - } - int suppDocCount = ItemFilterUtil - .countOriginalBitstreamMime(context, item, ItemFilterUtil.getSupportedDocumentMimeTypes()); - return docCount == suppDocCount; - } - }, - has_unsupp_doc_type("Item has Document Bitstream that is Unsupported", null, CAT_MIME_SUPP) { - public boolean testItem(Context context, Item item) { - int docCount = ItemFilterUtil - .countOriginalBitstreamMime(context, item, ItemFilterUtil.getDocumentMimeTypes()); - if (docCount == 0) { - return false; - } - int suppDocCount = ItemFilterUtil - .countOriginalBitstreamMime(context, item, ItemFilterUtil.getSupportedDocumentMimeTypes()); - return (docCount - suppDocCount) > 0; - } - }, - has_small_pdf("Has unusually small PDF", null, ItemFilterDefs.CAT_MIME) { - public boolean testItem(Context context, Item item) { - return ItemFilterUtil - .countBitstreamSmallerThanMinSize(context, BundleName.ORIGINAL, item, ItemFilterDefs.MIMES_PDF, - "rest.report-pdf-min-size") > 0; - } - }, - has_large_pdf("Has unusually large PDF", null, ItemFilterDefs.CAT_MIME) { - public boolean testItem(Context context, Item item) { - return ItemFilterUtil - .countBitstreamLargerThanMaxSize(context, BundleName.ORIGINAL, item, ItemFilterDefs.MIMES_PDF, - "rest.report-pdf-max-size") > 0; - } - }, - has_unsupported_bundle("Has bitstream in an unsuppored bundle", null, CAT_MISC) { - public boolean testItem(Context context, Item item) { - String[] bundleList = DSpaceServicesFactory.getInstance().getConfigurationService() - .getArrayProperty("rest.report-supp-bundles"); - return ItemFilterUtil.hasUnsupportedBundle(item, bundleList); - } - }, - has_small_thumbnail("Has unusually small thumbnail", null, CAT_MISC) { - public boolean testItem(Context context, Item item) { - return ItemFilterUtil - .countBitstreamSmallerThanMinSize(context, BundleName.THUMBNAIL, item, ItemFilterDefs.MIMES_JPG, - "rest.report-thumbnail-min-size") > 0; - } - }, - has_doc_without_text("Has document bitstream without TEXT item", null, ItemFilterDefs.CAT_MIME) { - public boolean testItem(Context context, Item item) { - int countDoc = ItemFilterUtil - .countOriginalBitstreamMime(context, item, ItemFilterUtil.getDocumentMimeTypes()); - if (countDoc == 0) { - return false; - } - int countText = ItemFilterUtil.countBitstream(BundleName.TEXT, item); - return countDoc > countText; - } - }, - has_original_without_thumbnail("Has original bitstream without thumbnail", null, CAT_MISC) { - public boolean testItem(Context context, Item item) { - int countBit = ItemFilterUtil.countOriginalBitstream(item); - if (countBit == 0) { - return false; - } - int countThumb = ItemFilterUtil.countBitstream(BundleName.THUMBNAIL, item); - return countBit > countThumb; - } - }, - has_invalid_thumbnail_name("Has invalid thumbnail name (assumes one thumbnail for each original)", null, - CAT_MISC) { - public boolean testItem(Context context, Item item) { - List originalNames = ItemFilterUtil.getBitstreamNames(BundleName.ORIGINAL, item); - List thumbNames = ItemFilterUtil.getBitstreamNames(BundleName.THUMBNAIL, item); - if (thumbNames.size() != originalNames.size()) { - return false; - } - for (String name : originalNames) { - if (!thumbNames.contains(name + ".jpg")) { - return true; - } - } - return false; - } - }, - has_non_generated_thumb("Has non generated thumbnail", null, CAT_MISC) { - public boolean testItem(Context context, Item item) { - String[] generatedThumbDesc = DSpaceServicesFactory.getInstance().getConfigurationService() - .getArrayProperty("rest.report-gen-thumbnail-desc"); - int countThumb = ItemFilterUtil.countBitstream(BundleName.THUMBNAIL, item); - if (countThumb == 0) { - return false; - } - int countGen = ItemFilterUtil.countBitstreamByDesc(BundleName.THUMBNAIL, item, generatedThumbDesc); - return (countThumb > countGen); - } - }, - no_license("Doesn't have a license", null, CAT_MISC) { - public boolean testItem(Context context, Item item) { - return ItemFilterUtil.countBitstream(BundleName.LICENSE, item) == 0; - } - }, - has_license_documentation("Has documentation in the license bundle", null, CAT_MISC) { - public boolean testItem(Context context, Item item) { - List names = ItemFilterUtil.getBitstreamNames(BundleName.LICENSE, item); - for (String name : names) { - if (!name.equals("license.txt")) { - return true; - } - } - return false; - } - },; - - private String title = null; - private String description = null; - - private EnumItemFilterDefs(String title, String description, String category) { - this.title = title; - this.description = description; - this.category = category; - } - - private EnumItemFilterDefs() { - this(null, null, null); - } - - public String getName() { - return name(); - } - - public String getTitle() { - return title; - } - - public String getDescription() { - return description; - } - - private String category = null; - - public String getCategory() { - return category; - } - } - - public ItemFilterDefsMisc() { - } - - public ItemFilterTest[] getFilters() { - return EnumItemFilterDefs.values(); - } -} diff --git a/dspace-rest/src/main/java/org/dspace/rest/filter/ItemFilterDefsPerm.java b/dspace-rest/src/main/java/org/dspace/rest/filter/ItemFilterDefsPerm.java deleted file mode 100644 index 9e80f31196..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/filter/ItemFilterDefsPerm.java +++ /dev/null @@ -1,138 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.rest.filter; - -import java.sql.SQLException; - -import org.apache.logging.log4j.Logger; -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; -import org.dspace.core.Context; -import org.dspace.rest.filter.ItemFilterUtil.BundleName; - -/** - * Define the set of use cases for filtering items of interest through the REST API. - * - * @author Terry Brady, Georgetown University - */ -public class ItemFilterDefsPerm implements ItemFilterList { - protected static AuthorizeService authorizeService = AuthorizeServiceFactory.getInstance().getAuthorizeService(); - public static final String CAT_PERM = "Perimission Filters"; - private static Logger log = org.apache.logging.log4j.LogManager.getLogger(ItemFilterDefsPerm.class); - - public ItemFilterDefsPerm() { - } - - public enum EnumItemFilterPermissionDefs implements ItemFilterTest { - has_restricted_original("Item has Restricted Original Bitstream", - "Item has at least one original bitstream that is not accessible to Anonymous user", - CAT_PERM) { - public boolean testItem(Context context, Item item) { - try { - for (Bundle bundle : item.getBundles()) { - if (!bundle.getName().equals(BundleName.ORIGINAL.name())) { - continue; - } - for (Bitstream bit : bundle.getBitstreams()) { - if (!authorizeService - .authorizeActionBoolean(getAnonContext(), bit, org.dspace.core.Constants.READ)) { - return true; - } - } - } - } catch (SQLException e) { - ItemFilterDefsPerm.log.warn("SQL Exception testing original bitstream access " + e.getMessage(), e); - } - return false; - } - }, - has_restricted_thumbnail("Item has Restricted Thumbnail", - "Item has at least one thumbnail that is not accessible to Anonymous user", CAT_PERM) { - public boolean testItem(Context context, Item item) { - try { - for (Bundle bundle : item.getBundles()) { - if (!bundle.getName().equals(BundleName.THUMBNAIL.name())) { - continue; - } - for (Bitstream bit : bundle.getBitstreams()) { - if (!authorizeService - .authorizeActionBoolean(getAnonContext(), bit, org.dspace.core.Constants.READ)) { - return true; - } - } - } - } catch (SQLException e) { - ItemFilterDefsPerm.log - .warn("SQL Exception testing thumbnail bitstream access " + e.getMessage(), e); - } - return false; - } - }, - has_restricted_metadata("Item has Restricted Metadata", - "Item has metadata that is not accessible to Anonymous user", CAT_PERM) { - public boolean testItem(Context context, Item item) { - try { - return !authorizeService - .authorizeActionBoolean(getAnonContext(), item, org.dspace.core.Constants.READ); - } catch (SQLException e) { - ItemFilterDefsPerm.log.warn("SQL Exception testing item metadata access " + e.getMessage(), e); - return false; - } - } - },; - - private static Context anonContext; - - private static Context getAnonContext() { - if (anonContext == null) { - anonContext = new Context(); - } - return anonContext; - } - - - private String title = null; - private String description = null; - - private EnumItemFilterPermissionDefs(String title, String description, String category) { - this.title = title; - this.description = description; - this.category = category; - } - - private EnumItemFilterPermissionDefs() { - this(null, null, null); - } - - public String getName() { - return name(); - } - - public String getTitle() { - return title; - } - - public String getDescription() { - return description; - } - - private String category = null; - - public String getCategory() { - return category; - } - } - - @Override - public ItemFilterTest[] getFilters() { - return EnumItemFilterPermissionDefs.values(); - } -} diff --git a/dspace-rest/src/main/java/org/dspace/rest/filter/ItemFilterList.java b/dspace-rest/src/main/java/org/dspace/rest/filter/ItemFilterList.java deleted file mode 100644 index f6590e36f8..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/filter/ItemFilterList.java +++ /dev/null @@ -1,12 +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.rest.filter; - -public interface ItemFilterList { - public ItemFilterTest[] getFilters(); -} diff --git a/dspace-rest/src/main/java/org/dspace/rest/filter/ItemFilterSet.java b/dspace-rest/src/main/java/org/dspace/rest/filter/ItemFilterSet.java deleted file mode 100644 index f70bc9664d..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/filter/ItemFilterSet.java +++ /dev/null @@ -1,143 +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.rest.filter; - -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import javax.servlet.ServletContext; -import javax.ws.rs.WebApplicationException; - -import org.apache.logging.log4j.Logger; -import org.dspace.authorize.factory.AuthorizeServiceFactory; -import org.dspace.authorize.service.AuthorizeService; -import org.dspace.core.Context; -import org.dspace.rest.common.Item; -import org.dspace.rest.common.ItemFilter; - -/** - * The set of Item Filter Use Cases to apply to a collection of items. - * - * @author Terry Brady, Georgetown University - */ -public class ItemFilterSet { - protected AuthorizeService authorizeService = AuthorizeServiceFactory.getInstance().getAuthorizeService(); - static Logger log = org.apache.logging.log4j.LogManager.getLogger(ItemFilterSet.class); - - private List itemFilters; - private ItemFilter allFiltersFilter; - - /** - * Construct a set of Item Filters identified by a list string. - * - * @param filterList Comma separated list of filter names to include. - * Use {@link org.dspace.rest.common.ItemFilter#ALL} to retrieve all filters. - * @param reportItems If true, return item details. If false, return only counts of items. - */ - public ItemFilterSet(String filterList, boolean reportItems) { - log.debug(String.format("Create ItemFilterSet: %s", filterList)); - itemFilters = ItemFilter.getItemFilters(filterList, reportItems); - allFiltersFilter = ItemFilter.getAllFiltersFilter(itemFilters); - } - - /** - * Get the special filter that represents the intersection of all items in the Item Filter Set. - * - * @return the special Item Filter that contains items that satisfied every other Item Filter in the Item Filter Set - */ - public ItemFilter getAllFiltersFilter() { - return allFiltersFilter; - } - - /** - * Evaluate an item against the use cases in the Item Filter Set. - * - * If an item satisfies all items in the Item Filter Set, it should also ve added to the special all items filter. - * - * @param context Active DSpace Context - * @param item DSpace Object to evaluate - * @param restItem REST representation of the DSpace Object being evaluated - */ - public void testItem(Context context, org.dspace.content.Item item, Item restItem) { - boolean bAllTrue = true; - for (ItemFilter itemFilter : itemFilters) { - if (itemFilter.hasItemTest()) { - bAllTrue &= itemFilter.testItem(context, item, restItem); - } - } - if (bAllTrue && allFiltersFilter != null) { - allFiltersFilter.addItem(restItem); - } - } - - /** - * Get all of the Item Filters initialized into the Item Filter Set - * - * @return a list of Item Filters initialized into the Item Filter Set - */ - public List getItemFilters() { - return itemFilters; - } - - /** - * Evaluate a set of Items against the Item Filters in the Item Filter Set - * Current DSpace Context - * - * @param context Current DSpace Context - * @param servletContext Context of the servlet container. - * @param childItems Collection of Items to Evaluate - * @param save If true, save the details of each item that is evaluated - * @param expand List of item details to include in the results - * @return The number of items evaluated - * @throws WebApplicationException Runtime exception for applications. - * @throws SQLException An exception that provides information on a database access error or other - * errors. - */ - public int processSaveItems(Context context, ServletContext servletContext, - Iterator childItems, boolean save, String expand) - throws WebApplicationException, SQLException { - return processSaveItems(context, servletContext, childItems, new ArrayList(), save, expand); - } - - /** - * Evaluate a set of Items against the Item Filters in the Item Filter Set - * - * @param context Current DSpace Context - * @param servletContext Context of the servlet container. - * @param childItems Collection of Items to Evaluate - * @param items List of items to contain saved results - * @param save If true, save the details of each item that is evaluated - * @param expand List of item details to include in the results - * @return The number of items evaluated - * @throws WebApplicationException Runtime exception for applications. - * @throws SQLException An exception that provides information on a database access error or other - * errors. - */ - public int processSaveItems(Context context, ServletContext servletContext, - Iterator childItems, List items, boolean save, - String expand) throws WebApplicationException, SQLException { - int count = 0; - while (childItems.hasNext()) { - count++; - org.dspace.content.Item item = childItems.next(); - log.debug(item.getHandle() + " evaluate."); - if (authorizeService.authorizeActionBoolean(context, item, org.dspace.core.Constants.READ)) { - Item restItem = new Item(item, servletContext, expand, context); - if (save) { - items.add(restItem); - } - testItem(context, item, restItem); - } else { - log.debug(item.getHandle() + " not authorized - not included in result set."); - } - } - return count; - } - -} diff --git a/dspace-rest/src/main/java/org/dspace/rest/filter/ItemFilterTest.java b/dspace-rest/src/main/java/org/dspace/rest/filter/ItemFilterTest.java deleted file mode 100644 index 4ef2998e16..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/filter/ItemFilterTest.java +++ /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/ - */ -package org.dspace.rest.filter; - -import org.dspace.content.Item; -import org.dspace.core.Context; - -/** - * Item Filter Use Case Interface. - * Items will be evaluated against a set of filters. - * - * @author Terry Brady, Georgetown University - */ -public interface ItemFilterTest { - public String getName(); - - public String getTitle(); - - public String getDescription(); - - public String getCategory(); - - public boolean testItem(Context context, Item i); -} diff --git a/dspace-rest/src/main/java/org/dspace/rest/filter/ItemFilterUtil.java b/dspace-rest/src/main/java/org/dspace/rest/filter/ItemFilterUtil.java deleted file mode 100644 index ddb75f0db8..0000000000 --- a/dspace-rest/src/main/java/org/dspace/rest/filter/ItemFilterUtil.java +++ /dev/null @@ -1,278 +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.rest.filter; - -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.List; -import java.util.regex.Pattern; - -import com.ibm.icu.util.Calendar; -import org.apache.logging.log4j.Logger; -import org.dspace.content.Bitstream; -import org.dspace.content.Bundle; -import org.dspace.content.Item; -import org.dspace.content.MetadataValue; -import org.dspace.content.factory.ContentServiceFactory; -import org.dspace.content.service.ItemService; -import org.dspace.core.Context; -import org.dspace.services.factory.DSpaceServicesFactory; - -public class ItemFilterUtil { - protected static ItemService itemService = ContentServiceFactory.getInstance().getItemService(); - static Logger log = org.apache.logging.log4j.LogManager.getLogger(ItemFilterUtil.class); - - public enum BundleName { ORIGINAL, TEXT, LICENSE, THUMBNAIL } - - /** - * Default constructor - */ - private ItemFilterUtil() { } - - static String[] getDocumentMimeTypes() { - return DSpaceServicesFactory.getInstance().getConfigurationService() - .getArrayProperty("rest.report-mime-document"); - } - - static String[] getSupportedDocumentMimeTypes() { - return DSpaceServicesFactory.getInstance().getConfigurationService() - .getArrayProperty("rest.report-mime-document-supported"); - } - - static String[] getSupportedImageMimeTypes() { - return DSpaceServicesFactory.getInstance().getConfigurationService() - .getArrayProperty("rest.report-mime-document-image"); - } - - static int countOriginalBitstream(Item item) { - return countBitstream(BundleName.ORIGINAL, item); - } - - static int countBitstream(BundleName bundleName, Item item) { - int count = 0; - for (Bundle bundle : item.getBundles()) { - if (!bundle.getName().equals(bundleName.name())) { - continue; - } - count += bundle.getBitstreams().size(); - } - - return count; - } - - static List getBitstreamNames(BundleName bundleName, Item item) { - ArrayList names = new ArrayList(); - for (Bundle bundle : item.getBundles()) { - if (!bundle.getName().equals(bundleName.name())) { - continue; - } - for (Bitstream bit : bundle.getBitstreams()) { - names.add(bit.getName()); - } - } - return names; - } - - - static int countOriginalBitstreamMime(Context context, Item item, String[] mimeList) { - return countBitstreamMime(context, BundleName.ORIGINAL, item, mimeList); - } - - static int countBitstreamMime(Context context, BundleName bundleName, Item item, String[] mimeList) { - int count = 0; - for (Bundle bundle : item.getBundles()) { - if (!bundle.getName().equals(bundleName.name())) { - continue; - } - for (Bitstream bit : bundle.getBitstreams()) { - for (String mime : mimeList) { - try { - if (bit.getFormat(context).getMIMEType().equals(mime.trim())) { - count++; - } - } catch (SQLException e) { - log.error("Get format error for bitstream " + bit.getName()); - } - } - } - } - return count; - } - - static int countBitstreamByDesc(BundleName bundleName, Item item, String[] descList) { - int count = 0; - for (Bundle bundle : item.getBundles()) { - if (!bundle.getName().equals(bundleName.name())) { - continue; - } - for (Bitstream bit : bundle.getBitstreams()) { - for (String desc : descList) { - String bitDesc = bit.getDescription(); - if (bitDesc == null) { - continue; - } - if (bitDesc.equals(desc.trim())) { - count++; - } - } - } - } - return count; - } - - static int countBitstreamSmallerThanMinSize(Context context, BundleName bundleName, Item item, String[] mimeList, - String prop) { - long size = DSpaceServicesFactory.getInstance().getConfigurationService().getLongProperty(prop); - int count = 0; - try { - for (Bundle bundle : item.getBundles()) { - if (!bundle.getName().equals(bundleName.name())) { - continue; - } - for (Bitstream bit : bundle.getBitstreams()) { - for (String mime : mimeList) { - if (bit.getFormat(context).getMIMEType().equals(mime.trim())) { - if (bit.getSizeBytes() < size) { - count++; - } - } - } - } - } - } catch (SQLException e) { - // ignore - } - return count; - } - - static int countBitstreamLargerThanMaxSize(Context context, BundleName bundleName, Item item, String[] mimeList, - String prop) { - long size = DSpaceServicesFactory.getInstance().getConfigurationService().getLongProperty(prop); - int count = 0; - try { - for (Bundle bundle : item.getBundles()) { - if (!bundle.getName().equals(bundleName.name())) { - continue; - } - for (Bitstream bit : bundle.getBitstreams()) { - for (String mime : mimeList) { - if (bit.getFormat(context).getMIMEType().equals(mime.trim())) { - if (bit.getSizeBytes() > size) { - count++; - } - } - } - } - } - } catch (SQLException e) { - // ignore - } - return count; - } - - static int countOriginalBitstreamMimeStartsWith(Context context, Item item, String prefix) { - return countBitstreamMimeStartsWith(context, BundleName.ORIGINAL, item, prefix); - } - - static int countBitstreamMimeStartsWith(Context context, BundleName bundleName, Item item, String prefix) { - int count = 0; - try { - for (Bundle bundle : item.getBundles()) { - if (!bundle.getName().equals(bundleName.name())) { - continue; - } - for (Bitstream bit : bundle.getBitstreams()) { - if (bit.getFormat(context).getMIMEType().startsWith(prefix)) { - count++; - } - } - } - } catch (SQLException e) { - // ignore - } - return count; - } - - static boolean hasUnsupportedBundle(Item item, String[] bundleList) { - if (bundleList == null) { - return false; - } - ArrayList bundles = new ArrayList(); - for (String bundleName : bundleList) { - bundles.add(bundleName.trim()); - } - for (Bundle bundle : item.getBundles()) { - if (!bundles.contains(bundle.getName())) { - return true; - } - } - return false; - } - - static boolean hasOriginalBitstreamMime(Context context, Item item, String[] mimeList) { - return hasBitstreamMime(context, BundleName.ORIGINAL, item, mimeList); - } - - static boolean hasBitstreamMime(Context context, BundleName bundleName, Item item, String[] mimeList) { - return countBitstreamMime(context, bundleName, item, mimeList) > 0; - } - - static boolean hasMetadataMatch(Item item, String fieldList, Pattern regex) { - if (fieldList.equals("*")) { - for (MetadataValue md : itemService - .getMetadata(item, org.dspace.content.Item.ANY, org.dspace.content.Item.ANY, - org.dspace.content.Item.ANY, org.dspace.content.Item.ANY)) { - if (regex.matcher(md.getValue()).matches()) { - return true; - } - } - } else { - for (String field : fieldList.split(",")) { - for (MetadataValue md : itemService.getMetadataByMetadataString(item, field.trim())) { - if (regex.matcher(md.getValue()).matches()) { - return true; - } - } - } - } - - return false; - } - - static boolean hasOnlyMetadataMatch(Item item, String fieldList, Pattern regex) { - boolean matches = false; - if (fieldList.equals("*")) { - for (MetadataValue md : itemService - .getMetadata(item, org.dspace.content.Item.ANY, org.dspace.content.Item.ANY, - org.dspace.content.Item.ANY, org.dspace.content.Item.ANY)) { - if (regex.matcher(md.getValue()).matches()) { - matches = true; - } else { - return false; - } - } - } else { - for (String field : fieldList.split(",")) { - for (MetadataValue md : itemService.getMetadataByMetadataString(item, field.trim())) { - if (regex.matcher(md.getValue()).matches()) { - matches = true; - } else { - return false; - } - } - } - } - return matches; - } - - static boolean recentlyModified(Item item, int days) { - Calendar cal = Calendar.getInstance(); - cal.add(Calendar.DATE, -days); - return cal.getTime().before(item.getLastModified()); - } -} diff --git a/dspace-rest/src/main/java/org/dspace/utils/DSpaceWebapp.java b/dspace-rest/src/main/java/org/dspace/utils/DSpaceWebapp.java deleted file mode 100644 index 5d3ce8bfa8..0000000000 --- a/dspace-rest/src/main/java/org/dspace/utils/DSpaceWebapp.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.utils; - -import org.dspace.app.util.AbstractDSpaceWebapp; - -/** - * An MBean to identify this web application. - * - * @author Bram Luyten (bram at atmire dot com) - */ -public class DSpaceWebapp - extends AbstractDSpaceWebapp { - public DSpaceWebapp() { - super("REST"); - } - - @Override - public boolean isUI() { - return false; - } -} diff --git a/dspace-rest/src/main/webapp/WEB-INF/applicationContext.xml b/dspace-rest/src/main/webapp/WEB-INF/applicationContext.xml deleted file mode 100644 index ec892fbaa4..0000000000 --- a/dspace-rest/src/main/webapp/WEB-INF/applicationContext.xml +++ /dev/null @@ -1,59 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/dspace-rest/src/main/webapp/WEB-INF/security-applicationContext.xml b/dspace-rest/src/main/webapp/WEB-INF/security-applicationContext.xml deleted file mode 100644 index 677753d7f0..0000000000 --- a/dspace-rest/src/main/webapp/WEB-INF/security-applicationContext.xml +++ /dev/null @@ -1,80 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/dspace-rest/src/main/webapp/WEB-INF/web.xml b/dspace-rest/src/main/webapp/WEB-INF/web.xml deleted file mode 100644 index 34d74d9630..0000000000 --- a/dspace-rest/src/main/webapp/WEB-INF/web.xml +++ /dev/null @@ -1,119 +0,0 @@ - - - - - - dspace.request - org.dspace.utils.servlet.DSpaceWebappServletFilter - - - - dspace.request - /* - - - - - springSecurityFilterChain - org.springframework.web.filter.DelegatingFilterProxy - - - - springSecurityFilterChain - /* - - - - - DSpace REST API (Deprecated) - - org.glassfish.jersey.servlet.ServletContainer - - - javax.ws.rs.Application - org.dspace.rest.DSpaceRestApplication - - 1 - - - - DSpace REST API (Deprecated) - /* - - - - default - /static/* - - - - - - DSpace REST API (Deprecated) - /* - - - CONFIDENTIAL - - - - - - - The location of the DSpace home directory - - dspace.dir - ${dspace.dir} - - - - - The location of the Log4J configuration - - log4jConfiguration - ${dspace.dir}/config/log4j2.xml - - - - contextConfigLocation - - /WEB-INF/applicationContext.xml, - /WEB-INF/security-applicationContext.xml - - - - - org.dspace.app.util.DSpaceContextListener - - - - - org.dspace.servicemanager.servlet.DSpaceKernelServletContextListener - - - - - org.springframework.web.context.ContextLoaderListener - - - - - org.dspace.app.util.DSpaceWebappListener - - - - diff --git a/dspace-rest/src/main/webapp/static/reports/authenticate.html b/dspace-rest/src/main/webapp/static/reports/authenticate.html deleted file mode 100644 index 046ced425c..0000000000 --- a/dspace-rest/src/main/webapp/static/reports/authenticate.html +++ /dev/null @@ -1,58 +0,0 @@ - - - - - - - - - - - - - - - Authenticate for the REST Report Tools - - -

    Login for an Authenticated Report View

    -
    This is intended for sites with Password Authentication Enabled
    - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - \ No newline at end of file diff --git a/dspace-rest/src/main/webapp/static/reports/index.html b/dspace-rest/src/main/webapp/static/reports/index.html deleted file mode 100644 index bc71b0417c..0000000000 --- a/dspace-rest/src/main/webapp/static/reports/index.html +++ /dev/null @@ -1,77 +0,0 @@ - - - - - - - - - - - - - - - - DSpace REST QC Client - - -Query Tool - -
    -

    DSpace REST QC Client

    -
    -

    Filters

    -
    -
    - -
    -
    -
    -
    - -
    -
    -

    Collection Report

    - -

    Item Results

    -
    -

    Additional data to return

    -
    - -
    -
    -

    Bitstream data to return

    -
    -
    - -
    -

    Results

    -
    -

    - -
    - - - - Export will export one page of results -
    - - -
    -
    -
    -
    - - \ No newline at end of file diff --git a/dspace-rest/src/main/webapp/static/reports/query.html b/dspace-rest/src/main/webapp/static/reports/query.html deleted file mode 100644 index 5a7a79cb20..0000000000 --- a/dspace-rest/src/main/webapp/static/reports/query.html +++ /dev/null @@ -1,105 +0,0 @@ - - - - - - - - - - - - - - - DSpace REST Query Client - - -Collection Filter - -
    -

    DSpace REST Query Client

    -
    -
    -

    Collection Selector

    -
    -
    -

    Metadata Field Queries

    -
    -
    - - -
    -
    -
    -
    - -
    -
    -

    Limit/Paginate Queries

    -
    -
    - - - -
    -
    - -
    -
    -

    Filters

    -
    -
    - -
    -
    -
    -
    - -
    -
    -

    Additional data to return

    -
    -
    -
    - -
    -
    -

    Bitstream data to return

    -
    -
    - -
    -

    Item Results

    -
    -

    - -
    - - - - Export will export one page of results, increase result limits as needed -
    - -
    -
    -
    -
    - - \ No newline at end of file diff --git a/dspace-rest/src/main/webapp/static/reports/restClient.css b/dspace-rest/src/main/webapp/static/reports/restClient.css deleted file mode 100644 index d81724ae67..0000000000 --- a/dspace-rest/src/main/webapp/static/reports/restClient.css +++ /dev/null @@ -1,98 +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 {border-collapse: collapse;border-right:solid thin black;} -table td, table th {border: thin solid black; padding: 4px;} -tr.header {background-color: #EEEEEE;} -tr:hover td, tr:hover th {background-color: #DDDDDD;} -tr.even td {border-bottom: thin dotted black;} -tr.odd td {border-top: thin dotted black;} -td.even {background-color: #EEFFEE;} -td.head {background-color: #EEEEFF;} -td.num {text-align: right;} -td.link {text-decoration: underline; color: blue;} -td, th {width: 100px;} -td.error {color: red;background-color: yellow;} -td.title, th.title {width: 400px;} -td.mod, th.mod {width: 200px;} -#itemtable {width: 100%;} -#itemdiv {display: none;} -td.ititle, th.ititle {width: 600px;} -td.partial {color:red; font-style: italic;} - -button:disabled { - background-color:gray; -} -input:read-only { - background-color: gray; -} -div.metadata { - padding: 2px; - width: 880px; -} -#metadatadiv select, #metadatadiv input { - padding: 2px; - margin: 4px; -} -#metadatadiv fieldset { - margin: 6px 15px; - width: 850px; -} - -#metadatadiv label { - font-weight: bold; -} - -#itemtable td div:not(:first-child) { - border-top: thin solid gray; -} - -body { - min-height: 700px; - min-width: 700px; -} - -tr.header th { - vertical-align: bottom; -} - -a.partial::after { - content:" ?"; -} - -fieldset.catdiv { - border: thin solid black; - margin-bottom: 8px; -} - -fieldset.catdiv div { - width: 380px; - float: left; -} - -#collSel { - width: 90%; -} - -#filterdiv label { - font-weight: normal; -} - -.button { - background-color: #EEEEEE; -} - -.toobig::before { - content: "*"; -} -#exlimit { - font-style: italic; -} - -.red { - color: red; -} \ No newline at end of file diff --git a/dspace-rest/src/main/webapp/static/reports/restCollReport.js b/dspace-rest/src/main/webapp/static/reports/restCollReport.js deleted file mode 100644 index 8d800a8edc..0000000000 --- a/dspace-rest/src/main/webapp/static/reports/restCollReport.js +++ /dev/null @@ -1,510 +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/ - */ -var CollReport = function() { - Report.call(this); - //If sortable.js is included, uncomment the following - //this.hasSorttable = function(){return true;} - this.getLangSuffix = function(){ - return "[en]"; - }; - - //Indicate if Password Authentication is supported - //this.makeAuthLink = function(){return true;}; - //Indicate if Shibboleth Authentication is supported - //this.makeShibLink = function(){return true;}; - - this.COLL_LIMIT = 20; - this.TOOBIG = 10000; - this.loadId = 0; - this.THREADS =11; - this.THREADSP = 11; - this.ACCIDX_COLL = 1; - this.ACCIDX_ITEM = 2; - this.IACCIDX_META = 0; - this.IACCIDX_BIT = 1; - this.IACCIDX_ITEM = 2; - this.getDefaultParameters = function(){ - return { - "show_fields[]" : [], - "show_fields_bits[]" : [], - filters : "", - limit : this.COUNT_LIMIT, - offset : 0, - icollection : "", - ifilter : "", - }; - }; - this.getCurrentParameters = function(){ - return { - "show_fields[]" : this.myMetadataFields.getShowFields(), - "show_fields_bits[]" : this.myBitstreamFields.getShowFieldsBits(), - filters : this.myFilters.getFilterList(), - limit : this.myReportParameters.getLimit(), - offset : this.myReportParameters.getOffset(), - icollection : $("#icollection").val(), - ifilter : $("#ifilter").val(), - }; - }; - var self = this; - - this.init = function() { - this.baseInit(); - $("#icollection").val(self.myReportParameters.params.icollection); - $("#ifilter").val(self.myReportParameters.params.ifilter); - $("#itemResults").accordion({ - heightStyle: "content", - collapsible: true, - active: 2 - }); - }; - - this.myAuth.callback = function(data) { - self.createCollectionTable(); - $(".showCollections").bind("click", function(){ - self.loadData(); - }); - $("#refresh-fields,#refresh-fields-bits").bind("click", function(){ - self.drawItemTable($("#icollection").val(), $("#ifilter").val(), 0); - }); - }; - - this.createCollectionTable = function() { - var self = this; - var tbl = $(""); - tbl.attr("id","table"); - $("#report").replaceWith(tbl); - - var thead = $(""); - tbl.append(thead); - var tbody = $(""); - tbl.append(tbody); - var tr = self.myHtmlUtil.addTr(thead).addClass("header"); - self.myHtmlUtil.addTh(tr, "Num").addClass("num").addClass("sorttable_numeric"); - self.myHtmlUtil.addTh(tr, "Community").addClass("title"); - self.myHtmlUtil.addTh(tr, "Collection").addClass("title"); - var thn = self.myHtmlUtil.addTh(tr, "Num Items").addClass("sorttable_numeric"); - self.myHtmlUtil.makeTotalCol(thn); - thn = self.myHtmlUtil.addTh(tr, "Num Filtered").addClass("sorttable_numeric"); - self.myHtmlUtil.makeTotalCol(thn); - - self.addCollections(); - }; - - this.addCollections = function() { - var self = this; - - $.ajax({ - url: "/rest/hierarchy", - dataType: "json", - headers: self.myAuth.getHeaders(), - success: function(data){ - if (data.community != null) { - $.each(data.community, function(index, comm){ - self.addCommunity(comm, comm); - }); - } - self.setCollectionCounts(0); - }, - error: function(xhr, status, errorThrown) { - alert("Error in /rest/hierarchy "+ status+ " " + errorThrown); - } - }); - }; - - this.addCommunity = function(top, comm) { - var self = this; - - if (comm.collection != null) { - $.each(comm.collection, function(index, coll){ - self.addCollection(top, coll); - }); - } - if (comm.community != null) { - $.each(comm.community, function(index, scomm){ - self.addCommunity(top, scomm); - }); - } - }; - - this.addCollection = function(top, coll) { - var self = this; - - var tbody = $("#table tbody"); - var index = tbody.find("tr").length; - - var tr = self.myHtmlUtil.addTr(tbody); - tr.attr("cid", coll.id).attr("index",index).addClass(index % 2 == 0 ? "odd data" : "even data"); - self.myHtmlUtil.addTd(tr, index + 1).addClass("num"); - var parval = self.myHtmlUtil.getAnchor(top.name, self.ROOTPATH + top.handle); - - self.myHtmlUtil.addTd(tr, parval).addClass("title comm"); - self.myHtmlUtil.addTdAnchor(tr, coll.name, self.ROOTPATH + coll.handle).addClass("title"); - }; - - - this.setCollectionCounts = function(offset) { - var self = this; - - $.ajax({ - url: "/rest/filtered-collections", - data: { - limit : self.COLL_LIMIT, - offset : offset - }, - dataType: "json", - headers: self.myAuth.getHeaders(), - success: function(data){ - $.each(data, function(index, coll){ - var id = self.getId(coll); - var tr = $("#table tbody").find("tr[cid="+id+"]"); - var td = tr.find("td.numCount"); - td.text(coll.numberItems); - td.on("click", function(){ - self.drawItemTable(self.getId(coll),'',0); - $("#icollection").val(self.getId(coll)); - $("#ifilter").val(""); - }); - }); - - //cannot assume data returned is full amount in case some items are restricted - //if (data.length == self.COLL_LIMIT) { - if (data.length > 0) { - self.setCollectionCounts(offset + self.COLL_LIMIT); - return; - } - self.myHtmlUtil.totalCol(3); - $("#table").addClass("sortable"); - - if (self.myFilters.getFilterList() != "") { - self.loadData(); - if ($("#icollection").val() != "") { - self.drawItemTable($("#icollection").val(), $("#ifilter").val(), 0); - } - } - }, - error: function(xhr, status, errorThrown) { - alert("Error in /rest/collections "+ status+ " " + errorThrown); - }, - complete: function(xhr, status) { - self.spinner.stop(); - $(".showCollections").attr("disabled", false); - } - }); - }; - - this.loadData = function() { - self.spinner.spin($("h1")[0]); - $(".showCollections").attr("disabled", true); - $("#metadatadiv").accordion("option", "active", self.ACCIDX_COLL); - self.loadId++; - $("td.datacol,th.datacol").remove(); - $("#table tr.data").addClass("processing"); - self.myFilters.filterString = self.myFilters.getFilterList(); - self.doRow(0, self.THREADS, self.loadId); - }; - - this.doRow = function(row, threads, curLoadId) { - if (self.loadId != curLoadId) return; - var tr = $("tr[index="+row+"]"); - if (!tr.is("*")){ - return; - } - - var cid = tr.attr("cid"); - $.ajax({ - url: "/rest/filtered-collections/"+cid, - data: { - limit : self.COUNT_LIMIT, - filters : self.myFilters.filterString, - }, - dataType: "json", - headers: self.myAuth.getHeaders(), - success: function(data) { - var numItems = data.numberItems; - var numItemsProcessed = data.numberItemsProcessed; - $.each(data.itemFilters, function(index, itemFilter){ - if (self.loadId != curLoadId) { - return; - } - var trh = $("#table tr.header"); - var filterName = itemFilter["filter-name"]; - var filterTitle = itemFilter.title == null ? filterName : itemFilter.title; - if (!trh.find("th."+filterName).is("*")) { - var th = self.myHtmlUtil.addTh(trh, filterTitle); - th.addClass(filterName).addClass("datacol").addClass("sorttable_numeric"); - self.myHtmlUtil.makeTotalCol(th); - - if (itemFilter.description != null) { - th.attr("title", itemFilter.description); - } - - $("tr.data").each(function(){ - var td = self.myHtmlUtil.addTd($(this), ""); - td.addClass(filterName).addClass("num").addClass("datacol"); - }); - } - - self.setCellCount(tr, cid, 0, (numItems != numItemsProcessed), itemFilter); - self.setFilteredCount(tr, cid, 0, numItems, numItemsProcessed); - }); - - tr.removeClass("processing"); - if (!$("#table tr.processing").is("*")) { - self.updateSortable(); - self.totalFilters(); - self.spinner.stop(); - $(".showCollections").attr("disabled", false); - return; - } - if (row % threads == 0 || threads == 1) { - for(var i=1; i<=threads; i++) { - self.doRow(row+i, threads, curLoadId); - } - } - }, - error: function(xhr, status, errorThrown) { - alert("Error in /rest/filtered-collections "+ status+ " " + errorThrown); - }, - complete: function(xhr, status) { - self.spinner.stop(); - $(".showCollections").attr("disabled", false); - } - }); - }; - - this.updateSortable = function() { - if (self.hasSorttable()) { - $("#table").removeClass("sortable"); - $("#table").addClass("sortable"); - sorttable.makeSortable($("#table")[0]); - } - }; - - this.totalFilters = function() { - var colcount = $("#table tr th").length; - for(var i=4; i= self.TOOBIG) { - td.addClass("toobig"); - title+= "\nIt will take significant time to apply this filter to the entire collection."; - } - td.attr("title", title); - return false; - } else { - self.totalFilters(); - } - return true; - }; - - this.setCellCount = function(tr, cid, offset, isPartial, itemFilter) { - var filterName = itemFilter["filter-name"]; - var icount = itemFilter["item-count"]; - - var td = tr.find("td."+filterName); - if (icount == null) { - icount = 0; - } - var cur = parseInt(td.text()); - if (!isNaN(cur)) { - icount += cur; - } - - td.removeClass("partial"); - td.removeClass("link"); - td.removeAttr("title"); - td.off(); - td.text(icount); - if (icount != 0) { - td.addClass("link"); - if (isPartial) { - td.addClass("partial"); - td.attr("title", "Collection partially processed, item counts are incomplete"); - } - td.on("click", function(){ - self.drawItemTable(cid,filterName,0); - $("#icollection").val(cid); - $("#ifilter").val(filterName); - }); - } - }; - - - this.drawItemTable = function(cid, filter, offset) { - self = this; - self.spinner.spin($("h1")[0]); - $("#itemtable").replaceWith($('
    ')); - var itbl = $("#itemtable"); - //itbl.find("tr").remove("*"); - var tr = self.myHtmlUtil.addTr(itbl).addClass("header"); - self.myHtmlUtil.addTh(tr, "Num").addClass("num").addClass("sorttable_numeric"); - self.myHtmlUtil.addTh(tr, "id"); - self.myHtmlUtil.addTh(tr, "Handle"); - self.myHtmlUtil.addTh(tr, "dc.title" + self.getLangSuffix()).addClass("title"); - var fields = $("#show-fields select").val(); - if (fields != null) { - $.each(fields, function(index, field){ - self.myHtmlUtil.addTh(tr, field + self.getLangSuffix()); - }); - } - var bitfields = $("#show-fields-bits select").val(); - if (bitfields != null) { - $.each(bitfields, function(index, bitf){ - self.myHtmlUtil.addTh(tr, bitf); - }); - } - - var expand = "items"; - if (fields != null) { - expand += ",metadata"; - } - if (bitfields != null) { - expand += ",bitstreams"; - } - - var params = { - expand: expand, - limit: self.ITEM_LIMIT, - filters: filter, - offset: offset, - "show_fields[]" : fields, - "show_fields_bits[]" : bitfields, - }; - - $.ajax({ - url: "/rest/filtered-collections/"+cid, - data: params, - dataType: "json", - headers: self.myAuth.getHeaders(), - success: function(data){ - var source = filter == "" ? data.items : data.itemFilters[0].items; - - $.each(source, function(index, item){ - var tr = self.myHtmlUtil.addTr(itbl); - tr.addClass(index % 2 == 0 ? "odd data" : "even data"); - self.myHtmlUtil.addTd(tr, offset+index+1).addClass("num"); - self.myHtmlUtil.addTd(tr, self.getId(item)); - self.myHtmlUtil.addTdAnchor(tr, item.handle, self.ROOTPATH + item.handle); - self.myHtmlUtil.addTd(tr, item.name).addClass("ititle"); - if (fields != null) { - $.each(fields, function(index, field){ - var td = self.myHtmlUtil.addTd(tr, ""); - $.each(item.metadata, function(mindex,mv){ - if (mv.key == field) { - td.append($("
    "+mv.value+"
    ")); - } - }); - }); - } - if (bitfields != null) { - $.each(bitfields, function(index, bitfield){ - var td = self.myHtmlUtil.addTd(tr, ""); - var fieldtext = self.myBitstreamFields.getKeyText(bitfield, item, bitfields); - for(var j=0; j"+fieldtext[j]+"")); - } - }); - } - }); - self.displayItems(filter + " Items in " + data.name, - offset, - self.ITEM_LIMIT, - data.numberItems, - function(){self.drawItemTable(cid, filter, (offset - self.ITEM_LIMIT < 0) ? 0 : offset - self.ITEM_LIMIT);}, - function(){self.drawItemTable(cid, filter, offset + self.ITEM_LIMIT);} - ); - - if (self.hasSorttable()){ - sorttable.makeSortable(itbl[0]); - } - $("#metadatadiv").accordion("option", "active", self.ACCIDX_ITEM); - }, - error: function(xhr, status, errorThrown) { - alert("Error in /rest/filtered-collections "+ status+ " " + errorThrown); - }, - complete: function(xhr, status) { - self.spinner.stop(); - $(".showCollections").attr("disabled", false); - $("#itemResults").accordion("option", "active", self.IACCIDX_ITEM); - } - }); - }; - - //Ignore the first column containing a row number and the item handle - this.exportCol = function(colnum, col) { - var data = ""; - if (colnum == 0) return ""; - if (colnum == 2) return ""; - data += (colnum == 1) ? "" : ","; - data += self.exportCell(col); - return data; - }; -}; -CollReport.prototype = Object.create(Report.prototype); - -$(document).ready(function(){ - var myReport=new CollReport(); - myReport.init(); -}); \ No newline at end of file diff --git a/dspace-rest/src/main/webapp/static/reports/restQueryReport.js b/dspace-rest/src/main/webapp/static/reports/restQueryReport.js deleted file mode 100644 index 18e9a61d08..0000000000 --- a/dspace-rest/src/main/webapp/static/reports/restQueryReport.js +++ /dev/null @@ -1,350 +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/ - */ -var QueryReport = function() { - Report.call(this); - - //If sortable.js is included, uncomment the following - //this.hasSorttable = function(){return true;} - this.getLangSuffix = function(){ - return "[en]"; - }; - - //Indicate if Password Authentication is supported - //this.makeAuthLink = function(){return true;}; - //Indicate if Shibboleth Authentication is supported - //this.makeShibLink = function(){return true;}; - - this.getDefaultParameters = function(){ - return { - "collSel[]" : [], - "query_field[]" : [], - "query_op[]" : [], - "query_val[]" : [], - "show_fields[]" : [], - "show_fields_bits[]" : [], - "filters" : "", - "limit" : this.ITEM_LIMIT, - "offset" : 0, - }; - }; - this.getCurrentParameters = function(){ - var expand = "parentCollection,metadata"; - if (this.myBitstreamFields.hasBitstreamFields()) { - expand += ",bitstreams"; - } - var params = { - "query_field[]" : [], - "query_op[]" : [], - "query_val[]" : [], - "collSel[]" : ($("#collSel").val() == null) ? [""] : $("#collSel").val(), - limit : this.myReportParameters.getLimit(), - offset : this.myReportParameters.getOffset(), - "expand" : expand, - filters : this.myFilters.getFilterList(), - "show_fields[]" : this.myMetadataFields.getShowFields(), - "show_fields_bits[]" : this.myBitstreamFields.getShowFieldsBits(), - }; - $("select.query-tool,input.query-tool").each(function() { - var paramArr = params[$(this).attr("name")]; - paramArr[paramArr.length] = $(this).val(); - }); - return params; - }; - var self = this; - - this.init = function() { - this.baseInit(); - }; - - this.initMetadataFields = function() { - this.myMetadataFields = new QueryableMetadataFields(self); - this.myMetadataFields.load(); - }; - this.myAuth.callback = function(data) { - $(".query-button").click(function(){self.runQuery();}); - }; - - this.runQuery = function() { - this.spinner.spin($("body")[0]); - $("button").attr("disabled", true); - $.ajax({ - url: "/rest/filtered-items", - data: this.getCurrentParameters(), - dataType: "json", - headers: self.myAuth.getHeaders(), - success: function(data){ - data.metadata = $("#show-fields select").val(); - data.bitfields = $("#show-fields-bits select").val(); - self.drawItemFilterTable(data); - self.spinner.stop(); - $("button").not("#next,#prev").attr("disabled", false); - }, - error: function(xhr, status, errorThrown) { - alert("Error in /rest/filtered-items "+ status+ " " + errorThrown); - }, - complete: function(xhr, status, errorThrown) { - self.spinner.stop(); - $("button").not("#next,#prev").attr("disabled", false); - } - }); - }; - - this.drawItemFilterTable = function(data) { - $("#itemtable").replaceWith($('
    ')); - var itbl = $("#itemtable"); - var tr = self.myHtmlUtil.addTr(itbl).addClass("header"); - self.myHtmlUtil.addTh(tr, "Num").addClass("num").addClass("sorttable_numeric"); - self.myHtmlUtil.addTh(tr, "id"); - self.myHtmlUtil.addTh(tr, "collection"); - self.myHtmlUtil.addTh(tr, "Item Handle"); - self.myHtmlUtil.addTh(tr, "dc.title" + self.getLangSuffix()); - - var mdCols = []; - if (data.metadata) { - $.each(data.metadata, function(index, field) { - if (field != "") { - self.myHtmlUtil.addTh(tr,field + self.getLangSuffix()).addClass("returnFields"); - mdCols[mdCols.length] = field; - } - }); - } - - if (data.bitfields) { - $.each(data.bitfields, function(index, bitfield) { - if (bitfield != "") { - self.myHtmlUtil.addTh(tr,bitfield).addClass("returnFields"); - mdCols[mdCols.length] = bitfield; - } - }); - } - - $.each(data.items, function(index, item){ - var tr = self.myHtmlUtil.addTr(itbl); - tr.addClass(index % 2 == 0 ? "odd data" : "even data"); - self.myHtmlUtil.addTd(tr, self.myReportParameters.getOffset()+index+1).addClass("num"); - self.myHtmlUtil.addTd(tr, self.getId(item)); - if (item.parentCollection == null) { - self.myHtmlUtil.addTd(tr, "--"); - } else { - self.myHtmlUtil.addTdAnchor(tr, item.parentCollection.name, self.ROOTPATH + item.parentCollection.handle); - } - self.myHtmlUtil.addTdAnchor(tr, item.handle, self.ROOTPATH + item.handle); - self.myHtmlUtil.addTd(tr, item.name); - - for(var i=0; i"+metadata.value+""); - td.append(div); - } - } - }); - var fieldtext = self.myBitstreamFields.getKeyText(key, item, data.bitfields); - for(var j=0; j"+fieldtext[j]+"")); - } - } - }); - - this.displayItems(data["query-annotation"], - this.myReportParameters.getOffset(), - this.myReportParameters.getLimit(), - data["unfiltered-item-count"], - function(){ - self.myReportParameters.updateOffset(false); - self.runQuery(); - }, - function(){ - self.myReportParameters.updateOffset(true); - self.runQuery(); - } - ); - - if (this.hasSorttable()) { - sorttable.makeSortable(itbl[0]); - } - $("#metadatadiv").accordion("option", "active", $("#metadatadiv > h3").length - 1); - }; - - //Ignore the first column containing a row number and the item handle, get handle for the collection - this.exportCol = function(colnum, col) { - var data = ""; - if (colnum == 0) return ""; - if (colnum == 3) return ""; - data += (colnum == 1) ? "" : ","; - - if (colnum == 2) { - var anchor = $(col).find("a"); - var href = anchor.is("a") ? anchor.attr("href").replace(self.ROOTPATH,"") : $(col).text(); - data += "\"" + href + "\""; - } else { - data += self.exportCell(col); } - return data; - }; -}; -QueryReport.prototype = Object.create(Report.prototype); - -$(document).ready(function(){ - var myReport=new QueryReport(); - myReport.init(); -}); - -var QueryableMetadataFields = function(report) { - MetadataFields.call(this, report); - var self = this; - - this.initFields = function(data, report) { - self.metadataSchemas = data; - var params = report.myReportParameters.params; - var fields = params["query_field[]"]; - var ops = params["query_op[]"]; - var vals = params["query_val[]"]; - if (fields && ops && vals) { - if (fields.length == 0) { - self.drawFilterQuery("*","exists",""); - } else { - for(var i=0; i i ? ops[i] : ""; - var val = vals.length > i ? vals[i] : ""; - self.drawFilterQuery(fields[i],op,val); - } - } - } - self.drawShowFields(params["show_fields[]"]); - self.initQueries(); - report.spinner.stop(); - $(".query-button").attr("disabled", false); - }; - - this.initQueries = function() { - $("#predefselect") - .append($("")) - .append($("")) - .append($("")) - .append($("")) - .append($("")) - .append($("")) - .append($("")) - .append($("")) - .append($("")) - .append($("")) - .append($("")) - .append($("")) - .append($("")) - .on("change",function(){ - $("div.metadata").remove(); - var val = $("#predefselect").val(); - if (val == 'new') { - self.drawFilterQuery("","",""); - } else if (val == 'q1') { - self.drawFilterQuery("dc.title","doesnt_exist",""); - } else if (val == 'q2') { - self.drawFilterQuery("dc.identifier.uri","doesnt_exist",""); - } else if (val == 'q3') { - self.drawFilterQuery("dc.subject.*","like","%;%"); - } else if (val == 'q4') { - self.drawFilterQuery("dc.contributor.author","like","% and %"); - } else if (val == 'q5') { - self.drawFilterQuery("dc.creator","like","% and %"); - } else if (val == 'q6') { - self.drawFilterQuery("dc.description","matches","^.*(http://|https://|mailto:).*$"); - } else if (val == 'q7') { - self.drawFilterQuery("dc.description.provenance","matches","^.*No\\. of bitstreams(.|\\r|\\n|\\r\\n)*\\.(PDF|pdf|DOC|doc|PPT|ppt|DOCX|docx|PPTX|pptx).*$"); - } else if (val == 'q8') { - self.drawFilterQuery("dc.description.provenance","doesnt_match","^.*No\\. of bitstreams(.|\\r|\\n|\\r\\n)*\\.(PDF|pdf|DOC|doc|PPT|ppt|DOCX|docx|PPTX|pptx).*$"); - } else if (val == 'q9') { - self.drawFilterQuery("*","matches","^\\s*$"); - } else if (val == 'q10') { - self.drawFilterQuery("dc.description.*","matches","^.*[^\\s]{50,}.*$"); - } else if (val == 'q12') { - self.drawFilterQuery("*","matches","^.*&#.*$"); - } else if (val == 'q13') { - self.drawFilterQuery("*","matches","^.*[^[:ascii:]].*$"); - } - }); - }; - - this.drawFilterQuery = function(pField, pOp, pVal) { - var div = $("