From 75d9d6747ed5853e71074397fa984f003791971c Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Tue, 2 Feb 2021 19:24:37 +0100 Subject: [PATCH 001/247] 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/247] [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/247] [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/247] [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/247] [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/247] [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/247] [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/247] [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/247] [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/247] [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/247] [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/247] [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/247] [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/247] [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/247] [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/247] [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/247] [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/247] [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/247] [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/247] [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/247] [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/247] [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/247] [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/247] [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/247] [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/247] [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/247] [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/247] [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/247] [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/247] [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/247] [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/247] [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/247] [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/247] [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/247] [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/247] [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/247] [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/247] [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/247] [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/247] [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/247] [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/247] [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/247] [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/247] [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/247] [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/247] [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/247] [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/247] 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 a76af35a0cd4f0c0e8737c736578b17bcc349691 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Tue, 1 Aug 2023 17:13:07 -0400 Subject: [PATCH 056/247] 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 bb9e88d1bb452d0865f4827134baf907e6d34044 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Wed, 2 Aug 2023 16:25:46 -0400 Subject: [PATCH 057/247] 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 be22790aad7f627e2ac027773e272b703986f589 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Wed, 2 Aug 2023 17:23:36 -0400 Subject: [PATCH 058/247] 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 d5dd2c93bb5dc3fef58080adb71fddc0f7d105b3 Mon Sep 17 00:00:00 2001 From: Francesco Bacchelli Date: Mon, 7 Aug 2023 08:59:01 +0200 Subject: [PATCH 059/247] 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 13:37:22 +0200 Subject: [PATCH 060/247] 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 061/247] 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 062/247] 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 063/247] 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 88749f6c61559b98fe35de3b8f4346f5995d1ef0 Mon Sep 17 00:00:00 2001 From: Koen Pauwels Date: Fri, 4 Aug 2023 17:00:35 +0200 Subject: [PATCH 064/247] 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 065/247] 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 066/247] 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 067/247] 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 068/247] 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 a4e580d3c5d33fbf5403dbd8ae9a51ee3d98bc09 Mon Sep 17 00:00:00 2001 From: Francesco Bacchelli Date: Tue, 22 Aug 2023 12:04:24 +0200 Subject: [PATCH 069/247] 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 1160341cb2a2c163c8fddc04ddab46de9041e1b8 Mon Sep 17 00:00:00 2001 From: Christian Bethge Date: Tue, 29 Aug 2023 15:28:21 +0200 Subject: [PATCH 070/247] 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 ef7f02fe81bc570353c0bf6a43706c77909e30e3 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 13 Sep 2023 16:56:29 -0500 Subject: [PATCH 071/247] 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 bc505d7cae03ee4c0a7c909bb3d9e9140587e4f7 Mon Sep 17 00:00:00 2001 From: Agustina Martinez Date: Fri, 15 Sep 2023 14:22:56 +0100 Subject: [PATCH 072/247] 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 073/247] 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 f51975bea191ee9c06073f8a31a9107703770479 Mon Sep 17 00:00:00 2001 From: frabacche Date: Mon, 25 Sep 2023 11:53:49 +0200 Subject: [PATCH 074/247] 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 075/247] 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 076/247] 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 077/247] 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 f821c3a628944623a12289d53a7462aae1655781 Mon Sep 17 00:00:00 2001 From: Agustina Martinez Date: Tue, 3 Oct 2023 10:53:54 +0200 Subject: [PATCH 078/247] 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 079/247] 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 080/247] 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 a1248074681a7bc4603176fb3e7d989b91edcbcd Mon Sep 17 00:00:00 2001 From: "Gantner, Florian Klaus" Date: Tue, 10 Oct 2023 16:19:11 +0200 Subject: [PATCH 081/247] 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 a9bcc0c223d0219f464d986d7b7c66b3c4cbc39c Mon Sep 17 00:00:00 2001 From: "Gantner, Florian Klaus" Date: Thu, 12 Oct 2023 17:58:13 +0200 Subject: [PATCH 082/247] 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 083/247] 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 084/247] 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 085/247] 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 086/247] 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 087/247] 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 088/247] 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 089/247] 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 090/247] 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 c5e2e4fac755a44ea3761f2d164c67844f25605c Mon Sep 17 00:00:00 2001 From: frabacche Date: Mon, 16 Oct 2023 10:46:32 +0200 Subject: [PATCH 091/247] 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 6504d749b91508096300e4545069a0554eb5934b Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Tue, 17 Oct 2023 16:28:37 +0200 Subject: [PATCH 092/247] [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 093/247] [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 094/247] 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 095/247] 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 096/247] 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 097/247] 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 098/247] 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 099/247] 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 100/247] 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 534ee3a699937eedd11aa5cb54f97b081bcda621 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 20 Oct 2023 15:08:03 -0500 Subject: [PATCH 101/247] 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 102/247] 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 103/247] 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 104/247] 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 105/247] 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 106/247] 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 107/247] 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 108/247] 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 109/247] 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 110/247] 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 111/247] 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 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 112/247] 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 113/247] 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 114/247] 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 115/247] 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 116/247] 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 117/247] 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 118/247] 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 119/247] 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 120/247] 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 121/247] 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 122/247] 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 123/247] 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 124/247] 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 125/247] 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 126/247] 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 127/247] 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 128/247] 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 129/247] 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 130/247] 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 131/247] 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 132/247] 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 133/247] 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 134/247] 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 135/247] 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 136/247] [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 137/247] 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 138/247] [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 139/247] [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 140/247] 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 141/247] 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 142/247] 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 143/247] 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 144/247] 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 145/247] 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 146/247] 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 147/247] 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 148/247] 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 44fc15f74baf6aa23c70f3b73975f3c82401e499 Mon Sep 17 00:00:00 2001 From: Shankeerthan Kasilingam Date: Tue, 5 Dec 2023 16:22:01 +0530 Subject: [PATCH 149/247] 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 150/247] 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 151/247] 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 152/247] [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 153/247] 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 154/247] 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 155/247] 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 156/247] 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 157/247] 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 158/247] [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 169/247] [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 170/247] [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 171/247] [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 172/247] 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 173/247] 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 174/247] 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 175/247] 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 176/247] 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 177/247] 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 178/247] [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 179/247] [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 180/247] 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 181/247] 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 182/247] 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 = $("