mirror of
https://github.com/DSpace/DSpace.git
synced 2025-10-15 14:03:17 +00:00
Merge branch 'main' of https://github.com/DSpace/DSpace into DURACOM-225
This commit is contained in:
@@ -528,7 +528,7 @@
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hamcrest</groupId>
|
||||
<artifactId>hamcrest-all</artifactId>
|
||||
<artifactId>hamcrest</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@@ -620,7 +620,7 @@
|
||||
<dependency>
|
||||
<groupId>com.maxmind.geoip2</groupId>
|
||||
<artifactId>geoip2</artifactId>
|
||||
<version>2.11.0</version>
|
||||
<version>2.17.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.ant</groupId>
|
||||
@@ -784,7 +784,7 @@
|
||||
<dependency>
|
||||
<groupId>com.opencsv</groupId>
|
||||
<artifactId>opencsv</artifactId>
|
||||
<version>5.7.1</version>
|
||||
<version>5.9</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Email templating -->
|
||||
@@ -867,32 +867,32 @@
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-buffer</artifactId>
|
||||
<version>4.1.94.Final</version>
|
||||
<version>4.1.106.Final</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-transport</artifactId>
|
||||
<version>4.1.94.Final</version>
|
||||
<version>4.1.106.Final</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-transport-native-unix-common</artifactId>
|
||||
<version>4.1.94.Final</version>
|
||||
<version>4.1.106.Final</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-common</artifactId>
|
||||
<version>4.1.94.Final</version>
|
||||
<version>4.1.106.Final</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-handler</artifactId>
|
||||
<version>4.1.94.Final</version>
|
||||
<version>4.1.106.Final</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-codec</artifactId>
|
||||
<version>4.1.94.Final</version>
|
||||
<version>4.1.106.Final</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.velocity</groupId>
|
||||
@@ -902,7 +902,7 @@
|
||||
<dependency>
|
||||
<groupId>org.xmlunit</groupId>
|
||||
<artifactId>xmlunit-core</artifactId>
|
||||
<version>2.8.0</version>
|
||||
<version>2.9.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
@@ -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();
|
||||
}
|
||||
|
@@ -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<Suggestion> 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<SuggestionTarget> 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);
|
||||
}
|
@@ -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<Suggestion> 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<SuggestionTarget> 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;
|
||||
}
|
@@ -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<String> 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<String, Object> 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<String, Object> 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<Suggestion> 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<Suggestion> suggestions = new ArrayList<Suggestion>();
|
||||
for (SolrDocument solrDoc : response.getResults()) {
|
||||
suggestions.add(convertSolrDoc(context, solrDoc, source));
|
||||
}
|
||||
return suggestions;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SuggestionTarget> 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<SuggestionTarget> suggestionTargets = new ArrayList<SuggestionTarget>();
|
||||
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<SuggestionEvidence> evidences = new LinkedList<SuggestionEvidence>();
|
||||
try {
|
||||
evidences = jsonMapper.readValue(evidencesJson, new TypeReference<List<SuggestionEvidence>>() {});
|
||||
} 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));
|
||||
}
|
||||
}
|
@@ -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<SuggestionEvidence> evidences = new LinkedList<SuggestionEvidence>();
|
||||
|
||||
private List<MetadataValueDTO> metadata = new LinkedList<MetadataValueDTO>();
|
||||
|
||||
/** 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<SuggestionEvidence> getEvidences() {
|
||||
return evidences;
|
||||
}
|
||||
|
||||
public List<MetadataValueDTO> 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;
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
|
||||
}
|
@@ -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<SuggestionTarget> 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<Suggestion> 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);
|
||||
|
||||
}
|
@@ -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<SuggestionTarget> 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<SuggestionTarget> 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<SuggestionSource> 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<Suggestion> 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<SuggestionProvider> getSuggestionProviders();
|
||||
}
|
@@ -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<String, SuggestionProvider> providersMap;
|
||||
|
||||
@Override
|
||||
public List<SuggestionProvider> 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<SuggestionTarget> 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<SuggestionTarget> findByTarget(Context context, UUID target, int pageSize, long offset) {
|
||||
List<SuggestionTarget> fullSourceTargets = new ArrayList<SuggestionTarget>();
|
||||
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<SuggestionTarget>() {
|
||||
@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<SuggestionSource> findAllSources(Context context, int pageSize, long offset) {
|
||||
List<SuggestionSource> fullSources = getSources(context).stream().skip(offset).limit(pageSize)
|
||||
.collect(Collectors.toList());
|
||||
return fullSources;
|
||||
}
|
||||
|
||||
private List<SuggestionSource> getSources(Context context) {
|
||||
List<SuggestionSource> results = new ArrayList<SuggestionSource>();
|
||||
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<Suggestion> 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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
|
||||
}
|
@@ -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;
|
||||
}
|
||||
|
||||
}
|
@@ -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<String> 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<String> 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);
|
||||
}
|
||||
}
|
@@ -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<String> contributorMetadata;
|
||||
|
||||
private List<String> names;
|
||||
|
||||
@Autowired
|
||||
private ItemService itemService;
|
||||
|
||||
/**
|
||||
* returns the metadata key of the Item which to base the filter on
|
||||
* @return metadata key
|
||||
*/
|
||||
public List<String> getContributorMetadata() {
|
||||
return contributorMetadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* set the metadata key of the Item which to base the filter on
|
||||
*/
|
||||
public void setContributorMetadata(List<String> contributorMetadata) {
|
||||
this.contributorMetadata = contributorMetadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* return the metadata key of ImportRecord which to base the filter on
|
||||
* @return
|
||||
*/
|
||||
public List<String> getNames() {
|
||||
return names;
|
||||
}
|
||||
|
||||
/**
|
||||
* set the metadata key of ImportRecord which to base the filter on
|
||||
*/
|
||||
public void setNames(List<String> 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<String[]> names = searchMetadataValues(researcher);
|
||||
int maxNameLenght = names.stream().mapToInt(n -> n[0].length()).max().orElse(1);
|
||||
List<String> metadataAuthors = new ArrayList<>();
|
||||
for (String contributorMetadatum : contributorMetadata) {
|
||||
metadataAuthors.addAll(getAllEntriesByMetadatum(importRecord, contributorMetadatum));
|
||||
}
|
||||
List<String> normalizedMetadataAuthors = metadataAuthors.stream().map(x -> normalize(x))
|
||||
.collect(Collectors.toList());
|
||||
int idx = 0;
|
||||
for (String nMetadataAuthor : normalizedMetadataAuthors) {
|
||||
Optional<String[]> 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<String[]> searchMetadataValues(Item researcher) {
|
||||
List<String[]> authors = new ArrayList<String[]>();
|
||||
for (String name : names) {
|
||||
List<MetadataValue> 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());
|
||||
}
|
||||
|
||||
}
|
@@ -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<MetadataValue> 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;
|
||||
}
|
||||
}
|
@@ -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);
|
||||
|
||||
}
|
@@ -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<String> names;
|
||||
|
||||
private ExternalDataProvider primaryProvider;
|
||||
|
||||
private List<ExternalDataProvider> otherProviders;
|
||||
|
||||
@Autowired
|
||||
private ConfigurationService configurationService;
|
||||
|
||||
private List<EvidenceScorer> pipeline;
|
||||
|
||||
public void setPrimaryProvider(ExternalDataProvider primaryProvider) {
|
||||
this.primaryProvider = primaryProvider;
|
||||
}
|
||||
|
||||
public void setOtherProviders(List<ExternalDataProvider> otherProviders) {
|
||||
this.otherProviders = otherProviders;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the pipeline of Approver
|
||||
* @param pipeline list Approver
|
||||
*/
|
||||
public void setPipeline(List<EvidenceScorer> 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<Suggestion> reduceAndTransform(Item researcher, List<ExternalDataObject> importRecords) {
|
||||
List<Suggestion> results = new ArrayList<>();
|
||||
for (ExternalDataObject r : importRecords) {
|
||||
boolean skip = false;
|
||||
List<SuggestionEvidence> evidences = new ArrayList<SuggestionEvidence>();
|
||||
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<String> searchValues = searchMetadataValues(researcher);
|
||||
while (loaded > 0) {
|
||||
List<ExternalDataObject> metadata = getImportRecords(searchValues, researcher, offset, limit);
|
||||
if (metadata.isEmpty()) {
|
||||
loaded = 0;
|
||||
continue;
|
||||
}
|
||||
offset += limit;
|
||||
loaded = metadata.size();
|
||||
List<Suggestion> 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<String> getNames() {
|
||||
return names;
|
||||
}
|
||||
|
||||
public void setNames(List<String> 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<ExternalDataObject> getImportRecords(List<String> searchValues,
|
||||
Item researcher, int offset, int limit) {
|
||||
List<ExternalDataObject> matchingRecords = new ArrayList<>();
|
||||
for (String searchValue : searchValues) {
|
||||
matchingRecords.addAll(
|
||||
primaryProvider.searchExternalDataObjects(searchValue, offset, limit));
|
||||
}
|
||||
List<ExternalDataObject> 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<ExternalDataObject> removeDuplicates(List<ExternalDataObject> importRecords) {
|
||||
List<ExternalDataObject> 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<ExternalDataObject> 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<String> searchMetadataValues(Item researcher) {
|
||||
List<String> authors = new ArrayList<String>();
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -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<T extends PublicationLoaderRunnable>
|
||||
extends PublicationLoaderScriptConfiguration<T> {
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
}
|
@@ -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<PublicationLoaderScriptConfiguration<PublicationLoaderRunnable>> {
|
||||
|
||||
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<PublicationLoaderRunnable> 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<Item> 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<Item> getResearchers(String profileUUID) {
|
||||
SearchService searchService = new DSpace().getSingletonService(SearchService.class);
|
||||
DiscoverQueryBuilder queryBuilder = SearchUtils.getQueryBuilder();
|
||||
List<QueryBuilderSearchFilter> filters = new ArrayList<QueryBuilderSearchFilter>();
|
||||
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;
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -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<T extends PublicationLoaderRunnable>
|
||||
extends ScriptConfiguration<T> {
|
||||
|
||||
private Class<T> dspaceRunnableClass;
|
||||
|
||||
@Override
|
||||
public Class<T> getDspaceRunnableClass() {
|
||||
return dspaceRunnableClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic setter for the dspaceRunnableClass
|
||||
* @param dspaceRunnableClass The dspaceRunnableClass to be set on this PublicationLoaderScriptConfiguration
|
||||
*/
|
||||
@Override
|
||||
public void setDspaceRunnableClass(Class<T> 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;
|
||||
}
|
||||
|
||||
}
|
@@ -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
|
||||
*
|
||||
|
@@ -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<DCInputSet> getInputsByCollectionHandle(String collectionHandle)
|
||||
public List<DCInputSet> 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<DCInputSet> inputSets = getInputsByCollectionHandle(collection.getHandle());
|
||||
List<DCInputSet> inputSets = getInputsByCollection(collection);
|
||||
for (DCInputSet inputSet : inputSets) {
|
||||
String[] tokenized = Utils.tokenize(field);
|
||||
String schema = tokenized[0];
|
||||
|
@@ -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<String, String> 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<String, String> 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<String, String>();
|
||||
submitDefns = new HashMap<String, List<Map<String, String>>>();
|
||||
communityToSubmissionConfig = new HashMap<String, String>();
|
||||
submitDefns = new LinkedHashMap<String, List<Map<String, String>>>();
|
||||
|
||||
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<Community> 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<Community> 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<Collection> collections = collectionService.findAllCollectionsByEntityType( context,
|
||||
|
@@ -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<DCInputSet> inputSets = inputsReader.getInputsByCollectionHandle(col_handle);
|
||||
List<DCInputSet> 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<String> differenceInSubmissionFields(Collection fromCollection, Collection toCollection)
|
||||
throws DCInputsReaderException {
|
||||
DCInputsReader reader = new DCInputsReader();
|
||||
List<DCInputSet> from = reader.getInputsByCollectionHandle(fromCollection.getHandle());
|
||||
List<DCInputSet> to = reader.getInputsByCollectionHandle(toCollection.getHandle());
|
||||
List<DCInputSet> from = reader.getInputsByCollection(fromCollection);
|
||||
List<DCInputSet> to = reader.getInputsByCollection(toCollection);
|
||||
|
||||
Set<String> fromFieldName = new HashSet<>();
|
||||
Set<String> toFieldName = new HashSet<>();
|
||||
|
@@ -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.
|
||||
|
@@ -1435,16 +1435,6 @@ prevent the generation of resource policy entry values with null dspace_object a
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Item> findByMetadataQuery(Context context, List<List<MetadataField>> listFieldList,
|
||||
List<String> query_op, List<String> query_val, List<UUID> 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;
|
||||
|
@@ -242,7 +242,7 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService
|
||||
// check if it is the requested collection
|
||||
Map<String, ChoiceAuthority> 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
|
||||
|
@@ -156,7 +156,8 @@ public class DCInputAuthority extends SelfNamedPlugin implements ChoiceAuthority
|
||||
int found = 0;
|
||||
List<Choice> v = new ArrayList<Choice>();
|
||||
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)) {
|
||||
|
@@ -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<Item> {
|
||||
public Iterator<Item> findByMetadataField(Context context, MetadataField metadataField, String value,
|
||||
boolean inArchive) throws SQLException;
|
||||
|
||||
public Iterator<Item> findByMetadataQuery(Context context, List<List<MetadataField>> listFieldList,
|
||||
List<String> query_op, List<String> query_val, List<UUID> collectionUuids,
|
||||
String regexClause, int offset, int limit) throws SQLException;
|
||||
|
||||
public Iterator<Item> findByAuthorityValue(Context context, MetadataField metadataField, String authority,
|
||||
boolean inArchive) throws SQLException;
|
||||
|
||||
|
@@ -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<Item> implements ItemDA
|
||||
return new UUIDIterator<Item>(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<Item> findByMetadataQuery(Context context, List<List<MetadataField>> listFieldList,
|
||||
List<String> query_op, List<String> query_val, List<UUID> 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<Item>) criteria.list()).iterator();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Item> findByAuthorityValue(Context context, MetadataField metadataField, String authority,
|
||||
boolean inArchive) throws SQLException {
|
||||
|
@@ -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<Item> findByMetadataQuery(Context context, List<List<MetadataField>> listFieldList,
|
||||
List<String> query_op, List<String> query_val, List<UUID> 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.
|
||||
|
@@ -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<MetadataValue> 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<String> getReqList(String handle) throws DCInputsReaderException {
|
||||
List<String> reqList = reqMap.get(handle);
|
||||
protected List<String> getReqList(Collection collection) throws DCInputsReaderException {
|
||||
List<String> reqList = reqMap.get(collection.getHandle());
|
||||
if (reqList == null) {
|
||||
reqList = reqMap.get("default");
|
||||
}
|
||||
if (reqList == null) {
|
||||
reqList = new ArrayList<String>();
|
||||
List<DCInputSet> inputSet = reader.getInputsByCollectionHandle(handle);
|
||||
List<DCInputSet> inputSet = reader.getInputsByCollection(collection);
|
||||
for (DCInputSet inputs : inputSet) {
|
||||
for (DCInput[] row : inputs.getFields()) {
|
||||
for (DCInput input : row) {
|
||||
|
@@ -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<IndexDiscoveryScriptConfiguratio
|
||||
return;
|
||||
}
|
||||
|
||||
String type = null;
|
||||
if (commandLine.hasOption(TYPE_OPTION)) {
|
||||
List<String> 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<IndexDiscoveryScriptConfiguratio
|
||||
} else if (indexClientOptions == IndexClientOptions.BUILD ||
|
||||
indexClientOptions == IndexClientOptions.BUILDANDSPELLCHECK) {
|
||||
handler.logInfo("(Re)building index from scratch.");
|
||||
if (StringUtils.isNotBlank(type)) {
|
||||
handler.logWarning(String.format("Type option, %s, not applicable for entire index rebuild option, b" +
|
||||
", type will be ignored", TYPE_OPTION));
|
||||
}
|
||||
indexer.deleteIndex();
|
||||
indexer.createIndex(context);
|
||||
if (indexClientOptions == IndexClientOptions.BUILDANDSPELLCHECK) {
|
||||
@@ -133,14 +154,14 @@ public class IndexClient extends DSpaceRunnable<IndexDiscoveryScriptConfiguratio
|
||||
} else if (indexClientOptions == IndexClientOptions.UPDATE ||
|
||||
indexClientOptions == IndexClientOptions.UPDATEANDSPELLCHECK) {
|
||||
handler.logInfo("Updating Index");
|
||||
indexer.updateIndex(context, false);
|
||||
indexer.updateIndex(context, false, type);
|
||||
if (indexClientOptions == IndexClientOptions.UPDATEANDSPELLCHECK) {
|
||||
checkRebuildSpellCheck(commandLine, indexer);
|
||||
}
|
||||
} else if (indexClientOptions == IndexClientOptions.FORCEUPDATE ||
|
||||
indexClientOptions == IndexClientOptions.FORCEUPDATEANDSPELLCHECK) {
|
||||
handler.logInfo("Updating Index");
|
||||
indexer.updateIndex(context, true);
|
||||
indexer.updateIndex(context, true, type);
|
||||
if (indexClientOptions == IndexClientOptions.FORCEUPDATEANDSPELLCHECK) {
|
||||
checkRebuildSpellCheck(commandLine, indexer);
|
||||
}
|
||||
|
@@ -8,8 +8,13 @@
|
||||
|
||||
package org.dspace.discovery;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.cli.CommandLine;
|
||||
import org.apache.commons.cli.Options;
|
||||
import org.dspace.discovery.indexobject.factory.IndexObjectFactoryFactory;
|
||||
|
||||
/**
|
||||
* This Enum holds all the possible options and combinations for the Index discovery script
|
||||
@@ -29,6 +34,8 @@ public enum IndexClientOptions {
|
||||
FORCEUPDATEANDSPELLCHECK,
|
||||
HELP;
|
||||
|
||||
public static final String TYPE_OPTION = "t";
|
||||
|
||||
/**
|
||||
* This method resolves the CommandLine parameters to figure out which action the index-discovery script should
|
||||
* perform
|
||||
@@ -71,11 +78,15 @@ public enum IndexClientOptions {
|
||||
|
||||
protected static Options constructOptions() {
|
||||
Options options = new Options();
|
||||
List<String> 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,
|
||||
|
@@ -64,7 +64,14 @@ public abstract class IndexFactoryImpl<T extends IndexableObject, S> 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<T extends IndexableObject, S> 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<T extends IndexableObject, S> 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<T extends IndexableObject, S> 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<T extends IndexableObject, S> 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);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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<ExternalDataObject> 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<SuggestionProvider> 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;
|
||||
}
|
||||
|
||||
|
@@ -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<String>} representing the value
|
||||
* of the metadata {@code field} found inside the {@code valueList}.
|
||||
* @param field String of the MetadataField to search
|
||||
* @return {@code Optional<String>} non empty if found.
|
||||
*/
|
||||
public Optional<String> 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<String> getSingleValue(String schema, String element, String qualifier) {
|
||||
return getValue(schema, element, qualifier).stream()
|
||||
.map(MetadatumDTO::getValue)
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a value to the valueList
|
||||
*
|
||||
|
@@ -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<MetadatumDTO> contributeMetadata(String fullJson) {
|
||||
|
||||
Collection<MetadatumDTO> metadata = new ArrayList<>();
|
||||
Collection<String> metadataValue = new ArrayList<>();
|
||||
|
||||
JsonNode jsonNode = convertStringJsonToJsonNode(fullJson);
|
||||
JsonNode array = jsonNode.at(getQuery());
|
||||
if (!array.isArray()) {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
Iterator<JsonNode> 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;
|
||||
}
|
||||
|
||||
}
|
@@ -87,5 +87,4 @@ public class SimpleXpathDateFormatMetadataContributor extends SimpleXpathMetadat
|
||||
dcValue.setSchema(field.getSchema());
|
||||
return dcValue;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
@@ -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<Element>
|
||||
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<ImportRecord> 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<ImportRecord> getRecords(Query query) throws MetadataSourceException {
|
||||
return retry(new SearchByQueryCallable(query));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<ImportRecord> findMatchingRecords(Query query) throws MetadataSourceException {
|
||||
throw new MethodNotFoundException("This method is not implemented for OpenAIRE");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<ImportRecord> 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<ImportRecord> {
|
||||
|
||||
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<ImportRecord> results = new ArrayList<ImportRecord>();
|
||||
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<Element> 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<Integer> {
|
||||
|
||||
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<Element> 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<List<ImportRecord>> {
|
||||
|
||||
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<ImportRecord> call() throws Exception {
|
||||
WebTarget localTarget = webTarget.queryParam(queryParam, q);
|
||||
localTarget = localTarget.queryParam("page", page + 1);
|
||||
localTarget = localTarget.queryParam("size", count);
|
||||
List<ImportRecord> results = new ArrayList<ImportRecord>();
|
||||
Invocation.Builder invocationBuilder = localTarget.request();
|
||||
Response response = invocationBuilder.get();
|
||||
if (response.getStatus() == 200) {
|
||||
String responseString = response.readEntity(String.class);
|
||||
List<Element> 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<MetadatumDTO> metadata = (List<MetadatumDTO>)transformSourceRecords.getValueList();
|
||||
ArrayList<MetadatumDTO> 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<Element> 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<Element> xpath = XPathFactory.instance().compile("//results/result",
|
||||
Filters.element(), null, namespaces);
|
||||
|
||||
List<Element> recordsList = xpath.evaluate(root);
|
||||
return recordsList;
|
||||
} catch (JDOMException | IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
38
dspace-api/src/main/java/org/dspace/importer/external/ror/service/RorFieldMapping.java
vendored
Normal file
38
dspace-api/src/main/java/org/dspace/importer/external/ror/service/RorFieldMapping.java
vendored
Normal file
@@ -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);
|
||||
}
|
||||
|
||||
}
|
@@ -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<String>
|
||||
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<ImportRecord> 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<ImportRecord> getRecords(String query, int start, int count) throws MetadataSourceException {
|
||||
return retry(new SearchByQueryCallable(query));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<ImportRecord> getRecords(Query query) throws MetadataSourceException {
|
||||
return retry(new SearchByQueryCallable(query));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImportRecord getRecord(Query query) throws MetadataSourceException {
|
||||
List<ImportRecord> records = retry(new SearchByIdCallable(query));
|
||||
return CollectionUtils.isEmpty(records) ? null : records.get(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<ImportRecord> findMatchingRecords(Query query) throws MetadataSourceException {
|
||||
throw new MethodNotFoundException("This method is not implemented for ROR");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<ImportRecord> 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<List<ImportRecord>> {
|
||||
|
||||
private Query query;
|
||||
|
||||
private SearchByQueryCallable(String queryString) {
|
||||
query = new Query();
|
||||
query.addParameter("query", queryString);
|
||||
}
|
||||
|
||||
private SearchByQueryCallable(Query query) {
|
||||
this.query = query;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ImportRecord> 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<List<ImportRecord>> {
|
||||
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<ImportRecord> 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<Integer> {
|
||||
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<String, Map<String, String>> params = new HashMap<String, Map<String, String>>();
|
||||
|
||||
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<ImportRecord> searchById(String id) {
|
||||
|
||||
List<ImportRecord> importResults = new ArrayList<>();
|
||||
|
||||
id = StringUtils.removeStart(id, ROR_IDENTIFIER_PREFIX);
|
||||
|
||||
try {
|
||||
Map<String, Map<String, String>> params = new HashMap<String, Map<String, String>>();
|
||||
|
||||
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<ImportRecord> search(String query) {
|
||||
List<ImportRecord> importResults = new ArrayList<>();
|
||||
try {
|
||||
Map<String, Map<String, String>> params = new HashMap<String, Map<String, String>>();
|
||||
|
||||
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<JsonNode> 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;
|
||||
}
|
||||
|
||||
}
|
@@ -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);
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -123,6 +123,20 @@
|
||||
</property>
|
||||
</bean>
|
||||
|
||||
<bean id="openaireImportServiceByAuthor"
|
||||
class="org.dspace.importer.external.openaire.service.OpenAireImportMetadataSourceServiceImpl" scope="singleton">
|
||||
<property name="metadataFieldMapping" ref="openairePublicationMetadataFieldMapping"/>
|
||||
<property name="queryParam" value="author"/>
|
||||
</bean>
|
||||
<bean id="openaireImportServiceByTitle"
|
||||
class="org.dspace.importer.external.openaire.service.OpenAireImportMetadataSourceServiceImpl" scope="singleton">
|
||||
<property name="metadataFieldMapping" ref="openairePublicationMetadataFieldMapping"/>
|
||||
<property name="queryParam" value="title"/>
|
||||
</bean>
|
||||
<bean id="openairePublicationMetadataFieldMapping"
|
||||
class="org.dspace.importer.external.openaire.metadatamapping.OpenAIREPublicationFieldMapping">
|
||||
</bean>
|
||||
|
||||
<bean id="CrossRefImportService" class="org.dspace.importer.external.crossref.CrossRefImportMetadataSourceServiceImpl" scope="singleton">
|
||||
<property name="metadataFieldMapping" ref="CrossRefMetadataFieldMapping"/>
|
||||
<property name="url" value="${crossref.url}"/>
|
||||
@@ -150,6 +164,12 @@
|
||||
<property name="viewMode" value="${scopus.search-api.viewMode}"/>
|
||||
</bean>
|
||||
<bean id="scopusMetadataFieldMapping" class="org.dspace.importer.external.scopus.service.ScopusFieldMapping"/>
|
||||
|
||||
<bean id="rorImportService" class="org.dspace.importer.external.ror.service.RorImportMetadataSourceServiceImpl">
|
||||
<property name="metadataFieldMapping" ref="rorMetadataFieldMapping"/>
|
||||
<property name="url" value="${ror.orgunit-import.api-url}"/>
|
||||
</bean>
|
||||
<bean id="rorMetadataFieldMapping" class="org.dspace.importer.external.ror.service.RorFieldMapping"/>
|
||||
|
||||
<bean id="vufindImportService" class="org.dspace.importer.external.vufind.VuFindImportMetadataSourceServiceImpl" scope="singleton">
|
||||
<!-- Set to empty to use the default set of fields -->
|
||||
|
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
|
||||
The contents of this file are subject to the license and copyright
|
||||
detailed in the LICENSE and NOTICE files at the root of the source
|
||||
tree and available online at
|
||||
|
||||
http://www.dspace.org/license/
|
||||
|
||||
-->
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:context="http://www.springframework.org/schema/context"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
||||
http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/context
|
||||
http://www.springframework.org/schema/context/spring-context.xsd"
|
||||
default-autowire-candidates="*Service,*DAO,javax.sql.DataSource">
|
||||
|
||||
<context:annotation-config/> <!-- allows us to use spring annotations in beans -->
|
||||
|
||||
<bean class="org.dspace.app.suggestion.SuggestionServiceImpl" id="org.dspace.app.suggestion.SuggestionService" />
|
||||
</beans>
|
@@ -24,6 +24,9 @@
|
||||
<name-map collection-handle="123456789/typebind-test" submission-name="typebindtest"/>
|
||||
<name-map collection-handle="123456789/accessCondition-not-discoverable" submission-name="accessConditionNotDiscoverable"/>
|
||||
<name-map collection-handle="123456789/test-hidden" submission-name="test-hidden"/>
|
||||
<name-map community-handle="123456789/topcommunity-test" submission-name="topcommunitytest"/>
|
||||
<name-map community-handle="123456789/subcommunity-test" submission-name="subcommunitytest"/>
|
||||
<name-map collection-handle="123456789/collection-test" submission-name="collectiontest"/>
|
||||
</submission-map>
|
||||
|
||||
|
||||
@@ -257,6 +260,18 @@
|
||||
<step id="test-always-hidden"/>
|
||||
</submission-process>
|
||||
|
||||
<submission-process name="topcommunitytest">
|
||||
<step id="collection"/>
|
||||
</submission-process>
|
||||
|
||||
<submission-process name="subcommunitytest">
|
||||
<step id="collection"/>
|
||||
</submission-process>
|
||||
|
||||
<submission-process name="collectiontest">
|
||||
<step id="collection"/>
|
||||
</submission-process>
|
||||
|
||||
</submission-definitions>
|
||||
|
||||
</item-submission>
|
||||
|
@@ -90,6 +90,8 @@
|
||||
</list>
|
||||
</property>
|
||||
</bean>
|
||||
|
||||
<bean class="org.dspace.app.suggestion.MockSuggestionExternalDataSource" />
|
||||
|
||||
<bean id="dataciteLiveImportDataProvider" class="org.dspace.external.provider.impl.LiveImportDataProvider">
|
||||
<property name="metadataSource" ref="DataCiteImportService"/>
|
||||
|
@@ -47,12 +47,14 @@
|
||||
<bean id="org.dspace.statistics.SolrStatisticsCore"
|
||||
class="org.dspace.statistics.MockSolrStatisticsCore"
|
||||
autowire-candidate="true"/>
|
||||
|
||||
|
||||
<!-- qa events -->
|
||||
<bean class="org.dspace.qaevent.MockQAEventService"
|
||||
id="org.dspace.qaevent.service.QAEventService" />
|
||||
|
||||
<bean class="org.dspace.statistics.GeoIpService" autowire-candidate="true"/>
|
||||
|
||||
|
||||
<!-- suggestion service for solr providers -->
|
||||
<bean id="org.dspace.app.suggestion.SolrSuggestionStorageService" class="org.dspace.app.suggestion.MockSolrSuggestionStorageService" />
|
||||
|
||||
</beans>
|
||||
|
@@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:context="http://www.springframework.org/schema/context"
|
||||
xmlns:util="http://www.springframework.org/schema/util"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
||||
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
|
||||
http://www.springframework.org/schema/context
|
||||
http://www.springframework.org/schema/context/spring-context-2.5.xsd
|
||||
http://www.springframework.org/schema/util
|
||||
http://www.springframework.org/schema/util/spring-util-3.0.xsd">
|
||||
|
||||
<context:annotation-config /> <!-- allows us to use spring annotations in beans -->
|
||||
|
||||
<!-- This is defined in the solr-services.xml
|
||||
<bean id="org.dspace.app.nbevent.service.NBEventService"
|
||||
class="org.dspace.app.nbevent.service.impl.NBEventServiceImpl" /> -->
|
||||
|
||||
<util:map id="suggestionProviders" map-class="java.util.HashMap"
|
||||
key-type="java.lang.String" value-type="org.dspace.app.suggestion.SuggestionProvider">
|
||||
<entry key="scopus">
|
||||
<bean class="org.dspace.app.suggestion.MockSolrSuggestionProvider">
|
||||
<property name="sourceName" value="scopus"></property>
|
||||
</bean>
|
||||
</entry>
|
||||
<entry key="reciter">
|
||||
<bean class="org.dspace.app.suggestion.MockSolrSuggestionProvider">
|
||||
<property name="sourceName" value="reciter"></property>
|
||||
</bean>
|
||||
</entry>
|
||||
</util:map>
|
||||
|
||||
</beans>
|
@@ -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());
|
||||
}
|
||||
}
|
@@ -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();
|
||||
}
|
||||
}
|
@@ -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<ExternalDataObject> 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<ExternalDataObject> 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;
|
||||
}
|
||||
|
||||
}
|
@@ -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<String,SuggestionProvider> 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<MetadataValue> values = List.of(getMetadata(item, "person.identifier.scopus-author-id", 0));
|
||||
|
||||
Object firstOrcidObject = profileSectionFactoryService.createOrcidObject(context, values, EXTERNAL_IDS);
|
||||
Optional<ExternalDataObject> 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<String> 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<MetadataValue> 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<String> putCodes) throws Exception {
|
||||
return unmarshall("workBulk-" + String.join("-", putCodes) + ".xml", WorkBulk.class);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> T unmarshall(String fileName, Class<T> 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()));
|
||||
}
|
||||
}
|
@@ -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());
|
||||
|
||||
}
|
||||
}
|
@@ -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<String> 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
|
||||
|
@@ -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<T, S> {
|
||||
static SubscribeService subscribeService;
|
||||
static SupervisionOrderService supervisionOrderService;
|
||||
static QAEventService qaEventService;
|
||||
static SolrSuggestionStorageService solrSuggestionService;
|
||||
|
||||
protected Context context;
|
||||
|
||||
@@ -185,6 +187,7 @@ public abstract class AbstractBuilder<T, S> {
|
||||
subscribeService = ContentServiceFactory.getInstance().getSubscribeService();
|
||||
supervisionOrderService = SupervisionOrderServiceFactory.getInstance().getSupervisionOrderService();
|
||||
qaEventService = new DSpace().getSingletonService(QAEventService.class);
|
||||
solrSuggestionService = new DSpace().getSingletonService(SolrSuggestionStorageService.class);
|
||||
}
|
||||
|
||||
|
||||
|
@@ -186,6 +186,10 @@ public class ItemBuilder extends AbstractDSpaceObjectBuilder<Item> {
|
||||
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);
|
||||
|
@@ -68,8 +68,8 @@ public class ProcessBuilder extends AbstractBuilder<Process, ProcessService> {
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
@@ -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<SuggestionTarget, SolrSuggestionStorageService> {
|
||||
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<Suggestion> 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<Suggestion> generateAllSuggestion() {
|
||||
List<Suggestion> allSuggestions = new ArrayList<Suggestion>();
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
@@ -77,6 +77,7 @@
|
||||
</common:source>
|
||||
<work:title>
|
||||
<common:title>Another cautionary tale.</common:title>
|
||||
<common:title>Second title</common:title>
|
||||
</work:title>
|
||||
<work:type>journal-article</work:type>
|
||||
<common:publication-date>
|
||||
|
Reference in New Issue
Block a user