Merge remote-tracking branch 'origin/main' into w2p-97298_issue-3281_self-register-issue-main

This commit is contained in:
Marie Verdonck
2022-12-23 14:26:41 +01:00
20 changed files with 495 additions and 52 deletions

View File

@@ -598,18 +598,19 @@ public class MetadataImport extends DSpaceRunnable<MetadataImportScriptConfigura
changes.add(whatHasChanged); changes.add(whatHasChanged);
} }
if (change) { if (change && (rowCount % configurationService.getIntProperty("bulkedit.change.commit.count", 100) == 0)) {
//only clear cache if changes have been made. c.commit();
c.uncacheEntity(wsItem); handler.logInfo(LogHelper.getHeader(c, "metadata_import_commit", "lineNumber=" + rowCount));
c.uncacheEntity(wfItem);
c.uncacheEntity(item);
} }
populateRefAndRowMap(line, item == null ? null : item.getID()); populateRefAndRowMap(line, item == null ? null : item.getID());
// keep track of current rows processed // keep track of current rows processed
rowCount++; rowCount++;
} }
if (change) {
c.commit();
}
c.setMode(originalMode); c.setMode(Context.Mode.READ_ONLY);
// Return the changes // Return the changes

View File

@@ -67,6 +67,7 @@ public class ItemImport extends DSpaceRunnable<ItemImportScriptConfiguration> {
protected String eperson = null; protected String eperson = null;
protected String[] collections = null; protected String[] collections = null;
protected boolean isTest = false; protected boolean isTest = false;
protected boolean isExcludeContent = false;
protected boolean isResume = false; protected boolean isResume = false;
protected boolean useWorkflow = false; protected boolean useWorkflow = false;
protected boolean useWorkflowSendEmail = false; protected boolean useWorkflowSendEmail = false;
@@ -119,6 +120,8 @@ public class ItemImport extends DSpaceRunnable<ItemImportScriptConfiguration> {
handler.logInfo("**Test Run** - not actually importing items."); handler.logInfo("**Test Run** - not actually importing items.");
} }
isExcludeContent = commandLine.hasOption('x');
if (commandLine.hasOption('p')) { if (commandLine.hasOption('p')) {
template = true; template = true;
} }
@@ -204,6 +207,7 @@ public class ItemImport extends DSpaceRunnable<ItemImportScriptConfiguration> {
.getItemImportService(); .getItemImportService();
try { try {
itemImportService.setTest(isTest); itemImportService.setTest(isTest);
itemImportService.setExcludeContent(isExcludeContent);
itemImportService.setResume(isResume); itemImportService.setResume(isResume);
itemImportService.setUseWorkflow(useWorkflow); itemImportService.setUseWorkflow(useWorkflow);
itemImportService.setUseWorkflowSendEmail(useWorkflowSendEmail); itemImportService.setUseWorkflowSendEmail(useWorkflowSendEmail);

View File

@@ -55,6 +55,9 @@ public class ItemImportCLIScriptConfiguration extends ItemImportScriptConfigurat
options.addOption(Option.builder("v").longOpt("validate") options.addOption(Option.builder("v").longOpt("validate")
.desc("test run - do not actually import items") .desc("test run - do not actually import items")
.hasArg(false).required(false).build()); .hasArg(false).required(false).build());
options.addOption(Option.builder("x").longOpt("exclude-bitstreams")
.desc("do not load or expect content bitstreams")
.hasArg(false).required(false).build());
options.addOption(Option.builder("p").longOpt("template") options.addOption(Option.builder("p").longOpt("template")
.desc("apply template") .desc("apply template")
.hasArg(false).required(false).build()); .hasArg(false).required(false).build());

View File

@@ -81,6 +81,9 @@ public class ItemImportScriptConfiguration<T extends ItemImport> extends ScriptC
options.addOption(Option.builder("v").longOpt("validate") options.addOption(Option.builder("v").longOpt("validate")
.desc("test run - do not actually import items") .desc("test run - do not actually import items")
.hasArg(false).required(false).build()); .hasArg(false).required(false).build());
options.addOption(Option.builder("x").longOpt("exclude-bitstreams")
.desc("do not load or expect content bitstreams")
.hasArg(false).required(false).build());
options.addOption(Option.builder("p").longOpt("template") options.addOption(Option.builder("p").longOpt("template")
.desc("apply template") .desc("apply template")
.hasArg(false).required(false).build()); .hasArg(false).required(false).build());

View File

@@ -62,6 +62,7 @@ import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.dspace.app.itemimport.service.ItemImportService; import org.dspace.app.itemimport.service.ItemImportService;
import org.dspace.app.util.LocalSchemaFilenameFilter; import org.dspace.app.util.LocalSchemaFilenameFilter;
@@ -135,7 +136,7 @@ import org.xml.sax.SAXException;
* allow the registration of files (bitstreams) into DSpace. * allow the registration of files (bitstreams) into DSpace.
*/ */
public class ItemImportServiceImpl implements ItemImportService, InitializingBean { public class ItemImportServiceImpl implements ItemImportService, InitializingBean {
private final Logger log = org.apache.logging.log4j.LogManager.getLogger(ItemImportServiceImpl.class); private final Logger log = LogManager.getLogger();
private DSpaceRunnableHandler handler; private DSpaceRunnableHandler handler;
@@ -181,6 +182,7 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea
protected String tempWorkDir; protected String tempWorkDir;
protected boolean isTest = false; protected boolean isTest = false;
protected boolean isExcludeContent = false;
protected boolean isResume = false; protected boolean isResume = false;
protected boolean useWorkflow = false; protected boolean useWorkflow = false;
protected boolean useWorkflowSendEmail = false; protected boolean useWorkflowSendEmail = false;
@@ -1403,6 +1405,10 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea
protected void processContentFileEntry(Context c, Item i, String path, protected void processContentFileEntry(Context c, Item i, String path,
String fileName, String bundleName, boolean primary) throws SQLException, String fileName, String bundleName, boolean primary) throws SQLException,
IOException, AuthorizeException { IOException, AuthorizeException {
if (isExcludeContent) {
return;
}
String fullpath = path + File.separatorChar + fileName; String fullpath = path + File.separatorChar + fileName;
// get an input stream // get an input stream
@@ -2342,6 +2348,11 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea
this.isTest = isTest; this.isTest = isTest;
} }
@Override
public void setExcludeContent(boolean isExcludeContent) {
this.isExcludeContent = isExcludeContent;
}
@Override @Override
public void setResume(boolean isResume) { public void setResume(boolean isResume) {
this.isResume = isResume; this.isResume = isResume;

View File

@@ -211,6 +211,13 @@ public interface ItemImportService {
*/ */
public void setTest(boolean isTest); public void setTest(boolean isTest);
/**
* Set exclude-content flag.
*
* @param isExcludeContent true or false
*/
public void setExcludeContent(boolean isExcludeContent);
/** /**
* Set resume flag * Set resume flag
* *

View File

@@ -10,6 +10,7 @@ package org.dspace.app.util;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException; import java.util.regex.PatternSyntaxException;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@@ -131,10 +132,15 @@ public class DCInput {
private boolean closedVocabulary = false; private boolean closedVocabulary = false;
/** /**
* the regex to comply with, null if nothing * the regex in ECMAScript standard format, usable also by rests.
*/ */
private String regex = null; private String regex = null;
/**
* the computed pattern, null if nothing
*/
private Pattern pattern = null;
/** /**
* allowed document types * allowed document types
*/ */
@@ -178,7 +184,7 @@ public class DCInput {
//check if the input have a language tag //check if the input have a language tag
language = Boolean.valueOf(fieldMap.get("language")); language = Boolean.valueOf(fieldMap.get("language"));
valueLanguageList = new ArrayList(); valueLanguageList = new ArrayList<>();
if (language) { if (language) {
String languageNameTmp = fieldMap.get("value-pairs-name"); String languageNameTmp = fieldMap.get("value-pairs-name");
if (StringUtils.isBlank(languageNameTmp)) { if (StringUtils.isBlank(languageNameTmp)) {
@@ -191,7 +197,7 @@ public class DCInput {
repeatable = "true".equalsIgnoreCase(repStr) repeatable = "true".equalsIgnoreCase(repStr)
|| "yes".equalsIgnoreCase(repStr); || "yes".equalsIgnoreCase(repStr);
String nameVariantsString = fieldMap.get("name-variants"); String nameVariantsString = fieldMap.get("name-variants");
nameVariants = (StringUtils.isNotBlank(nameVariantsString)) ? nameVariants = StringUtils.isNotBlank(nameVariantsString) ?
nameVariantsString.equalsIgnoreCase("true") : false; nameVariantsString.equalsIgnoreCase("true") : false;
label = fieldMap.get("label"); label = fieldMap.get("label");
inputType = fieldMap.get("input-type"); inputType = fieldMap.get("input-type");
@@ -203,17 +209,17 @@ public class DCInput {
} }
hint = fieldMap.get("hint"); hint = fieldMap.get("hint");
warning = fieldMap.get("required"); warning = fieldMap.get("required");
required = (warning != null && warning.length() > 0); required = warning != null && warning.length() > 0;
visibility = fieldMap.get("visibility"); visibility = fieldMap.get("visibility");
readOnly = fieldMap.get("readonly"); readOnly = fieldMap.get("readonly");
vocabulary = fieldMap.get("vocabulary"); vocabulary = fieldMap.get("vocabulary");
regex = fieldMap.get("regex"); this.initRegex(fieldMap.get("regex"));
String closedVocabularyStr = fieldMap.get("closedVocabulary"); String closedVocabularyStr = fieldMap.get("closedVocabulary");
closedVocabulary = "true".equalsIgnoreCase(closedVocabularyStr) closedVocabulary = "true".equalsIgnoreCase(closedVocabularyStr)
|| "yes".equalsIgnoreCase(closedVocabularyStr); || "yes".equalsIgnoreCase(closedVocabularyStr);
// parsing of the <type-bind> element (using the colon as split separator) // parsing of the <type-bind> element (using the colon as split separator)
typeBind = new ArrayList<>(); typeBind = new ArrayList<String>();
String typeBindDef = fieldMap.get("type-bind"); String typeBindDef = fieldMap.get("type-bind");
if (typeBindDef != null && typeBindDef.trim().length() > 0) { if (typeBindDef != null && typeBindDef.trim().length() > 0) {
String[] types = typeBindDef.split(","); String[] types = typeBindDef.split(",");
@@ -238,6 +244,22 @@ public class DCInput {
} }
protected void initRegex(String regex) {
this.regex = null;
this.pattern = null;
if (regex != null) {
try {
Optional.ofNullable(RegexPatternUtils.computePattern(regex))
.ifPresent(pattern -> {
this.pattern = pattern;
this.regex = regex;
});
} catch (PatternSyntaxException e) {
log.warn("The regex field of input {} with value {} is invalid!", this.label, regex);
}
}
}
/** /**
* Is this DCInput for display in the given scope? The scope should be * Is this DCInput for display in the given scope? The scope should be
* either "workflow" or "submit", as per the input forms definition. If the * either "workflow" or "submit", as per the input forms definition. If the
@@ -248,7 +270,7 @@ public class DCInput {
* @return whether the input should be displayed or not * @return whether the input should be displayed or not
*/ */
public boolean isVisible(String scope) { public boolean isVisible(String scope) {
return (visibility == null || visibility.equals(scope)); return visibility == null || visibility.equals(scope);
} }
/** /**
@@ -512,8 +534,12 @@ public class DCInput {
return visibility; return visibility;
} }
public Pattern getPattern() {
return this.pattern;
}
public String getRegex() { public String getRegex() {
return regex; return this.regex;
} }
public String getFieldName() { public String getFieldName() {
@@ -546,8 +572,7 @@ public class DCInput {
public boolean validate(String value) { public boolean validate(String value) {
if (StringUtils.isNotBlank(value)) { if (StringUtils.isNotBlank(value)) {
try { try {
if (StringUtils.isNotBlank(regex)) { if (this.pattern != null) {
Pattern pattern = Pattern.compile(regex);
if (!pattern.matcher(value).matches()) { if (!pattern.matcher(value).matches()) {
return false; return false;
} }
@@ -557,7 +582,6 @@ public class DCInput {
} }
} }
return true; return true;
} }

View File

@@ -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 java.util.regex.Pattern.CASE_INSENSITIVE;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import org.apache.commons.lang3.StringUtils;
/**
* Utility class useful for check regex and patterns.
*
* @author Vincenzo Mecca (vins01-4science - vincenzo.mecca at 4science.com)
*
*/
public class RegexPatternUtils {
// checks input having the format /{pattern}/{flags}
// allowed flags are: g,i,m,s,u,y
public static final String REGEX_INPUT_VALIDATOR = "(/?)(.+)\\1([gimsuy]*)";
// flags usable inside regex definition using format (?i|m|s|u|y)
public static final String REGEX_FLAGS = "(?%s)";
public static final Pattern PATTERN_REGEX_INPUT_VALIDATOR =
Pattern.compile(REGEX_INPUT_VALIDATOR, CASE_INSENSITIVE);
/**
* Computes a pattern starting from a regex definition with flags that
* uses the standard format: <code>/{regex}/{flags}</code> (ECMAScript format).
* This method can transform an ECMAScript regex into a java {@code Pattern} object
* wich can be used to validate strings.
* <br/>
* If regex is null, empty or blank a null {@code Pattern} will be retrieved
* If it's a valid regex, then a non-null {@code Pattern} will be retrieved,
* an exception will be thrown otherwise.
*
* @param regex with format <code>/{regex}/{flags}</code>
* @return {@code Pattern} regex pattern instance
* @throws PatternSyntaxException
*/
public static final Pattern computePattern(String regex) throws PatternSyntaxException {
if (StringUtils.isBlank(regex)) {
return null;
}
Matcher inputMatcher = PATTERN_REGEX_INPUT_VALIDATOR.matcher(regex);
String regexPattern = regex;
String regexFlags = "";
if (inputMatcher.matches()) {
regexPattern =
Optional.of(inputMatcher.group(2))
.filter(StringUtils::isNotBlank)
.orElse(regex);
regexFlags =
Optional.ofNullable(inputMatcher.group(3))
.filter(StringUtils::isNotBlank)
.map(flags -> String.format(REGEX_FLAGS, flags))
.orElse("")
.replaceAll("g", "");
}
return Pattern.compile(regexFlags + regexPattern);
}
private RegexPatternUtils() {}
}

View File

@@ -31,10 +31,12 @@ import org.dspace.content.DSpaceObject;
import org.dspace.content.Item; import org.dspace.content.Item;
import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.factory.ContentServiceFactory;
import org.dspace.content.service.BitstreamService; import org.dspace.content.service.BitstreamService;
import org.dspace.content.service.CollectionService;
import org.dspace.content.service.WorkspaceItemService; import org.dspace.content.service.WorkspaceItemService;
import org.dspace.core.Constants; import org.dspace.core.Constants;
import org.dspace.core.Context; import org.dspace.core.Context;
import org.dspace.discovery.DiscoverQuery; import org.dspace.discovery.DiscoverQuery;
import org.dspace.discovery.DiscoverQuery.SORT_ORDER;
import org.dspace.discovery.DiscoverResult; import org.dspace.discovery.DiscoverResult;
import org.dspace.discovery.IndexableObject; import org.dspace.discovery.IndexableObject;
import org.dspace.discovery.SearchService; import org.dspace.discovery.SearchService;
@@ -830,7 +832,7 @@ public class AuthorizeServiceImpl implements AuthorizeService {
query = formatCustomQuery(query); query = formatCustomQuery(query);
DiscoverResult discoverResult = getDiscoverResult(context, query + "search.resourcetype:" + DiscoverResult discoverResult = getDiscoverResult(context, query + "search.resourcetype:" +
IndexableCommunity.TYPE, IndexableCommunity.TYPE,
offset, limit); offset, limit, null, null);
for (IndexableObject solrCollections : discoverResult.getIndexableObjects()) { for (IndexableObject solrCollections : discoverResult.getIndexableObjects()) {
Community community = ((IndexableCommunity) solrCollections).getIndexedObject(); Community community = ((IndexableCommunity) solrCollections).getIndexedObject();
communities.add(community); communities.add(community);
@@ -852,7 +854,7 @@ public class AuthorizeServiceImpl implements AuthorizeService {
query = formatCustomQuery(query); query = formatCustomQuery(query);
DiscoverResult discoverResult = getDiscoverResult(context, query + "search.resourcetype:" + DiscoverResult discoverResult = getDiscoverResult(context, query + "search.resourcetype:" +
IndexableCommunity.TYPE, IndexableCommunity.TYPE,
null, null); null, null, null, null);
return discoverResult.getTotalSearchResults(); return discoverResult.getTotalSearchResults();
} }
@@ -877,7 +879,7 @@ public class AuthorizeServiceImpl implements AuthorizeService {
query = formatCustomQuery(query); query = formatCustomQuery(query);
DiscoverResult discoverResult = getDiscoverResult(context, query + "search.resourcetype:" + DiscoverResult discoverResult = getDiscoverResult(context, query + "search.resourcetype:" +
IndexableCollection.TYPE, IndexableCollection.TYPE,
offset, limit); offset, limit, CollectionService.SOLR_SORT_FIELD, SORT_ORDER.asc);
for (IndexableObject solrCollections : discoverResult.getIndexableObjects()) { for (IndexableObject solrCollections : discoverResult.getIndexableObjects()) {
Collection collection = ((IndexableCollection) solrCollections).getIndexedObject(); Collection collection = ((IndexableCollection) solrCollections).getIndexedObject();
collections.add(collection); collections.add(collection);
@@ -899,7 +901,7 @@ public class AuthorizeServiceImpl implements AuthorizeService {
query = formatCustomQuery(query); query = formatCustomQuery(query);
DiscoverResult discoverResult = getDiscoverResult(context, query + "search.resourcetype:" + DiscoverResult discoverResult = getDiscoverResult(context, query + "search.resourcetype:" +
IndexableCollection.TYPE, IndexableCollection.TYPE,
null, null); null, null, null, null);
return discoverResult.getTotalSearchResults(); return discoverResult.getTotalSearchResults();
} }
@@ -919,7 +921,7 @@ public class AuthorizeServiceImpl implements AuthorizeService {
} }
try { try {
DiscoverResult discoverResult = getDiscoverResult(context, query, null, null); DiscoverResult discoverResult = getDiscoverResult(context, query, null, null, null, null);
if (discoverResult.getTotalSearchResults() > 0) { if (discoverResult.getTotalSearchResults() > 0) {
return true; return true;
} }
@@ -931,7 +933,8 @@ public class AuthorizeServiceImpl implements AuthorizeService {
return false; return false;
} }
private DiscoverResult getDiscoverResult(Context context, String query, Integer offset, Integer limit) private DiscoverResult getDiscoverResult(Context context, String query, Integer offset, Integer limit,
String sortField, SORT_ORDER sortOrder)
throws SearchServiceException, SQLException { throws SearchServiceException, SQLException {
String groupQuery = getGroupToQuery(groupService.allMemberGroups(context, context.getCurrentUser())); String groupQuery = getGroupToQuery(groupService.allMemberGroups(context, context.getCurrentUser()));
@@ -947,7 +950,9 @@ public class AuthorizeServiceImpl implements AuthorizeService {
if (limit != null) { if (limit != null) {
discoverQuery.setMaxResults(limit); discoverQuery.setMaxResults(limit);
} }
if (sortField != null && sortOrder != null) {
discoverQuery.setSortField(sortField, sortOrder);
}
return searchService.search(context, discoverQuery); return searchService.search(context, discoverQuery);
} }

View File

@@ -17,6 +17,7 @@ import java.util.UUID;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.apache.solr.client.solrj.util.ClientUtils;
import org.dspace.authorize.factory.AuthorizeServiceFactory; import org.dspace.authorize.factory.AuthorizeServiceFactory;
import org.dspace.authorize.service.AuthorizeService; import org.dspace.authorize.service.AuthorizeService;
import org.dspace.content.Item; import org.dspace.content.Item;
@@ -206,7 +207,8 @@ public class SolrBrowseDAO implements BrowseDAO {
query.addFilterQueries("{!field f=" + facetField + "_partial}" + value); query.addFilterQueries("{!field f=" + facetField + "_partial}" + value);
} }
if (StringUtils.isNotBlank(startsWith) && orderField != null) { if (StringUtils.isNotBlank(startsWith) && orderField != null) {
query.addFilterQueries("bi_" + orderField + "_sort:" + startsWith + "*"); query.addFilterQueries(
"bi_" + orderField + "_sort:" + ClientUtils.escapeQueryChars(startsWith) + "*");
} }
// filter on item to be sure to don't include any other object // filter on item to be sure to don't include any other object
// indexed in the Discovery Search core // indexed in the Discovery Search core

View File

@@ -43,6 +43,7 @@ import org.dspace.core.I18nUtil;
import org.dspace.core.LogHelper; import org.dspace.core.LogHelper;
import org.dspace.core.service.LicenseService; import org.dspace.core.service.LicenseService;
import org.dspace.discovery.DiscoverQuery; import org.dspace.discovery.DiscoverQuery;
import org.dspace.discovery.DiscoverQuery.SORT_ORDER;
import org.dspace.discovery.DiscoverResult; import org.dspace.discovery.DiscoverResult;
import org.dspace.discovery.IndexableObject; import org.dspace.discovery.IndexableObject;
import org.dspace.discovery.SearchService; import org.dspace.discovery.SearchService;
@@ -946,6 +947,7 @@ public class CollectionServiceImpl extends DSpaceObjectServiceImpl<Collection> i
discoverQuery.setDSpaceObjectFilter(IndexableCollection.TYPE); discoverQuery.setDSpaceObjectFilter(IndexableCollection.TYPE);
discoverQuery.setStart(offset); discoverQuery.setStart(offset);
discoverQuery.setMaxResults(limit); discoverQuery.setMaxResults(limit);
discoverQuery.setSortField(SOLR_SORT_FIELD, SORT_ORDER.asc);
DiscoverResult resp = retrieveCollectionsWithSubmit(context, discoverQuery, null, community, q); DiscoverResult resp = retrieveCollectionsWithSubmit(context, discoverQuery, null, community, q);
for (IndexableObject solrCollections : resp.getIndexableObjects()) { for (IndexableObject solrCollections : resp.getIndexableObjects()) {
Collection c = ((IndexableCollection) solrCollections).getIndexedObject(); Collection c = ((IndexableCollection) solrCollections).getIndexedObject();
@@ -1025,6 +1027,7 @@ public class CollectionServiceImpl extends DSpaceObjectServiceImpl<Collection> i
discoverQuery.setDSpaceObjectFilter(IndexableCollection.TYPE); discoverQuery.setDSpaceObjectFilter(IndexableCollection.TYPE);
discoverQuery.setStart(offset); discoverQuery.setStart(offset);
discoverQuery.setMaxResults(limit); discoverQuery.setMaxResults(limit);
discoverQuery.setSortField(SOLR_SORT_FIELD, SORT_ORDER.asc);
DiscoverResult resp = retrieveCollectionsWithSubmit(context, discoverQuery, DiscoverResult resp = retrieveCollectionsWithSubmit(context, discoverQuery,
entityType, community, q); entityType, community, q);
for (IndexableObject solrCollections : resp.getIndexableObjects()) { for (IndexableObject solrCollections : resp.getIndexableObjects()) {

View File

@@ -33,6 +33,11 @@ import org.dspace.eperson.Group;
public interface CollectionService public interface CollectionService
extends DSpaceObjectService<Collection>, DSpaceObjectLegacySupportService<Collection> { extends DSpaceObjectService<Collection>, DSpaceObjectLegacySupportService<Collection> {
/*
* Field used to sort community and collection lists at solr
*/
public static final String SOLR_SORT_FIELD = "dc.title_sort";
/** /**
* Create a new collection with a new ID. * Create a new collection with a new ID.
* Once created the collection is added to the given community * Once created the collection is added to the given community
@@ -46,7 +51,6 @@ public interface CollectionService
public Collection create(Context context, Community community) throws SQLException, public Collection create(Context context, Community community) throws SQLException,
AuthorizeException; AuthorizeException;
/** /**
* Create a new collection with the supplied handle and with a new ID. * Create a new collection with the supplied handle and with a new ID.
* Once created the collection is added to the given community * Once created the collection is added to the given community

View File

@@ -9,6 +9,7 @@ package org.dspace.handle;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@@ -211,17 +212,17 @@ public class HandleServiceImpl implements HandleService {
@Override @Override
public void unbindHandle(Context context, DSpaceObject dso) public void unbindHandle(Context context, DSpaceObject dso)
throws SQLException { throws SQLException {
List<Handle> handles = getInternalHandles(context, dso); Iterator<Handle> handles = dso.getHandles().iterator();
if (CollectionUtils.isNotEmpty(handles)) { if (handles.hasNext()) {
for (Handle handle : handles) { while (handles.hasNext()) {
final Handle handle = handles.next();
handles.remove();
//Only set the "resouce_id" column to null when unbinding a handle. //Only set the "resouce_id" column to null when unbinding a handle.
// We want to keep around the "resource_type_id" value, so that we // We want to keep around the "resource_type_id" value, so that we
// can verify during a restore whether the same *type* of resource // can verify during a restore whether the same *type* of resource
// is reusing this handle! // is reusing this handle!
handle.setDSpaceObject(null); handle.setDSpaceObject(null);
//Also remove the handle from the DSO list to keep a consistent model
dso.getHandles().remove(handle);
handleDAO.save(context, handle); handleDAO.save(context, handle);
@@ -256,7 +257,7 @@ public class HandleServiceImpl implements HandleService {
@Override @Override
public String findHandle(Context context, DSpaceObject dso) public String findHandle(Context context, DSpaceObject dso)
throws SQLException { throws SQLException {
List<Handle> handles = getInternalHandles(context, dso); List<Handle> handles = dso.getHandles();
if (CollectionUtils.isEmpty(handles)) { if (CollectionUtils.isEmpty(handles)) {
return null; return null;
} else { } else {
@@ -328,20 +329,6 @@ public class HandleServiceImpl implements HandleService {
//////////////////////////////////////// ////////////////////////////////////////
// Internal methods // Internal methods
//////////////////////////////////////// ////////////////////////////////////////
/**
* Return the handle for an Object, or null if the Object has no handle.
*
* @param context DSpace context
* @param dso DSpaceObject for which we require our handles
* @return The handle for object, or null if the object has no handle.
* @throws SQLException If a database error occurs
*/
protected List<Handle> getInternalHandles(Context context, DSpaceObject dso)
throws SQLException {
return handleDAO.getHandlesByDSpaceObject(context, dso);
}
/** /**
* Find the database row corresponding to handle. * Find the database row corresponding to handle.
* *

View File

@@ -0,0 +1,9 @@
--
-- The contents of this file are subject to the license and copyright
-- detailed in the LICENSE and NOTICE files at the root of the source
-- tree and available online at
--
-- http://www.dspace.org/license/
--
CREATE INDEX resourcepolicy_action_idx ON resourcepolicy(action_id);

View File

@@ -0,0 +1,9 @@
--
-- The contents of this file are subject to the license and copyright
-- detailed in the LICENSE and NOTICE files at the root of the source
-- tree and available online at
--
-- http://www.dspace.org/license/
--
CREATE INDEX resourcepolicy_action_idx ON resourcepolicy(action_id);

View File

@@ -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.util;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import org.dspace.AbstractUnitTest;
import org.junit.Test;
/**
* Tests for RegexPatternUtils
*
* @author Vincenzo Mecca (vins01-4science - vincenzo.mecca at 4science.com)
*
*/
public class RegexPatternUtilsTest extends AbstractUnitTest {
@Test
public void testValidRegexWithFlag() {
final String insensitiveWord = "/[a-z]+/i";
Pattern computePattern = Pattern.compile(insensitiveWord);
assertNotNull(computePattern);
Matcher matcher = computePattern.matcher("Hello");
assertFalse(matcher.matches());
matcher = computePattern.matcher("DSpace");
assertFalse(matcher.matches());
matcher = computePattern.matcher("Community");
assertFalse(matcher.matches());
matcher = computePattern.matcher("/wrongpattern/i");
assertTrue(matcher.matches());
matcher = computePattern.matcher("001");
assertFalse(matcher.matches());
matcher = computePattern.matcher("?/'`}{][<>.,");
assertFalse(matcher.matches());
computePattern = RegexPatternUtils.computePattern(insensitiveWord);
assertNotNull(computePattern);
matcher = computePattern.matcher("Hello");
assertTrue(matcher.matches());
matcher = computePattern.matcher("DSpace");
assertTrue(matcher.matches());
matcher = computePattern.matcher("Community");
assertTrue(matcher.matches());
matcher = computePattern.matcher("/wrong-pattern/i");
assertFalse(matcher.matches());
matcher = computePattern.matcher("001");
assertFalse(matcher.matches());
matcher = computePattern.matcher("?/'`}{][<>.,");
assertFalse(matcher.matches());
}
@Test
public void testRegexWithoutFlag() {
final String sensitiveWord = "[a-z]+";
Pattern computePattern = RegexPatternUtils.computePattern(sensitiveWord);
assertNotNull(computePattern);
Matcher matcher = computePattern.matcher("hello");
assertTrue(matcher.matches());
matcher = computePattern.matcher("dspace");
assertTrue(matcher.matches());
matcher = computePattern.matcher("community");
assertTrue(matcher.matches());
matcher = computePattern.matcher("Hello");
assertFalse(matcher.matches());
matcher = computePattern.matcher("DSpace");
assertFalse(matcher.matches());
matcher = computePattern.matcher("Community");
assertFalse(matcher.matches());
matcher = computePattern.matcher("/wrongpattern/i");
assertFalse(matcher.matches());
matcher = computePattern.matcher("001");
assertFalse(matcher.matches());
matcher = computePattern.matcher("?/'`}{][<>.,");
assertFalse(matcher.matches());
final String sensitiveWordWithDelimiter = "/[a-z]+/";
computePattern = RegexPatternUtils.computePattern(sensitiveWordWithDelimiter);
assertNotNull(computePattern);
matcher = computePattern.matcher("hello");
assertTrue(matcher.matches());
matcher = computePattern.matcher("dspace");
assertTrue(matcher.matches());
matcher = computePattern.matcher("community");
assertTrue(matcher.matches());
matcher = computePattern.matcher("Hello");
assertFalse(matcher.matches());
matcher = computePattern.matcher("DSpace");
assertFalse(matcher.matches());
matcher = computePattern.matcher("Community");
assertFalse(matcher.matches());
matcher = computePattern.matcher("/wrongpattern/i");
assertFalse(matcher.matches());
matcher = computePattern.matcher("001");
assertFalse(matcher.matches());
matcher = computePattern.matcher("?/'`}{][<>.,");
assertFalse(matcher.matches());
}
@Test
public void testWithFuzzyRegex() {
String fuzzyRegex = "/[a-z]+";
Pattern computePattern = RegexPatternUtils.computePattern(fuzzyRegex);
assertNotNull(computePattern);
Matcher matcher = computePattern.matcher("/hello");
assertTrue(matcher.matches());
matcher = computePattern.matcher("hello");
assertFalse(matcher.matches());
matcher = computePattern.matcher("Hello");
assertFalse(matcher.matches());
fuzzyRegex = "[a-z]+/";
computePattern = RegexPatternUtils.computePattern(fuzzyRegex);
matcher = computePattern.matcher("hello/");
assertTrue(matcher.matches());
matcher = computePattern.matcher("/hello");
assertFalse(matcher.matches());
matcher = computePattern.matcher("hello");
assertFalse(matcher.matches());
matcher = computePattern.matcher("Hello");
assertFalse(matcher.matches());
// equals to pattern \\[a-z]+\\ -> searching for a word delimited by '\'
fuzzyRegex = "\\\\[a-z]+\\\\";
computePattern = RegexPatternUtils.computePattern(fuzzyRegex);
// equals to '\hello\'
matcher = computePattern.matcher("\\hello\\");
assertTrue(matcher.matches());
matcher = computePattern.matcher("/hello");
assertFalse(matcher.matches());
matcher = computePattern.matcher("hello");
assertFalse(matcher.matches());
matcher = computePattern.matcher("Hello");
assertFalse(matcher.matches());
// equals to pattern /[a-z]+/ -> searching for a string delimited by '/'
fuzzyRegex = "\\/[a-z]+\\/";
computePattern = RegexPatternUtils.computePattern(fuzzyRegex);
matcher = computePattern.matcher("/hello/");
assertTrue(matcher.matches());
matcher = computePattern.matcher("/hello");
assertFalse(matcher.matches());
matcher = computePattern.matcher("hello");
assertFalse(matcher.matches());
matcher = computePattern.matcher("Hello");
assertFalse(matcher.matches());
}
@Test
public void testInvalidRegex() {
String invalidSensitive = "[a-z+";
assertThrows(PatternSyntaxException.class, () -> RegexPatternUtils.computePattern(invalidSensitive));
String invalidRange = "a{1-";
assertThrows(PatternSyntaxException.class, () -> RegexPatternUtils.computePattern(invalidRange));
String invalidGroupPattern = "(abc";
assertThrows(PatternSyntaxException.class, () -> RegexPatternUtils.computePattern(invalidGroupPattern));
String emptyPattern = "";
Pattern computePattern = RegexPatternUtils.computePattern(emptyPattern);
assertNull(computePattern);
String blankPattern = " ";
computePattern = RegexPatternUtils.computePattern(blankPattern);
assertNull(computePattern);
String nullPattern = null;
computePattern = RegexPatternUtils.computePattern(nullPattern);
assertNull(computePattern);
}
@Test
public void testMultiFlagRegex() {
String multilineSensitive = "/[a-z]+/gi";
Pattern computePattern = RegexPatternUtils.computePattern(multilineSensitive);
assertNotNull(computePattern);
Matcher matcher = computePattern.matcher("hello");
assertTrue(matcher.matches());
matcher = computePattern.matcher("Hello");
assertTrue(matcher.matches());
multilineSensitive = "/[a-z]+/gim";
computePattern = RegexPatternUtils.computePattern(multilineSensitive);
assertNotNull(computePattern);
matcher = computePattern.matcher("Hello" + System.lineSeparator() + "Everyone");
assertTrue(matcher.find());
assertEquals("Hello", matcher.group());
assertTrue(matcher.find());
assertEquals("Everyone", matcher.group());
matcher = computePattern.matcher("hello");
assertTrue(matcher.matches());
matcher = computePattern.matcher("HELLO");
assertTrue(matcher.matches());
}
}

View File

@@ -111,7 +111,7 @@ public class VersionRestRepository extends DSpaceRestRepository<VersionRest, Int
} }
EPerson submitter = item.getSubmitter(); EPerson submitter = item.getSubmitter();
boolean isAdmin = authorizeService.isAdmin(context); boolean isAdmin = authorizeService.isAdmin(context, item);
boolean canCreateVersion = configurationService.getBooleanProperty("versioning.submitterCanCreateNewVersion"); boolean canCreateVersion = configurationService.getBooleanProperty("versioning.submitterCanCreateNewVersion");
if (!isAdmin && !(canCreateVersion && Objects.equals(submitter, context.getCurrentUser()))) { if (!isAdmin && !(canCreateVersion && Objects.equals(submitter, context.getCurrentUser()))) {

View File

@@ -1111,10 +1111,37 @@ public class BrowsesResourceControllerIT extends AbstractControllerIntegrationTe
//We expect the totalElements to be the 1 item present in the collection //We expect the totalElements to be the 1 item present in the collection
.andExpect(jsonPath("$.page.totalElements", is(1))) .andExpect(jsonPath("$.page.totalElements", is(1)))
//As this is is a small collection, we expect to go-to page 0 //As this is a small collection, we expect to go-to page 0
.andExpect(jsonPath("$.page.number", is(0))) .andExpect(jsonPath("$.page.number", is(0)))
.andExpect(jsonPath("$._links.self.href", containsString("startsWith=Blade"))) .andExpect(jsonPath("$._links.self.href", containsString("startsWith=Blade")))
//Verify that the index jumps to the "Blade Runner" item.
.andExpect(jsonPath("$._embedded.items",
contains(ItemMatcher.matchItemWithTitleAndDateIssued(item2,
"Blade Runner",
"1982-06-25")
)));
//Test filtering with spaces:
//** WHEN **
//An anonymous user browses the items in the Browse by Title endpoint
//with startsWith set to Blade Runner and scope set to Col 1
getClient().perform(get("/api/discover/browses/title/items?startsWith=Blade Runner")
.param("scope", col1.getID().toString())
.param("size", "2"))
//** THEN **
//The status has to be 200 OK
.andExpect(status().isOk())
//We expect the content type to be "application/hal+json;charset=UTF-8"
.andExpect(content().contentType(contentType))
//We expect the totalElements to be the 1 item present in the collection
.andExpect(jsonPath("$.page.totalElements", is(1)))
//As this is a small collection, we expect to go-to page 0
.andExpect(jsonPath("$.page.number", is(0)))
.andExpect(jsonPath("$._links.self.href", containsString("startsWith=Blade Runner")))
//Verify that the index jumps to the "Blade Runner" item. //Verify that the index jumps to the "Blade Runner" item.
.andExpect(jsonPath("$._embedded.items", .andExpect(jsonPath("$._embedded.items",
contains(ItemMatcher.matchItemWithTitleAndDateIssued(item2, contains(ItemMatcher.matchItemWithTitleAndDateIssued(item2,

View File

@@ -850,6 +850,55 @@ public class VersionRestRepositoryIT extends AbstractControllerIntegrationTest {
.andExpect(status().isUnauthorized()); .andExpect(status().isUnauthorized());
} }
@Test
public void createNewVersionItemByCollectionAdminTest() throws Exception {
context.turnOffAuthorisationSystem();
Community rootCommunity = CommunityBuilder.createCommunity(context)
.withName("Parent Community")
.build();
EPerson colAdmin = EPersonBuilder.createEPerson(context)
.withCanLogin(true)
.withEmail("coladmin@email.com")
.withPassword(password)
.withNameInMetadata("Collection", "Admin")
.build();
Collection col = CollectionBuilder
.createCollection(context, rootCommunity)
.withName("Collection 1")
.withAdminGroup(colAdmin)
.build();
Item item = ItemBuilder.createItem(context, col)
.withTitle("Public test item")
.withIssueDate("2022-12-19")
.withAuthor("Doe, John")
.withSubject("ExtraEntry")
.build();
item.setSubmitter(eperson);
context.restoreAuthSystemState();
AtomicReference<Integer> idRef = new AtomicReference<Integer>();
String token = getAuthToken(colAdmin.getEmail(), password);
try {
getClient(token).perform(post("/api/versioning/versions")
.param("summary", "test summary!")
.contentType(MediaType.parseMediaType(RestMediaTypes.TEXT_URI_LIST_VALUE))
.content("/api/core/items/" + item.getID()))
.andExpect(status().isCreated())
.andExpect(jsonPath("$", Matchers.allOf(
hasJsonPath("$.version", is(2)),
hasJsonPath("$.summary", is("test summary!")),
hasJsonPath("$.type", is("version"))
)))
.andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), "$.id")));
} finally {
VersionBuilder.delete(idRef.get());
}
}
@Test @Test
public void patchReplaceSummaryTest() throws Exception { public void patchReplaceSummaryTest() throws Exception {
context.turnOffAuthorisationSystem(); context.turnOffAuthorisationSystem();

View File

@@ -32,3 +32,11 @@
# By default, only 'dspace.agreements.end-user' can be deleted in bulk, as doing so allows # By default, only 'dspace.agreements.end-user' can be deleted in bulk, as doing so allows
# an administrator to force all users to re-review the End User Agreement on their next login. # an administrator to force all users to re-review the End User Agreement on their next login.
bulkedit.allow-bulk-deletion = dspace.agreements.end-user bulkedit.allow-bulk-deletion = dspace.agreements.end-user
### metadata import script ###
# Set the number after which the changes should be committed while running the script
# After too much consecutive records everything starts to slow down because too many things are being loaded into memory
# If we commit these to the database these are cleared out of our memory and we don't lose as much performance
# By default this is set to 100
bulkedit.change.commit.count = 100