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/.github/workflows/docker.yml b/.github/workflows/docker.yml
index 338c7371f6..9f1e407cff 100644
--- a/.github/workflows/docker.yml
+++ b/.github/workflows/docker.yml
@@ -102,6 +102,8 @@ jobs:
build_id: dspace-solr
image_name: dspace/dspace-solr
dockerfile_path: ./dspace/src/main/docker/dspace-solr/Dockerfile
+ # Must pass solrconfigs to the Dockerfile so that it can find the required Solr config files
+ dockerfile_additional_contexts: 'solrconfigs=./dspace/solr/'
secrets:
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
DOCKER_ACCESS_TOKEN: ${{ secrets.DOCKER_ACCESS_TOKEN }}
diff --git a/.github/workflows/reusable-docker-build.yml b/.github/workflows/reusable-docker-build.yml
index 46bdab3b68..aa8327f4d1 100644
--- a/.github/workflows/reusable-docker-build.yml
+++ b/.github/workflows/reusable-docker-build.yml
@@ -24,6 +24,12 @@ on:
dockerfile_context:
required: false
type: string
+ default: '.'
+ # Optionally a list of "additional_contexts" to pass to Dockerfile. Defaults to empty
+ dockerfile_additional_contexts:
+ required: false
+ type: string
+ default: ''
# If Docker image should have additional tag flavor details (e.g. a suffix), it may be passed in.
tags_flavor:
required: false
@@ -123,7 +129,9 @@ jobs:
id: docker_build
uses: docker/build-push-action@v5
with:
- context: ${{ inputs.dockerfile_context || '.' }}
+ build-contexts: |
+ ${{ inputs.dockerfile_additional_contexts }}
+ 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),
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/docker-compose-cli.yml b/docker-compose-cli.yml
index 7dbdde3703..b2c6df636b 100644
--- a/docker-compose-cli.yml
+++ b/docker-compose-cli.yml
@@ -1,5 +1,10 @@
version: "3.7"
-
+networks:
+ # Default to using network named 'dspacenet' from docker-compose.yml.
+ # Its full name will be prepended with the project name (e.g. "-p d7" means it will be named "d7_dspacenet")
+ default:
+ name: ${COMPOSE_PROJECT_NAME}_dspacenet
+ external: true
services:
dspace-cli:
image: "${DOCKER_OWNER:-dspace}/dspace-cli:${DSPACE_VER:-latest}"
@@ -26,13 +31,8 @@ services:
- ./dspace/config:/dspace/config
entrypoint: /dspace/bin/dspace
command: help
- networks:
- - dspacenet
tty: true
stdin_open: true
volumes:
assetstore:
-
-networks:
- dspacenet:
diff --git a/docker-compose.yml b/docker-compose.yml
index 14f47ebdb6..2e3d640940 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -36,7 +36,7 @@ services:
depends_on:
- dspacedb
networks:
- dspacenet:
+ - dspacenet
ports:
- published: 8080
target: 8080
@@ -89,8 +89,10 @@ services:
container_name: dspacesolr
image: "${DOCKER_OWNER:-dspace}/dspace-solr:${DSPACE_VER:-latest}"
build:
- context: .
- dockerfile: ./dspace/src/main/docker/dspace-solr/Dockerfile
+ context: ./dspace/src/main/docker/dspace-solr/
+ # Provide path to Solr configs necessary to build Docker image
+ additional_contexts:
+ solrconfigs: ./dspace/solr/
args:
SOLR_VERSION: "${SOLR_VER:-8.11}"
networks:
@@ -123,6 +125,8 @@ services:
cp -r /opt/solr/server/solr/configsets/statistics/* statistics
precreate-core qaevent /opt/solr/server/solr/configsets/qaevent
cp -r /opt/solr/server/solr/configsets/qaevent/* qaevent
+ precreate-core suggestion /opt/solr/server/solr/configsets/suggestion
+ cp -r /opt/solr/server/solr/configsets/suggestion/* suggestion
exec solr -f
volumes:
assetstore:
diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml
index 849dbd1356..f6b06f69ed 100644
--- a/dspace-api/pom.xml
+++ b/dspace-api/pom.xml
@@ -528,7 +528,7 @@
org.hamcrest
- hamcrest-all
+ hamcrest
test
@@ -620,7 +620,7 @@
com.maxmind.geoip2
geoip2
- 2.11.0
+ 2.17.0
org.apache.ant
@@ -784,7 +784,7 @@
com.opencsv
opencsv
- 5.7.1
+ 5.9
@@ -867,32 +867,32 @@
io.netty
netty-buffer
- 4.1.94.Final
+ 4.1.106.Final
io.netty
netty-transport
- 4.1.94.Final
+ 4.1.106.Final
io.netty
netty-transport-native-unix-common
- 4.1.94.Final
+ 4.1.106.Final
io.netty
netty-common
- 4.1.94.Final
+ 4.1.106.Final
io.netty
netty-handler
- 4.1.94.Final
+ 4.1.106.Final
io.netty
netty-codec
- 4.1.94.Final
+ 4.1.106.Final
org.apache.velocity
@@ -902,7 +902,7 @@
org.xmlunit
xmlunit-core
- 2.8.0
+ 2.9.1
test
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();
}
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..f5acd2ccbc
--- /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);
+ }
+ }
+
+ /**
+ * check if the externalDataObject may have suggestion
+ * @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..6281b69107
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/suggestion/SolrSuggestionStorageServiceImpl.java
@@ -0,0 +1,360 @@
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in the 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.sql.SQLException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.stream.Collectors;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+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.lang3.StringUtils;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+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.apache.solr.common.params.FacetParams;
+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 {
+ private static final Logger log = LogManager.getLogger(SolrSuggestionStorageServiceImpl.class);
+
+ 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)) {
+ ObjectMapper jsonMapper = new JsonMapper();
+ jsonMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+ SolrInputDocument document = new SolrInputDocument();
+ document.addField(SOURCE, suggestion.getSource());
+ // suggestion id is written as concatenation of
+ // source + ":" + targetID + ":" + idPart (of externalDataObj)
+ 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, jsonMapper.writeValueAsString(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.setParam(FacetParams.FACET_OFFSET, String.valueOf(offset));
+ solrQuery.setFacetLimit((int) (pageSize));
+ QueryResponse response = getSolr().query(solrQuery);
+ FacetField facetField = response.getFacetField(TARGET_ID);
+ List suggestionTargets = new ArrayList();
+ int idx = 0;
+ for (Count c : facetField.getValues()) {
+ 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);
+ ObjectMapper jsonMapper = new JsonMapper();
+ jsonMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+ List evidences = new LinkedList();
+ try {
+ evidences = jsonMapper.readValue(evidencesJson, new TypeReference>() {});
+ } catch (JsonProcessingException e) {
+ log.error(e);
+ }
+ 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..7812cbd522
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/suggestion/Suggestion.java
@@ -0,0 +1,99 @@
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in the 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;
+
+/**
+ * This entity contains metadatas that should be added to the targeted Item
+ *
+ * @author Andrea Bollini (andrea.bollini at 4science.it)
+ */
+public class Suggestion {
+
+ /** id of the suggestion */
+ private String id;
+
+ /** the dc.title of the item */
+ private String display;
+
+ /** the external source name the suggestion comes from */
+ private String source;
+
+ /** external uri of the item */
+ private String externalSourceUri;
+
+ /** item targeted by this suggestion */
+ private Item target;
+
+ private List evidences = new LinkedList();
+
+ private List metadata = new LinkedList();
+
+ /** suggestion creation
+ * @param source name of the external source
+ * @param target the targeted item in repository
+ * @param idPart external item id, used mainly for suggestion @see #id creation
+ * */
+ 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..d7f04929a1
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/suggestion/SuggestionEvidence.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.suggestion;
+
+/**
+ * This DTO class is returned by an {@link org.dspace.app.suggestion.openaire.EvidenceScorer} to model the concept of
+ * an evidence / fact that has been used to evaluate the precision of a suggestion increasing or decreasing the score
+ * of the suggestion.
+ *
+ * @author Andrea Bollini (andrea.bollini at 4science.it)
+ */
+public class SuggestionEvidence {
+
+ /** name of the evidence */
+ private String name;
+
+ /** positive or negative value to influence the score of the suggestion */
+ private double score;
+
+ /** additional notes */
+ 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..7cfc3cfb53
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/suggestion/SuggestionProvider.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.suggestion;
+
+import java.util.List;
+import java.util.UUID;
+
+import org.dspace.core.Context;
+import org.dspace.external.model.ExternalDataObject;
+
+/**
+ *
+ * Interface for suggestion management like finding and counting.
+ * @see org.dspace.app.suggestion.SuggestionTarget
+ * @author Francesco Bacchelli (francesco.bacchelli at 4science.com)
+ *
+ */
+public interface SuggestionProvider {
+
+ /** find all suggestion targets
+ * @see org.dspace.app.suggestion.SuggestionTarget
+ * */
+ public List findAllTargets(Context context, int pageSize, long offset);
+
+ /** count all suggestion targets */
+ public long countAllTargets(Context context);
+
+ /** find a suggestion target by UUID */
+ public SuggestionTarget findTarget(Context context, UUID target);
+
+ /** find unprocessed suggestions (paged) by target UUID
+ * @see org.dspace.app.suggestion.Suggestion
+ * */
+ public List findAllUnprocessedSuggestions(Context context, UUID target, int pageSize, long offset,
+ boolean ascending);
+
+ /** find unprocessed suggestions by target UUID */
+ public long countUnprocessedSuggestionByTarget(Context context, UUID target);
+
+ /** find an unprocessed suggestion by target UUID and suggestion id */
+ public Suggestion findUnprocessedSuggestion(Context context, UUID target, String id);
+
+ /** reject a specific suggestion by target @param target and by suggestion id @param idPart */
+ public void rejectSuggestion(Context context, UUID target, String idPart);
+
+ /** flag a suggestion as processed */
+ 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..41d33026ed
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/suggestion/SuggestionService.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.suggestion;
+
+import java.util.List;
+import java.util.UUID;
+
+import org.dspace.core.Context;
+
+/**
+ * Service that handles {@link Suggestion}.
+ *
+ * @author Andrea Bollini (andrea.bollini at 4science.it)
+ */
+public interface SuggestionService {
+
+ /** find a {@link SuggestionTarget } by source name and suggestion id */
+ public SuggestionTarget find(Context context, String source, UUID id);
+
+ /** count all suggetion targets by suggestion source */
+ public long countAll(Context context, String source);
+
+ /** find all suggestion targets by source (paged) */
+ public List findAllTargets(Context context, String source, int pageSize, long offset);
+
+ /** count all (unprocessed) suggestions by the given target uuid */
+ public long countAllByTarget(Context context, UUID target);
+
+ /** find suggestion target by targeted item (paged) */
+ public List findByTarget(Context context, UUID target, int pageSize, long offset);
+
+ /** find suggestion source by source name */
+ public SuggestionSource findSource(Context context, String source);
+
+ /** count all suggestion sources */
+ public long countSources(Context context);
+
+ /** find all suggestion sources (paged) */
+ public List findAllSources(Context context, int pageSize, long offset);
+
+ /** find unprocessed suggestion by id */
+ public Suggestion findUnprocessedSuggestion(Context context, String id);
+
+ /** reject a specific suggestion by its id */
+ public void rejectSuggestion(Context context, String id);
+
+ /** find all suggestions by targeted item and external source */
+ public List findByTargetAndSource(Context context, UUID target, String source, int pageSize,
+ long offset, boolean ascending);
+
+ /** count all suggestions by targeted item id and source name */
+ public long countAllByTargetAndSource(Context context, String source, UUID target);
+
+ /** returns all suggestion providers */
+ 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..66773fbc12
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/suggestion/SuggestionServiceImpl.java
@@ -0,0 +1,194 @@
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in the 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()) {
+ // all the suggestion target will be related to the same target (i.e. the same researcher - person item)
+ 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());
+ }
+ }
+ );
+ // this list will be as large as the number of sources available in the repository so it is unlikely that
+ // real pagination will occur
+ 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..6dcc3f7e1e
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/suggestion/SuggestionSource.java
@@ -0,0 +1,49 @@
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in the LICENSE and NOTICE files at the root of the source
+ * tree and available online at
+ *
+ * http://www.dspace.org/license/
+ */
+package org.dspace.app.suggestion;
+
+/**
+ * This DTO class is used to pass around the number of items interested by suggestion provided by a specific source
+ * (i.e. openaire)
+ *
+ * @author Andrea Bollini (andrea.bollini at 4science.it)
+ */
+public class SuggestionSource {
+
+ /** source name of the suggestion */
+ private String name;
+
+ /** number of targeted items */
+ private int total;
+
+ public SuggestionSource() {
+ }
+
+ /**
+ * Summarize the available suggestions from a source.
+ *
+ * @param name 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..db82aa8081
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/suggestion/SuggestionTarget.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.suggestion;
+
+import org.dspace.content.Item;
+
+/**
+ * This DTO class is used to pass around the number of suggestions available from a specific source for a target
+ * repository item
+ *
+ * @author Andrea Bollini (andrea.bollini at 4science.it)
+ */
+public class SuggestionTarget {
+
+ /** the item targeted */
+ private Item target;
+
+ /** source name of the suggestion */
+ private String source;
+
+ /** total count of suggestions for same target and source */
+ private int total;
+
+ public SuggestionTarget() {
+ }
+
+ /**
+ * Wrap a target repository item (usually a person item) 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 != null ? 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/openaire/AuthorNamesScorer.java b/dspace-api/src/main/java/org/dspace/app/suggestion/openaire/AuthorNamesScorer.java
new file mode 100644
index 0000000000..60b1521f7e
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/suggestion/openaire/AuthorNamesScorer.java
@@ -0,0 +1,151 @@
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in the LICENSE and NOTICE files at the root of the source
+ * tree and available online at
+ *
+ * http://www.dspace.org/license/
+ */
+package org.dspace.app.suggestion.openaire;
+
+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
+ */
+ 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;
+ }
+
+ /**
+ * cleans up undesired characters
+ * @param value the string to clean up
+ * @return cleaned up string
+ * */
+ 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/openaire/DateScorer.java b/dspace-api/src/main/java/org/dspace/app/suggestion/openaire/DateScorer.java
new file mode 100644
index 0000000000..94f81715fa
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/suggestion/openaire/DateScorer.java
@@ -0,0 +1,214 @@
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in the 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.openaire;
+
+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 {
+
+ /**
+ * if available it should contains the metadata field key in the form (schema.element[.qualifier]) that contains
+ * the birth date of the researcher
+ */
+ private String birthDateMetadata;
+
+ /**
+ * if available it should contains the metadata field key in the form (schema.element[.qualifier]) that contains
+ * the date of graduation of the researcher. If the metadata has multiple values the min will be used
+ */
+ private String educationDateMetadata;
+
+ /**
+ * The minimal age that is expected for a researcher to be a potential author of a scholarly contribution
+ * (i.e. the minimum delta from the publication date and the birth date)
+ */
+ private int birthDateDelta = 20;
+
+ /**
+ * The maximum age that is expected for a researcher to be a potential author of a scholarly contribution
+ * (i.e. the maximum delta from the publication date and the birth date)
+ */
+ private int birthDateRange = 50;
+
+ /**
+ * The number of year from/before the graduation that is expected for a researcher to be a potential
+ * author of a scholarly contribution (i.e. the minimum delta from the publication date and the first
+ * graduation date)
+ */
+ private int educationDateDelta = -3;
+
+ /**
+ * The maximum scientific longevity that is expected for a researcher from its graduation to be a potential
+ * author of a scholarly contribution (i.e. the maximum delta from the publication date and the first
+ * graduation date)
+ */
+ private int educationDateRange = 50;
+
+ @Autowired
+ private ItemService itemService;
+
+ /**
+ * the metadata used in the publication to track the publication date (i.e. dc.date.issued)
+ */
+ 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 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.
+ * {@link DateScorer#birthDateMetadata}, {@link DateScorer#educationDateMetadata}
+ *
+ * @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 setting your birthday in your profile.");
+ } 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"));
+ }
+ }
+ }
+
+ /**
+ * returns min and max year interval in between it's probably that the researcher
+ * actually contributed to the suggested item
+ * @param researcher
+ * @return
+ */
+ private Integer[] calculateRange(Item researcher) {
+ 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[] {
+ educationDateYear + educationDateDelta,
+ educationDateYear + educationDateDelta + educationDateRange
+ };
+ } else if (birthDateYear > 0) {
+ return new Integer[] {
+ birthDateYear + birthDateDelta,
+ 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/openaire/EvidenceScorer.java b/dspace-api/src/main/java/org/dspace/app/suggestion/openaire/EvidenceScorer.java
new file mode 100644
index 0000000000..027e9902f9
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/suggestion/openaire/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.openaire;
+
+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.PublicationApproverServiceImpl}
+ * 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 importRecord);
+
+}
diff --git a/dspace-api/src/main/java/org/dspace/app/suggestion/openaire/PublicationLoader.java b/dspace-api/src/main/java/org/dspace/app/suggestion/openaire/PublicationLoader.java
new file mode 100644
index 0000000000..7ad723af12
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/suggestion/openaire/PublicationLoader.java
@@ -0,0 +1,256 @@
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in the LICENSE and NOTICE files at the root of the source
+ * tree and available online at
+ *
+ * http://www.dspace.org/license/
+ */
+package org.dspace.app.suggestion.openaire;
+
+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 PublicationLoader 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.openaire.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 {
+ int offset = 0;
+ int limit = 10;
+ int loaded = limit;
+ List searchValues = searchMetadataValues(researcher);
+ while (loaded > 0) {
+ List metadata = getImportRecords(searchValues, researcher, offset, limit);
+ if (metadata.isEmpty()) {
+ loaded = 0;
+ continue;
+ }
+ offset += limit;
+ loaded = metadata.size();
+ 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 searchValues query
+ * @param researcher item to extract metadata from
+ * @param limit for pagination purpose
+ * @param offset for pagination purpose
+ * @return list of ImportRecord
+ */
+ private List getImportRecords(List searchValues,
+ Item researcher, int offset, int limit) {
+ List matchingRecords = new ArrayList<>();
+ for (String searchValue : searchValues) {
+ matchingRecords.addAll(
+ primaryProvider.searchExternalDataObjects(searchValue, offset, limit));
+ }
+ 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/app/suggestion/openaire/PublicationLoaderCliScriptConfiguration.java b/dspace-api/src/main/java/org/dspace/app/suggestion/openaire/PublicationLoaderCliScriptConfiguration.java
new file mode 100644
index 0000000000..f5289fd99a
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/suggestion/openaire/PublicationLoaderCliScriptConfiguration.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.openaire;
+
+import org.apache.commons.cli.Options;
+
+/**
+ * Extension of {@link PublicationLoaderScriptConfiguration} for CLI.
+ *
+ * @author Alessandro Martelli (alessandro.martelli at 4science.it)
+ */
+public class PublicationLoaderCliScriptConfiguration
+ extends PublicationLoaderScriptConfiguration {
+
+ @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/openaire/PublicationLoaderRunnable.java b/dspace-api/src/main/java/org/dspace/app/suggestion/openaire/PublicationLoaderRunnable.java
new file mode 100644
index 0000000000..a408c448e9
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/suggestion/openaire/PublicationLoaderRunnable.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.suggestion.openaire;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.commons.cli.ParseException;
+import org.dspace.content.Item;
+import org.dspace.core.Context;
+import org.dspace.discovery.DiscoverQuery;
+import org.dspace.discovery.SearchService;
+import org.dspace.discovery.SearchServiceException;
+import org.dspace.discovery.SearchUtils;
+import org.dspace.discovery.utils.DiscoverQueryBuilder;
+import org.dspace.discovery.utils.parameter.QueryBuilderSearchFilter;
+import org.dspace.scripts.DSpaceRunnable;
+import org.dspace.sort.SortOption;
+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 PublicationLoaderRunnable
+ extends DSpaceRunnable> {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(PublicationLoaderRunnable.class);
+
+ private PublicationLoader oairePublicationLoader = null;
+
+ protected Context context;
+
+ protected String profile;
+
+ @Override
+ @SuppressWarnings({ "rawtypes", "unchecked" })
+ public PublicationLoaderScriptConfiguration getScriptConfiguration() {
+ PublicationLoaderScriptConfiguration configuration = new DSpace().getServiceManager()
+ .getServiceByName("import-openaire-suggestions", PublicationLoaderScriptConfiguration.class);
+ return configuration;
+ }
+
+ @Override
+ public void setup() throws ParseException {
+
+ oairePublicationLoader = new DSpace().getServiceManager().getServiceByName(
+ "OpenairePublicationLoader", PublicationLoader.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();
+
+ Iterator- researchers = getResearchers(profile);
+ while (researchers.hasNext()) {
+ Item researcher = researchers.next();
+ 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 profileUUID uuid of the researcher. If null, all researcher will be
+ * returned.
+ * @return the researcher with specified UUID or all researchers
+ */
+ @SuppressWarnings("rawtypes")
+ private Iterator
- getResearchers(String profileUUID) {
+ SearchService searchService = new DSpace().getSingletonService(SearchService.class);
+ DiscoverQueryBuilder queryBuilder = SearchUtils.getQueryBuilder();
+ List filters = new ArrayList();
+ String query = "*:*";
+ if (profileUUID != null) {
+ query = "search.resourceid:" + profileUUID.toString();
+ }
+ try {
+ DiscoverQuery discoverQuery = queryBuilder.buildQuery(context, null,
+ SearchUtils.getDiscoveryConfigurationByName("person"),
+ query, filters,
+ "Item", 10, Long.getLong("0"), null, SortOption.DESCENDING);
+ return searchService.iteratorSearch(context, null, discoverQuery);
+ } catch (SearchServiceException e) {
+ LOGGER.error("Unable to read researcher on solr", e);
+ }
+ return null;
+ }
+}
diff --git a/dspace-api/src/main/java/org/dspace/app/suggestion/openaire/PublicationLoaderRunnableCli.java b/dspace-api/src/main/java/org/dspace/app/suggestion/openaire/PublicationLoaderRunnableCli.java
new file mode 100644
index 0000000000..6c59b725d5
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/suggestion/openaire/PublicationLoaderRunnableCli.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.openaire;
+
+import org.apache.commons.cli.HelpFormatter;
+import org.apache.commons.cli.ParseException;
+import org.dspace.utils.DSpace;
+
+public class PublicationLoaderRunnableCli extends PublicationLoaderRunnable {
+
+ @Override
+ @SuppressWarnings({ "rawtypes", "unchecked" })
+ public PublicationLoaderCliScriptConfiguration getScriptConfiguration() {
+ PublicationLoaderCliScriptConfiguration configuration = new DSpace().getServiceManager()
+ .getServiceByName("import-openaire-suggestions", PublicationLoaderCliScriptConfiguration.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 Researchers Suggestions", getScriptConfiguration().getOptions());
+ System.exit(0);
+ }
+ }
+
+}
diff --git a/dspace-api/src/main/java/org/dspace/app/suggestion/openaire/PublicationLoaderScriptConfiguration.java b/dspace-api/src/main/java/org/dspace/app/suggestion/openaire/PublicationLoaderScriptConfiguration.java
new file mode 100644
index 0000000000..8bef7de40d
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/suggestion/openaire/PublicationLoaderScriptConfiguration.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.openaire;
+
+import org.apache.commons.cli.Options;
+import org.dspace.scripts.configuration.ScriptConfiguration;
+
+public class PublicationLoaderScriptConfiguration
+ extends ScriptConfiguration {
+
+ private Class dspaceRunnableClass;
+
+ @Override
+ public Class getDspaceRunnableClass() {
+ return dspaceRunnableClass;
+ }
+
+ /**
+ * Generic setter for the dspaceRunnableClass
+ * @param dspaceRunnableClass The dspaceRunnableClass to be set on this PublicationLoaderScriptConfiguration
+ */
+ @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/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-api/src/main/java/org/dspace/app/util/DCInputsReader.java b/dspace-api/src/main/java/org/dspace/app/util/DCInputsReader.java
index 38692c73a6..8dc8239ca5 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
@@ -14,7 +14,6 @@ import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
-import javax.servlet.ServletException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.FactoryConfigurationError;
@@ -150,17 +149,16 @@ public class DCInputsReader {
* Returns the set of DC inputs used for a particular collection, or the
* default set if no inputs defined for the collection
*
- * @param collectionHandle collection's unique Handle
+ * @param collection collection for which search the set of DC inputs
* @return DC input set
* @throws DCInputsReaderException if no default set defined
- * @throws ServletException
*/
- public List getInputsByCollectionHandle(String collectionHandle)
+ public List getInputsByCollection(Collection collection)
throws DCInputsReaderException {
SubmissionConfig config;
try {
config = SubmissionServiceFactory.getInstance().getSubmissionConfigService()
- .getSubmissionConfigByCollection(collectionHandle);
+ .getSubmissionConfigByCollection(collection);
String formName = config.getSubmissionName();
if (formName == null) {
throw new DCInputsReaderException("No form designated as default");
@@ -691,7 +689,7 @@ public class DCInputsReader {
public String getInputFormNameByCollectionAndField(Collection collection, String field)
throws DCInputsReaderException {
- List inputSets = getInputsByCollectionHandle(collection.getHandle());
+ List inputSets = getInputsByCollection(collection);
for (DCInputSet inputSet : inputSets) {
String[] tokenized = Utils.tokenize(field);
String schema = tokenized[0];
diff --git a/dspace-api/src/main/java/org/dspace/app/util/SubmissionConfigReader.java b/dspace-api/src/main/java/org/dspace/app/util/SubmissionConfigReader.java
index 0f144fd69f..1c91b40b97 100644
--- a/dspace-api/src/main/java/org/dspace/app/util/SubmissionConfigReader.java
+++ b/dspace-api/src/main/java/org/dspace/app/util/SubmissionConfigReader.java
@@ -11,6 +11,7 @@ import java.io.File;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
@@ -21,6 +22,7 @@ import javax.xml.parsers.FactoryConfigurationError;
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.DSpaceObject;
import org.dspace.content.factory.ContentServiceFactory;
import org.dspace.content.service.CollectionService;
@@ -90,6 +92,13 @@ public class SubmissionConfigReader {
*/
private Map collectionToSubmissionConfig = null;
+ /**
+ * Hashmap which stores which submission process configuration is used by
+ * which community, computed from the item submission config file
+ * (specifically, the 'submission-map' tag)
+ */
+ private Map communityToSubmissionConfig = null;
+
/**
* Reference to the global submission step definitions defined in the
* "step-definitions" section
@@ -127,6 +136,7 @@ public class SubmissionConfigReader {
public void reload() throws SubmissionConfigReaderException {
collectionToSubmissionConfig = null;
+ communityToSubmissionConfig = null;
stepDefns = null;
submitDefns = null;
buildInputs(configDir + SUBMIT_DEF_FILE_PREFIX + SUBMIT_DEF_FILE_SUFFIX);
@@ -145,7 +155,8 @@ public class SubmissionConfigReader {
*/
private void buildInputs(String fileName) throws SubmissionConfigReaderException {
collectionToSubmissionConfig = new HashMap();
- submitDefns = new HashMap>>();
+ communityToSubmissionConfig = new HashMap();
+ submitDefns = new LinkedHashMap>>();
String uri = "file:" + new File(fileName).getAbsolutePath();
@@ -210,18 +221,41 @@ public class SubmissionConfigReader {
* Returns the Item Submission process config used for a particular
* collection, or the default if none is defined for the collection
*
- * @param collectionHandle collection's unique Handle
+ * @param col collection for which search Submission process config
* @return the SubmissionConfig representing the item submission config
- * @throws SubmissionConfigReaderException if no default submission process configuration defined
+ * @throws IllegalStateException if no default submission process configuration defined
*/
- public SubmissionConfig getSubmissionConfigByCollection(String collectionHandle) {
- // get the name of the submission process config for this collection
- String submitName = collectionToSubmissionConfig
- .get(collectionHandle);
- if (submitName == null) {
+ public SubmissionConfig getSubmissionConfigByCollection(Collection col) {
+
+ String submitName;
+
+ if (col != null) {
+
+ // get the name of the submission process config for this collection
submitName = collectionToSubmissionConfig
- .get(DEFAULT_COLLECTION);
+ .get(col.getHandle());
+ if (submitName != null) {
+ return getSubmissionConfigByName(submitName);
+ }
+
+ if (!communityToSubmissionConfig.isEmpty()) {
+ try {
+ List communities = col.getCommunities();
+ for (Community com : communities) {
+ submitName = getSubmissionConfigByCommunity(com);
+ if (submitName != null) {
+ return getSubmissionConfigByName(submitName);
+ }
+ }
+ } catch (SQLException sqle) {
+ throw new IllegalStateException("Error occurred while getting item submission configured " +
+ "by community", sqle);
+ }
+ }
}
+
+ submitName = collectionToSubmissionConfig.get(DEFAULT_COLLECTION);
+
if (submitName == null) {
throw new IllegalStateException(
"No item submission process configuration designated as 'default' in 'submission-map' section of " +
@@ -230,6 +264,30 @@ public class SubmissionConfigReader {
return getSubmissionConfigByName(submitName);
}
+ /**
+ * Recursive function to return the Item Submission process config
+ * used for a community or the closest community parent, or null
+ * if none is defined
+ *
+ * @param com community for which search Submission process config
+ * @return the SubmissionConfig representing the item submission config
+ */
+ private String getSubmissionConfigByCommunity(Community com) {
+ String submitName = communityToSubmissionConfig
+ .get(com.getHandle());
+ if (submitName != null) {
+ return submitName;
+ }
+ List communities = com.getParentCommunities();
+ for (Community parentCom : communities) {
+ submitName = getSubmissionConfigByCommunity(parentCom);
+ if (submitName != null) {
+ return submitName;
+ }
+ }
+ return null;
+ }
+
/**
* Returns the Item Submission process config
*
@@ -357,13 +415,14 @@ public class SubmissionConfigReader {
Node nd = nl.item(i);
if (nd.getNodeName().equals("name-map")) {
String id = getAttribute(nd, "collection-handle");
+ String communityId = getAttribute(nd, "community-handle");
String entityType = getAttribute(nd, "collection-entity-type");
String value = getAttribute(nd, "submission-name");
String content = getValue(nd);
- if (id == null && entityType == null) {
+ if (id == null && communityId == null && entityType == null) {
throw new SAXException(
- "name-map element is missing collection-handle or collection-entity-type attribute " +
- "in 'item-submission.xml'");
+ "name-map element is missing collection-handle or community-handle or collection-entity-type " +
+ "attribute in 'item-submission.xml'");
}
if (value == null) {
throw new SAXException(
@@ -375,7 +434,8 @@ public class SubmissionConfigReader {
}
if (id != null) {
collectionToSubmissionConfig.put(id, value);
-
+ } else if (communityId != null) {
+ communityToSubmissionConfig.put(communityId, value);
} else {
// get all collections for this entity-type
List collections = collectionService.findAllCollectionsByEntityType( context,
diff --git a/dspace-api/src/main/java/org/dspace/app/util/Util.java b/dspace-api/src/main/java/org/dspace/app/util/Util.java
index f8ef3b1731..3bc828d6c4 100644
--- a/dspace-api/src/main/java/org/dspace/app/util/Util.java
+++ b/dspace-api/src/main/java/org/dspace/app/util/Util.java
@@ -405,21 +405,13 @@ public class Util {
DCInput myInputs = null;
boolean myInputsFound = false;
String formFileName = I18nUtil.getInputFormsFileName(locale);
- String col_handle = "";
Collection collection = item.getOwningCollection();
- if (collection == null) {
- // set an empty handle so to get the default input set
- col_handle = "";
- } else {
- col_handle = collection.getHandle();
- }
-
// Read the input form file for the specific collection
DCInputsReader inputsReader = new DCInputsReader(formFileName);
- List inputSets = inputsReader.getInputsByCollectionHandle(col_handle);
+ List inputSets = inputsReader.getInputsByCollection(collection);
// Replace the values of Metadatum[] with the correct ones in case
// of
@@ -500,8 +492,8 @@ public class Util {
public static List differenceInSubmissionFields(Collection fromCollection, Collection toCollection)
throws DCInputsReaderException {
DCInputsReader reader = new DCInputsReader();
- List from = reader.getInputsByCollectionHandle(fromCollection.getHandle());
- List to = reader.getInputsByCollectionHandle(toCollection.getHandle());
+ List from = reader.getInputsByCollection(fromCollection);
+ List to = reader.getInputsByCollection(toCollection);
Set fromFieldName = new HashSet<>();
Set toFieldName = new HashSet<>();
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 1aadbea162..89e42c182b 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.
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/authority/ChoiceAuthorityServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java
index 34ba9e8c45..f4d1f02710 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
@@ -242,7 +242,7 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService
// check if it is the requested collection
Map controllerFormDef = controllerFormDefinitions.get(fieldKey);
SubmissionConfig submissionConfig = submissionConfigService
- .getSubmissionConfigByCollection(collection.getHandle());
+ .getSubmissionConfigByCollection(collection);
String submissionName = submissionConfig.getSubmissionName();
// check if the requested collection has a submission definition that use an authority for the metadata
if (controllerFormDef.containsKey(submissionName)) {
@@ -495,7 +495,7 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService
try {
configReaderService = SubmissionServiceFactory.getInstance().getSubmissionConfigService();
SubmissionConfig submissionName = configReaderService
- .getSubmissionConfigByCollection(collection.getHandle());
+ .getSubmissionConfigByCollection(collection);
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/DCInputAuthority.java b/dspace-api/src/main/java/org/dspace/content/authority/DCInputAuthority.java
index b1d8cf36a5..902bded33e 100644
--- a/dspace-api/src/main/java/org/dspace/content/authority/DCInputAuthority.java
+++ b/dspace-api/src/main/java/org/dspace/content/authority/DCInputAuthority.java
@@ -156,7 +156,8 @@ public class DCInputAuthority extends SelfNamedPlugin implements ChoiceAuthority
int found = 0;
List v = new ArrayList();
for (int i = 0; i < valuesLocale.length; ++i) {
- if (query == null || StringUtils.containsIgnoreCase(valuesLocale[i], query)) {
+ // In a DCInputAuthority context, a user will want to query the labels, not the values
+ if (query == null || StringUtils.containsIgnoreCase(labelsLocale[i], query)) {
if (found >= start && v.size() < limit) {
v.add(new Choice(null, valuesLocale[i], labelsLocale[i]));
if (valuesLocale[i].equalsIgnoreCase(query)) {
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 6730e6ab4a..965ab569eb 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,21 +23,11 @@ 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.core.UUIDIterator;
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.
@@ -193,120 +182,6 @@ public class ItemDAOImpl extends AbstractHibernateDSODAO
- implements ItemDA
return new UUIDIterator
- (context, uuids, Item.class, this);
}
- 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-api/src/main/java/org/dspace/ctask/general/RequiredMetadata.java b/dspace-api/src/main/java/org/dspace/ctask/general/RequiredMetadata.java
index 07bfed5fe5..2899e3f6bd 100644
--- a/dspace-api/src/main/java/org/dspace/ctask/general/RequiredMetadata.java
+++ b/dspace-api/src/main/java/org/dspace/ctask/general/RequiredMetadata.java
@@ -17,6 +17,7 @@ import org.dspace.app.util.DCInput;
import org.dspace.app.util.DCInputSet;
import org.dspace.app.util.DCInputsReader;
import org.dspace.app.util.DCInputsReaderException;
+import org.dspace.content.Collection;
import org.dspace.content.DSpaceObject;
import org.dspace.content.Item;
import org.dspace.content.MetadataValue;
@@ -69,7 +70,7 @@ public class RequiredMetadata extends AbstractCurationTask {
handle = "in workflow";
}
sb.append("Item: ").append(handle);
- for (String req : getReqList(item.getOwningCollection().getHandle())) {
+ for (String req : getReqList(item.getOwningCollection())) {
List vals = itemService.getMetadataByMetadataString(item, req);
if (vals.size() == 0) {
sb.append(" missing required field: ").append(req);
@@ -91,14 +92,14 @@ public class RequiredMetadata extends AbstractCurationTask {
}
}
- protected List getReqList(String handle) throws DCInputsReaderException {
- List reqList = reqMap.get(handle);
+ protected List getReqList(Collection collection) throws DCInputsReaderException {
+ List reqList = reqMap.get(collection.getHandle());
if (reqList == null) {
reqList = reqMap.get("default");
}
if (reqList == null) {
reqList = new ArrayList();
- List inputSet = reader.getInputsByCollectionHandle(handle);
+ List inputSet = reader.getInputsByCollection(collection);
for (DCInputSet inputs : inputSet) {
for (DCInput[] row : inputs.getFields()) {
for (DCInput input : row) {
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..b70e9162f7 100644
--- a/dspace-api/src/main/java/org/dspace/discovery/IndexClient.java
+++ b/dspace-api/src/main/java/org/dspace/discovery/IndexClient.java
@@ -7,14 +7,20 @@
*/
package org.dspace.discovery;
+import static org.dspace.discovery.IndexClientOptions.TYPE_OPTION;
+
import java.io.IOException;
import java.sql.SQLException;
+import java.util.Arrays;
import java.util.Iterator;
+import java.util.List;
import java.util.Optional;
import java.util.UUID;
+import java.util.stream.Collectors;
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 +57,17 @@ public class IndexClient extends DSpaceRunnable indexableObjectTypes = IndexObjectFactoryFactory.getInstance().getIndexFactories().stream()
+ .map((indexFactory -> indexFactory.getType())).collect(Collectors.toList());
+ type = commandLine.getOptionValue(TYPE_OPTION);
+ if (!indexableObjectTypes.contains(type)) {
+ handler.handleException(String.format("%s is not a valid indexable object type, options: %s",
+ type, Arrays.toString(indexableObjectTypes.toArray())));
+ }
+ }
+
/** Acquire from dspace-services in future */
/**
* new DSpace.getServiceManager().getServiceByName("org.dspace.discovery.SolrIndexer");
@@ -113,6 +130,10 @@ 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,
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..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
@@ -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);
+
}
}
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/datamodel/ImportRecord.java b/dspace-api/src/main/java/org/dspace/importer/external/datamodel/ImportRecord.java
index 3fc34dc511..cbd4bc1245 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,31 @@ public class ImportRecord {
return values;
}
+ /**
+ * Returns an {@code Optional} representing the value
+ * of the metadata {@code field} found inside the {@code valueList}.
+ * @param field String of the MetadataField to search
+ * @return {@code Optional} non empty if found.
+ */
+ public Optional getSingleValue(String field) {
+ MetadataFieldName metadataFieldName = new MetadataFieldName(field);
+ return getSingleValue(metadataFieldName.schema, metadataFieldName.element, metadataFieldName.qualifier);
+ }
+
+ /**
+ * Retrieves a single value for the given schema, element, and qualifier.
+ *
+ * @param schema the schema for the value
+ * @param element the element for the value
+ * @param qualifier the qualifier for the value
+ * @return an optional containing the single value, if present
+ */
+ 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..52c6422554
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/RorParentOrgUnitMetadataContributor.java
@@ -0,0 +1,130 @@
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in 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;
+
+/**
+ * A ROR JsonPath Metadata processor that should be configured inside the {@code ror-integration.xml} file.
+ * This allows the extraction of a given contributor with a specific mappings from the ROR JSON response.
+ *
+ * @author Vincenzo Mecca (vins01-4science - vincenzo.mecca at 4science.com)
+ */
+public class RorParentOrgUnitMetadataContributor extends SimpleJsonPathMetadataContributor {
+
+ /**
+ * Determines which field of the JSON detains the {@code type} of this
+ * specific node (that needs to be mapped).
+ *
+ */
+ private String typeField;
+
+ /**
+ * Determines which is the type of the main parent node that needs to be mapped.
+ * It should match the value of the {@code typeField} of the JSON node.
+ *
+ */
+ private String parentType;
+
+ /**
+ * Determines which is the field of the JSON that contains the value
+ * that needs to be mapped into a {@code MetadatumDTO}.
+ */
+ private String labelField;
+
+ /**
+ * Creates a {@code MetadatumDTO} for each correctly mapped JSON node
+ * of the ROR response.
+ * Partial / Unmatched parent-type metadatum will be ignored from this mapping.
+ *
+ * @param fullJson ROR response
+ * @return a collection of read ROR metadata.
+ */
+ @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/metadatamapping/contributor/SimpleXpathDateFormatMetadataContributor.java b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleXpathDateFormatMetadataContributor.java
index fb15cd60ab..f3a186e450 100644
--- 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
@@ -87,5 +87,4 @@ public class SimpleXpathDateFormatMetadataContributor extends SimpleXpathMetadat
dcValue.setSchema(field.getSchema());
return dcValue;
}
-
-}
\ No newline at end of file
+}
diff --git a/dspace-api/src/main/java/org/dspace/importer/external/openaire/metadatamapping/OpenAIREPublicationFieldMapping.java b/dspace-api/src/main/java/org/dspace/importer/external/openaire/metadatamapping/OpenAIREPublicationFieldMapping.java
new file mode 100644
index 0000000000..d58ffc8ca9
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/importer/external/openaire/metadatamapping/OpenAIREPublicationFieldMapping.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.importer.external.openaire.metadatamapping;
+
+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 OpenAIRE metadatum fields on the DSpace metadatum
+ * fields
+ *
+ * @author Mykhaylo Boychuk (4science.it)
+ */
+public class OpenAIREPublicationFieldMapping extends AbstractMetadataFieldMapping {
+
+ @Override
+ @Resource(name = "openairePublicationsMetadataFieldMap")
+ public void setMetadataFieldMap(Map metadataFieldMap) {
+ super.setMetadataFieldMap(metadataFieldMap);
+ }
+}
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
new file mode 100644
index 0000000000..0e59c9eb0e
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/importer/external/openaire/service/OpenAireImportMetadataSourceServiceImpl.java
@@ -0,0 +1,353 @@
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in 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.openaire.service;
+
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.Callable;
+import javax.el.MethodNotFoundException;
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.client.Invocation;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.Response;
+
+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.metadatamapping.MetadatumDTO;
+import org.dspace.importer.external.service.AbstractImportMetadataSourceService;
+import org.dspace.importer.external.service.components.QuerySource;
+import org.dspace.services.ConfigurationService;
+import org.jdom2.Document;
+import org.jdom2.Element;
+import org.jdom2.JDOMException;
+import org.jdom2.Namespace;
+import org.jdom2.filter.Filters;
+import org.jdom2.input.SAXBuilder;
+import org.jdom2.xpath.XPathExpression;
+import org.jdom2.xpath.XPathFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * Implements a data source for querying OpenAIRE
+ *
+ * @author Pasquale Cavallo (pasquale.cavallo at 4science dot it)
+ */
+public class OpenAireImportMetadataSourceServiceImpl extends AbstractImportMetadataSourceService
+ implements QuerySource {
+
+ @Autowired(required = true)
+ protected ConfigurationService configurationService;
+
+ 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 = configurationService.getProperty("openaire.base.url");
+ }
+ 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 (Element 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);
+
+ SAXBuilder saxBuilder = new SAXBuilder();
+ Document document = saxBuilder.build(new StringReader(responseString));
+ Element root = document.getRootElement();
+
+ XPathExpression xpath = XPathFactory.instance().compile("/header/total",
+ Filters.element(), null);
+
+ Element totalItem = (Element) xpath.evaluateFirst(root);
+ return totalItem != null ? Integer.parseInt(totalItem.getText()) : null;
+
+ } 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 (Element 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) {
+
+ try {
+ SAXBuilder saxBuilder = new SAXBuilder();
+ Document document = saxBuilder.build(new StringReader(recordsSrc));
+ Element root = document.getRootElement();
+
+ List namespaces = Arrays.asList(
+ 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",
+ Filters.element(), null, namespaces);
+
+ List recordsList = xpath.evaluate(root);
+ return recordsList;
+ } catch (JDOMException | IOException e) {
+ return null;
+ }
+ }
+
+
+
+}
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..d7caeffdba
--- /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 ROR metadatum fields on the DSpace metadatum fields
+ *
+ * @author Vincenzo Mecca (vins01-4science - vincenzo.mecca at 4science.com)
+ */
+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..59063271f3
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/importer/external/ror/service/RorImportMetadataSourceServiceImpl.java
@@ -0,0 +1,278 @@
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in 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;
+
+/**
+ * Implements a {@code AbstractImportMetadataSourceService} for querying ROR services.
+ *
+ * @author Vincenzo Mecca (vins01-4science - vincenzo.mecca at 4science.com)
+ */
+public class RorImportMetadataSourceServiceImpl extends AbstractImportMetadataSourceService
+ implements QuerySource {
+
+ private final static Logger log = LogManager.getLogger();
+ protected static final String ROR_IDENTIFIER_PREFIX = "https://ror.org/";
+
+ 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 ROR 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 ROR 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 a
+ * ROR query. This Callable uses as query value to ROR the string queryString
+ * passed to constructor. If the object will be construct through {@code Query}
+ * instance, the value of the Query's map with the key "query" will be used.
+ *
+ * @author Vincenzo Mecca (vins01-4science - vincenzo.mecca at 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));
+ }
+ }
+
+ /**
+ * Counts the number of results for the given query.
+ *
+ * @param query the query string to count results for
+ * @return the number of results for the given query
+ */
+ 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 importResults = new ArrayList<>();
+
+ id = StringUtils.removeStart(id, ROR_IDENTIFIER_PREFIX);
+
+ 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 importResults;
+ }
+
+ JsonNode jsonNode = convertStringJsonToJsonNode(resp);
+ importResults.add(transformSourceRecords(jsonNode.toString()));
+
+ } catch (URISyntaxException e) {
+ e.printStackTrace();
+ }
+ return importResults;
+ }
+
+ private List search(String query) {
+ List importResults = 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 importResults;
+ }
+
+ JsonNode jsonNode = convertStringJsonToJsonNode(resp);
+ JsonNode docs = jsonNode.at("/items");
+ if (docs.isArray()) {
+ Iterator nodes = docs.elements();
+ while (nodes.hasNext()) {
+ JsonNode node = nodes.next();
+ importResults.add(transformSourceRecords(node.toString()));
+ }
+ } else {
+ importResults.add(transformSourceRecords(docs.toString()));
+ }
+ } catch (URISyntaxException e) {
+ e.printStackTrace();
+ }
+ return importResults;
+ }
+
+ 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/java/org/dspace/submit/service/SubmissionConfigService.java b/dspace-api/src/main/java/org/dspace/submit/service/SubmissionConfigService.java
index c4b111a38f..36ba82f60d 100644
--- a/dspace-api/src/main/java/org/dspace/submit/service/SubmissionConfigService.java
+++ b/dspace-api/src/main/java/org/dspace/submit/service/SubmissionConfigService.java
@@ -34,7 +34,7 @@ public interface SubmissionConfigService {
public int countSubmissionConfigs();
- public SubmissionConfig getSubmissionConfigByCollection(String collectionHandle);
+ public SubmissionConfig getSubmissionConfigByCollection(Collection collection);
public SubmissionConfig getSubmissionConfigByName(String submitName);
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
index a72bcc2c3b..fe063954a1 100644
--- a/dspace-api/src/main/java/org/dspace/submit/service/SubmissionConfigServiceImpl.java
+++ b/dspace-api/src/main/java/org/dspace/submit/service/SubmissionConfigServiceImpl.java
@@ -57,8 +57,8 @@ public class SubmissionConfigServiceImpl implements SubmissionConfigService, Ini
}
@Override
- public SubmissionConfig getSubmissionConfigByCollection(String collectionHandle) {
- return submissionConfigReader.getSubmissionConfigByCollection(collectionHandle);
+ public SubmissionConfig getSubmissionConfigByCollection(Collection collection) {
+ return submissionConfigReader.getSubmissionConfigByCollection(collection);
}
@Override
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..7f6f872ce0 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
@@ -123,6 +123,20 @@
+
+
+
+
+
+
+
+
+
+
+
@@ -150,6 +164,12 @@
+
+
+
+
+
+
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/item-submission.xml b/dspace-api/src/test/data/dspaceFolder/config/item-submission.xml
index 452460501a..921306ca2b 100644
--- a/dspace-api/src/test/data/dspaceFolder/config/item-submission.xml
+++ b/dspace-api/src/test/data/dspaceFolder/config/item-submission.xml
@@ -24,6 +24,9 @@
+
+
+
@@ -257,6 +260,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
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 37e1fb5089..83d45b38cc 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 29703e3ee0..0a7d7c5196 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,12 +47,14 @@
-
+
-
+
+
+
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/app/suggestion/SuggestionUtilsIT.java b/dspace-api/src/test/java/org/dspace/app/suggestion/SuggestionUtilsIT.java
new file mode 100644
index 0000000000..dd9c0d8f5f
--- /dev/null
+++ b/dspace-api/src/test/java/org/dspace/app/suggestion/SuggestionUtilsIT.java
@@ -0,0 +1,218 @@
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in the 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 java.util.Optional.of;
+import static org.dspace.app.suggestion.SuggestionUtils.getFirstEntryByMetadatum;
+import static org.dspace.orcid.model.OrcidProfileSectionType.EXTERNAL_IDS;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.greaterThan;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.io.File;
+import java.net.URL;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Optional;
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.Unmarshaller;
+
+import org.apache.commons.collections.CollectionUtils;
+import org.dspace.AbstractIntegrationTestWithDatabase;
+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.content.MetadataValue;
+import org.dspace.content.dto.MetadataValueDTO;
+import org.dspace.content.factory.ContentServiceFactory;
+import org.dspace.content.service.ItemService;
+import org.dspace.external.factory.ExternalServiceFactory;
+import org.dspace.external.model.ExternalDataObject;
+import org.dspace.external.provider.ExternalDataProvider;
+import org.dspace.external.provider.impl.OrcidPublicationDataProvider;
+import org.dspace.external.service.ExternalDataService;
+import org.dspace.kernel.ServiceManager;
+import org.dspace.orcid.client.OrcidClient;
+import org.dspace.orcid.client.OrcidConfiguration;
+import org.dspace.orcid.factory.OrcidServiceFactory;
+import org.dspace.orcid.model.OrcidTokenResponseDTO;
+import org.dspace.orcid.service.OrcidProfileSectionFactoryService;
+import org.dspace.services.ConfigurationService;
+import org.dspace.services.factory.DSpaceServicesFactory;
+import org.dspace.utils.DSpace;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.orcid.jaxb.model.v3.release.record.Work;
+import org.orcid.jaxb.model.v3.release.record.WorkBulk;
+import org.orcid.jaxb.model.v3.release.record.summary.Works;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * Tests for suggestion utilities @see SuggestionUtils
+ * @author Francesco Bacchelli (francesco.bacchelli at 4science.it)
+ */
+public class SuggestionUtilsIT extends AbstractIntegrationTestWithDatabase {
+
+ private static ConfigurationService cfg;
+ private static final String ORCID = "0000-1111-2222-3333";
+ private static final String ACCESS_TOKEN = "32c83ccb-c6d5-4981-b6ea-6a34a36de8ab";
+ private static final String BASE_XML_DIR_PATH = "org/dspace/app/orcid-works/";
+ private OrcidPublicationDataProvider dataProvider;
+ private SolrSuggestionProvider solrSuggestionProvider;
+ private OrcidProfileSectionFactoryService profileSectionFactoryService;
+ private ItemService itemService;
+ private Collection collection;
+ private ExternalDataProvider primaryProvider;
+ private Collection persons;
+ private OrcidConfiguration orcidConfiguration;
+ private OrcidClient orcidClientMock;
+ private OrcidClient orcidClient;
+ private String originalClientId;
+
+ @Autowired
+ private SuggestionService suggestionService;
+
+ @Before
+ public void setup() throws Exception {
+ context.turnOffAuthorisationSystem();
+
+ parentCommunity = CommunityBuilder.createCommunity(context)
+ .withName("Parent Community")
+ .build();
+
+ persons = CollectionBuilder.createCollection(context, parentCommunity)
+ .withEntityType("Person")
+ .withName("Profiles")
+ .build();
+
+ profileSectionFactoryService = OrcidServiceFactory.getInstance().getOrcidProfileSectionFactoryService();
+ itemService = ContentServiceFactory.getInstance().getItemService();
+
+ context.restoreAuthSystemState();
+
+ cfg = DSpaceServicesFactory.getInstance().getConfigurationService();
+
+ ServiceManager serviceManager = DSpaceServicesFactory.getInstance().getServiceManager();
+ HashMap providers = serviceManager.getServiceByName("suggestionProviders",
+ HashMap.class);
+ solrSuggestionProvider = (SolrSuggestionProvider) providers.get("scopus");
+ dataProvider = new DSpace().getServiceManager()
+ .getServiceByName("orcidPublicationDataProvider", OrcidPublicationDataProvider.class);
+ ExternalDataService externalDataService = ExternalServiceFactory.getInstance().getExternalDataService();
+ primaryProvider = externalDataService.getExternalDataProvider("openaireFunding");
+
+ orcidConfiguration = new DSpace().getServiceManager()
+ .getServiceByName("org.dspace.orcid.client.OrcidConfiguration", OrcidConfiguration.class);
+
+ orcidClientMock = mock(OrcidClient.class);
+ orcidClient = dataProvider.getOrcidClient();
+
+ dataProvider.setReadPublicAccessToken(null);
+ dataProvider.setOrcidClient(orcidClientMock);
+
+ originalClientId = orcidConfiguration.getClientId();
+ orcidConfiguration.setClientId("DSPACE-CLIENT-ID");
+ orcidConfiguration.setClientSecret("DSPACE-CLIENT-SECRET");
+
+ when(orcidClientMock.getReadPublicAccessToken()).thenReturn(buildTokenResponse(ACCESS_TOKEN));
+
+ when(orcidClientMock.getWorks(any(), eq(ORCID))).thenReturn(unmarshall("works.xml", Works.class));
+ when(orcidClientMock.getWorks(eq(ORCID))).thenReturn(unmarshall("works.xml", Works.class));
+
+ when(orcidClientMock.getObject(any(), eq(ORCID), any(), eq(Work.class)))
+ .then((invocation) -> of(unmarshall("work-" + invocation.getArgument(2) + ".xml", Work.class)));
+ when(orcidClientMock.getObject(eq(ORCID), any(), eq(Work.class)))
+ .then((invocation) -> of(unmarshall("work-" + invocation.getArgument(1) + ".xml", Work.class)));
+
+ when(orcidClientMock.getWorkBulk(any(), eq(ORCID), any()))
+ .then((invocation) -> unmarshallWorkBulk(invocation.getArgument(2)));
+ when(orcidClientMock.getWorkBulk(eq(ORCID), any()))
+ .then((invocation) -> unmarshallWorkBulk(invocation.getArgument(1)));
+ }
+
+ @After
+ public void after() {
+ dataProvider.setOrcidClient(orcidClient);
+ orcidConfiguration.setClientId(originalClientId);
+ }
+
+ @Test
+ public void testGetAllEntriesByMetadatum() {
+ context.turnOffAuthorisationSystem();
+ Item item = ItemBuilder.createItem(context, persons)
+ .withTitle("Test profile")
+ .withScopusAuthorIdentifier("SCOPUS-123456")
+ .withResearcherIdentifier("R-ID-01")
+ .build();
+ context.restoreAuthSystemState();
+
+ List values = List.of(getMetadata(item, "person.identifier.scopus-author-id", 0));
+
+ Object firstOrcidObject = profileSectionFactoryService.createOrcidObject(context, values, EXTERNAL_IDS);
+ Optional optional = dataProvider.getExternalDataObject(ORCID + "::277902");
+
+ ExternalDataObject externalDataObject = optional.get();
+ String openAireId = externalDataObject.getId();
+ Suggestion suggestion = new Suggestion(solrSuggestionProvider.getSourceName(), item, openAireId);
+ suggestion.getMetadata().add(
+ new MetadataValueDTO("dc", "title", null, null, "dcTitle"));
+ suggestion.setDisplay(getFirstEntryByMetadatum(externalDataObject, "dc", "title", null));
+ suggestion.getMetadata().add(new MetadataValueDTO("dc", "date", "issued", null, new Date().toString()));
+ suggestion.getMetadata().add(new MetadataValueDTO("dc", "description", "abstract", null, "description"));
+ suggestion.setExternalSourceUri(cfg.getProperty("dspace.server.url")
+ + "/api/integration/externalsources/" + primaryProvider.getSourceIdentifier() + "/entryValues/"
+ + openAireId);
+ List result = SuggestionUtils.getAllEntriesByMetadatum(externalDataObject, "dc", "title", null);
+
+ assertTrue(result != null && !result.isEmpty());
+
+ assertTrue(CollectionUtils.isEqualCollection(
+ SuggestionUtils.getAllEntriesByMetadatum(externalDataObject, "dc.title"),
+ result));
+
+ String firstResult = SuggestionUtils.getFirstEntryByMetadatum(externalDataObject, "dc", "title", null);
+ assertTrue("Another cautionary tale.".equalsIgnoreCase(firstResult));
+ firstResult = SuggestionUtils.getFirstEntryByMetadatum(externalDataObject, "dc.title");
+ assertTrue("Another cautionary tale.".equalsIgnoreCase(firstResult));
+ }
+
+ private MetadataValue getMetadata(Item item, String metadataField, int place) {
+ List values = itemService.getMetadataByMetadataString(item, metadataField);
+ assertThat(values.size(), greaterThan(place));
+ return values.get(place);
+ }
+
+ private OrcidTokenResponseDTO buildTokenResponse(String accessToken) {
+ OrcidTokenResponseDTO response = new OrcidTokenResponseDTO();
+ response.setAccessToken(accessToken);
+ return response;
+ }
+
+ private WorkBulk unmarshallWorkBulk(List putCodes) throws Exception {
+ return unmarshall("workBulk-" + String.join("-", putCodes) + ".xml", WorkBulk.class);
+ }
+
+ @SuppressWarnings("unchecked")
+ private T unmarshall(String fileName, Class clazz) throws Exception {
+ JAXBContext jaxbContext = JAXBContext.newInstance(clazz);
+ Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
+ URL resource = getClass().getClassLoader().getResource(BASE_XML_DIR_PATH + fileName);
+ if (resource == null) {
+ throw new IllegalStateException("No resource found named " + BASE_XML_DIR_PATH + fileName);
+ }
+ return (T) unmarshaller.unmarshal(new File(resource.getFile()));
+ }
+}
diff --git a/dspace-api/src/test/java/org/dspace/app/util/SubmissionConfigIT.java b/dspace-api/src/test/java/org/dspace/app/util/SubmissionConfigIT.java
new file mode 100644
index 0000000000..0db4926d22
--- /dev/null
+++ b/dspace-api/src/test/java/org/dspace/app/util/SubmissionConfigIT.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.util;
+
+import static org.junit.Assert.assertEquals;
+
+import org.dspace.AbstractIntegrationTestWithDatabase;
+import org.dspace.builder.CollectionBuilder;
+import org.dspace.builder.CommunityBuilder;
+import org.dspace.content.Collection;
+import org.dspace.content.Community;
+import org.dspace.submit.factory.SubmissionServiceFactory;
+import org.dspace.submit.service.SubmissionConfigService;
+import org.junit.Test;
+
+/**
+ * Integration Tests for parsing and utilities on submission config forms / readers
+ *
+ * @author Toni Prieto
+ */
+public class SubmissionConfigIT extends AbstractIntegrationTestWithDatabase {
+
+ @Test
+ public void testSubmissionMapByCommunityHandleSubmissionConfig()
+ throws SubmissionConfigReaderException {
+
+ context.turnOffAuthorisationSystem();
+ // Sep up a structure with one top community and two subcommunities with collections
+ Community topcom = CommunityBuilder.createCommunity(context, "123456789/topcommunity-test")
+ .withName("Parent Community")
+ .build();
+ Community subcom1 = CommunityBuilder.createSubCommunity(context, topcom, "123456789/subcommunity-test")
+ .withName("Subcommunity 1")
+ .build();
+ Community subcom2 = CommunityBuilder.createSubCommunity(context, topcom, "123456789/not-mapped3")
+ .withName("Subcommunity 2")
+ .build();
+ // col1 should use the form item submission form mapped for subcom1
+ Collection col1 = CollectionBuilder.createCollection(context, subcom1, "123456789/not-mapped1")
+ .withName("Collection 1")
+ .build();
+ // col2 should use the item submission form mapped for the top community
+ Collection col2 = CollectionBuilder.createCollection(context, subcom2, "123456789/not-mapped2")
+ .withName("Collection 2")
+ .build();
+ // col3 should use the item submission form directly mapped for this collection
+ Collection col3 = CollectionBuilder.createCollection(context, subcom1, "123456789/collection-test")
+ .withName("Collection 3")
+ .build();
+ context.restoreAuthSystemState();
+
+ SubmissionConfigService submissionConfigService = SubmissionServiceFactory.getInstance()
+ .getSubmissionConfigService();
+
+ // for col1, it should return the item submission form defined for their parent subcom1
+ SubmissionConfig submissionConfig1 = submissionConfigService.getSubmissionConfigByCollection(col1);
+ assertEquals("subcommunitytest", submissionConfig1.getSubmissionName());
+
+ // for col2, it should return the item submission form defined for topcom
+ SubmissionConfig submissionConfig2 = submissionConfigService.getSubmissionConfigByCollection(col2);
+ assertEquals("topcommunitytest", submissionConfig2.getSubmissionName());
+
+ // for col3, it should return the item submission form defined directly for the collection
+ SubmissionConfig submissionConfig3 = submissionConfigService.getSubmissionConfigByCollection(col3);
+ assertEquals("collectiontest", submissionConfig3.getSubmissionName());
+
+ }
+}
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 cb1f828b93..4ac1931098 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
@@ -9,17 +9,20 @@ package org.dspace.app.util;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
+import static org.mockito.Mockito.when;
import java.util.ArrayList;
import java.util.List;
import org.dspace.AbstractUnitTest;
+import org.dspace.content.Collection;
import org.dspace.submit.factory.SubmissionServiceFactory;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
+import org.mockito.Mock;
/**
* Tests for parsing and utilities on submission config forms / readers
@@ -30,6 +33,9 @@ public class SubmissionConfigTest extends AbstractUnitTest {
DCInputsReader inputReader;
+ @Mock
+ private Collection col1;
+
@BeforeClass
public static void setUpClass() {
}
@@ -56,6 +62,8 @@ public class SubmissionConfigTest extends AbstractUnitTest {
String typeBindSubmissionName = "typebindtest";
String typeBindSubmissionStepName = "typebindtest";
+ when(col1.getHandle()).thenReturn(typeBindHandle);
+
// Expected field lists from typebindtest form
List allConfiguredFields = new ArrayList<>();
allConfiguredFields.add("dc.title");
@@ -67,7 +75,7 @@ public class SubmissionConfigTest extends AbstractUnitTest {
// Get submission configuration
SubmissionConfig submissionConfig =
SubmissionServiceFactory.getInstance().getSubmissionConfigService()
- .getSubmissionConfigByCollection(typeBindHandle);
+ .getSubmissionConfigByCollection(col1);
// 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 775dfaabe2..013a18cd52 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.suggestion.SolrSuggestionStorageService;
import org.dspace.app.util.SubmissionConfigReaderException;
import org.dspace.authorize.AuthorizeException;
import org.dspace.authorize.factory.AuthorizeServiceFactory;
@@ -116,6 +117,7 @@ public abstract class AbstractBuilder {
static SubscribeService subscribeService;
static SupervisionOrderService supervisionOrderService;
static QAEventService qaEventService;
+ static SolrSuggestionStorageService solrSuggestionService;
protected Context context;
@@ -185,6 +187,7 @@ public abstract class AbstractBuilder {
subscribeService = ContentServiceFactory.getInstance().getSubscribeService();
supervisionOrderService = SupervisionOrderServiceFactory.getInstance().getSupervisionOrderService();
qaEventService = new DSpace().getSingletonService(QAEventService.class);
+ 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 f4f504e60f..5e9545fcaf 100644
--- a/dspace-api/src/test/java/org/dspace/builder/ItemBuilder.java
+++ b/dspace-api/src/test/java/org/dspace/builder/ItemBuilder.java
@@ -186,6 +186,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/ProcessBuilder.java b/dspace-api/src/test/java/org/dspace/builder/ProcessBuilder.java
index 0631e1b55a..fe8f7b8167 100644
--- a/dspace-api/src/test/java/org/dspace/builder/ProcessBuilder.java
+++ b/dspace-api/src/test/java/org/dspace/builder/ProcessBuilder.java
@@ -68,8 +68,8 @@ public class ProcessBuilder extends AbstractBuilder {
public ProcessBuilder withStartAndEndTime(String startTime, String endTime) throws ParseException {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("dd/MM/yyyy");
- process.setStartTime(simpleDateFormat.parse(startTime));
- process.setFinishedTime(simpleDateFormat.parse(endTime));
+ process.setStartTime(startTime == null ? null : simpleDateFormat.parse(startTime));
+ process.setFinishedTime(endTime == null ? null : simpleDateFormat.parse(endTime));
return this;
}
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-api/src/test/resources/org/dspace/app/orcid-works/works.xml b/dspace-api/src/test/resources/org/dspace/app/orcid-works/works.xml
index 411160ef8e..9ad07607e3 100644
--- a/dspace-api/src/test/resources/org/dspace/app/orcid-works/works.xml
+++ b/dspace-api/src/test/resources/org/dspace/app/orcid-works/works.xml
@@ -77,6 +77,7 @@
Another cautionary tale.
+ Second title
journal-article
diff --git a/dspace-oai/pom.xml b/dspace-oai/pom.xml
index b900ebe88d..db8b55c79b 100644
--- a/dspace-oai/pom.xml
+++ b/dspace-oai/pom.xml
@@ -156,7 +156,7 @@
org.hamcrest
- hamcrest-all
+ hamcrest
compile
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-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