diff --git a/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImport.java b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImport.java index 1d53ca51c0..4161bbb4d8 100644 --- a/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImport.java +++ b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImport.java @@ -598,18 +598,19 @@ public class MetadataImport extends DSpaceRunnable { .getItemImportService(); try { itemImportService.setTest(isTest); + itemImportService.setExcludeContent(isExcludeContent); itemImportService.setResume(isResume); itemImportService.setUseWorkflow(useWorkflow); itemImportService.setUseWorkflowSendEmail(useWorkflowSendEmail); diff --git a/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportCLIScriptConfiguration.java b/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportCLIScriptConfiguration.java index 6582996f36..d265cbf4a1 100644 --- a/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportCLIScriptConfiguration.java +++ b/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportCLIScriptConfiguration.java @@ -13,7 +13,7 @@ import org.dspace.scripts.configuration.ScriptConfiguration; /** * The {@link ScriptConfiguration} for the {@link ItemImportCLI} script - * + * * @author Francesco Pio Scognamiglio (francescopio.scognamiglio at 4science.com) */ public class ItemImportCLIScriptConfiguration extends ItemImportScriptConfiguration { @@ -55,6 +55,9 @@ public class ItemImportCLIScriptConfiguration extends ItemImportScriptConfigurat options.addOption(Option.builder("v").longOpt("validate") .desc("test run - do not actually import items") .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") .desc("apply template") .hasArg(false).required(false).build()); diff --git a/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportScriptConfiguration.java b/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportScriptConfiguration.java index fc761af0fa..a3149040c4 100644 --- a/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportScriptConfiguration.java +++ b/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportScriptConfiguration.java @@ -19,7 +19,7 @@ import org.springframework.beans.factory.annotation.Autowired; /** * The {@link ScriptConfiguration} for the {@link ItemImport} script - * + * * @author Francesco Pio Scognamiglio (francescopio.scognamiglio at 4science.com) */ public class ItemImportScriptConfiguration extends ScriptConfiguration { @@ -81,6 +81,9 @@ public class ItemImportScriptConfiguration extends ScriptC options.addOption(Option.builder("v").longOpt("validate") .desc("test run - do not actually import items") .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") .desc("apply template") .hasArg(false).required(false).build()); diff --git a/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportServiceImpl.java b/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportServiceImpl.java index 047a589b66..076cc8ebe2 100644 --- a/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportServiceImpl.java @@ -62,6 +62,7 @@ import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.exception.ExceptionUtils; +import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.dspace.app.itemimport.service.ItemImportService; import org.dspace.app.util.LocalSchemaFilenameFilter; @@ -135,7 +136,7 @@ import org.xml.sax.SAXException; * allow the registration of files (bitstreams) into DSpace. */ 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; @@ -181,6 +182,7 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea protected String tempWorkDir; protected boolean isTest = false; + protected boolean isExcludeContent = false; protected boolean isResume = false; protected boolean useWorkflow = false; protected boolean useWorkflowSendEmail = false; @@ -1403,6 +1405,10 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea protected void processContentFileEntry(Context c, Item i, String path, String fileName, String bundleName, boolean primary) throws SQLException, IOException, AuthorizeException { + if (isExcludeContent) { + return; + } + String fullpath = path + File.separatorChar + fileName; // get an input stream @@ -2342,6 +2348,11 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea this.isTest = isTest; } + @Override + public void setExcludeContent(boolean isExcludeContent) { + this.isExcludeContent = isExcludeContent; + } + @Override public void setResume(boolean isResume) { this.isResume = isResume; diff --git a/dspace-api/src/main/java/org/dspace/app/itemimport/service/ItemImportService.java b/dspace-api/src/main/java/org/dspace/app/itemimport/service/ItemImportService.java index 76314897ec..e99ece31b9 100644 --- a/dspace-api/src/main/java/org/dspace/app/itemimport/service/ItemImportService.java +++ b/dspace-api/src/main/java/org/dspace/app/itemimport/service/ItemImportService.java @@ -211,6 +211,13 @@ public interface ItemImportService { */ public void setTest(boolean isTest); + /** + * Set exclude-content flag. + * + * @param isExcludeContent true or false + */ + public void setExcludeContent(boolean isExcludeContent); + /** * Set resume flag * diff --git a/dspace-api/src/main/java/org/dspace/app/util/DCInput.java b/dspace-api/src/main/java/org/dspace/app/util/DCInput.java index d2830bf89a..11f9aadd86 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/DCInput.java +++ b/dspace-api/src/main/java/org/dspace/app/util/DCInput.java @@ -10,6 +10,7 @@ package org.dspace.app.util; import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; import javax.annotation.Nullable; @@ -131,10 +132,15 @@ public class DCInput { 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; + /** + * the computed pattern, null if nothing + */ + private Pattern pattern = null; + /** * allowed document types */ @@ -178,7 +184,7 @@ public class DCInput { //check if the input have a language tag language = Boolean.valueOf(fieldMap.get("language")); - valueLanguageList = new ArrayList(); + valueLanguageList = new ArrayList<>(); if (language) { String languageNameTmp = fieldMap.get("value-pairs-name"); if (StringUtils.isBlank(languageNameTmp)) { @@ -191,7 +197,7 @@ public class DCInput { repeatable = "true".equalsIgnoreCase(repStr) || "yes".equalsIgnoreCase(repStr); String nameVariantsString = fieldMap.get("name-variants"); - nameVariants = (StringUtils.isNotBlank(nameVariantsString)) ? + nameVariants = StringUtils.isNotBlank(nameVariantsString) ? nameVariantsString.equalsIgnoreCase("true") : false; label = fieldMap.get("label"); inputType = fieldMap.get("input-type"); @@ -203,17 +209,17 @@ public class DCInput { } hint = fieldMap.get("hint"); warning = fieldMap.get("required"); - required = (warning != null && warning.length() > 0); + required = warning != null && warning.length() > 0; visibility = fieldMap.get("visibility"); readOnly = fieldMap.get("readonly"); vocabulary = fieldMap.get("vocabulary"); - regex = fieldMap.get("regex"); + this.initRegex(fieldMap.get("regex")); String closedVocabularyStr = fieldMap.get("closedVocabulary"); closedVocabulary = "true".equalsIgnoreCase(closedVocabularyStr) || "yes".equalsIgnoreCase(closedVocabularyStr); // parsing of the element (using the colon as split separator) - typeBind = new ArrayList<>(); + typeBind = new ArrayList(); String typeBindDef = fieldMap.get("type-bind"); if (typeBindDef != null && typeBindDef.trim().length() > 0) { 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 * 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 */ public boolean isVisible(String scope) { - return (visibility == null || visibility.equals(scope)); + return visibility == null || visibility.equals(scope); } /** @@ -381,7 +403,7 @@ public class DCInput { /** * Get the style for this form field - * + * * @return the style */ public String getStyle() { @@ -512,8 +534,12 @@ public class DCInput { return visibility; } + public Pattern getPattern() { + return this.pattern; + } + public String getRegex() { - return regex; + return this.regex; } public String getFieldName() { @@ -546,8 +572,7 @@ public class DCInput { public boolean validate(String value) { if (StringUtils.isNotBlank(value)) { try { - if (StringUtils.isNotBlank(regex)) { - Pattern pattern = Pattern.compile(regex); + if (this.pattern != null) { if (!pattern.matcher(value).matches()) { return false; } @@ -557,7 +582,6 @@ public class DCInput { } } - return true; } diff --git a/dspace-api/src/main/java/org/dspace/app/util/RegexPatternUtils.java b/dspace-api/src/main/java/org/dspace/app/util/RegexPatternUtils.java new file mode 100644 index 0000000000..578e57fb09 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/util/RegexPatternUtils.java @@ -0,0 +1,73 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.util; + +import static 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: /{regex}/{flags} (ECMAScript format). + * This method can transform an ECMAScript regex into a java {@code Pattern} object + * wich can be used to validate strings. + *
+ * 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 /{regex}/{flags} + * @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() {} + +} diff --git a/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java b/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java index a9874afda6..5dd491fd4d 100644 --- a/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java @@ -31,10 +31,12 @@ import org.dspace.content.DSpaceObject; import org.dspace.content.Item; import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.BitstreamService; +import org.dspace.content.service.CollectionService; import org.dspace.content.service.WorkspaceItemService; import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.discovery.DiscoverQuery; +import org.dspace.discovery.DiscoverQuery.SORT_ORDER; import org.dspace.discovery.DiscoverResult; import org.dspace.discovery.IndexableObject; import org.dspace.discovery.SearchService; @@ -830,7 +832,7 @@ public class AuthorizeServiceImpl implements AuthorizeService { query = formatCustomQuery(query); DiscoverResult discoverResult = getDiscoverResult(context, query + "search.resourcetype:" + IndexableCommunity.TYPE, - offset, limit); + offset, limit, null, null); for (IndexableObject solrCollections : discoverResult.getIndexableObjects()) { Community community = ((IndexableCommunity) solrCollections).getIndexedObject(); communities.add(community); @@ -852,7 +854,7 @@ public class AuthorizeServiceImpl implements AuthorizeService { query = formatCustomQuery(query); DiscoverResult discoverResult = getDiscoverResult(context, query + "search.resourcetype:" + IndexableCommunity.TYPE, - null, null); + null, null, null, null); return discoverResult.getTotalSearchResults(); } @@ -877,7 +879,7 @@ public class AuthorizeServiceImpl implements AuthorizeService { query = formatCustomQuery(query); DiscoverResult discoverResult = getDiscoverResult(context, query + "search.resourcetype:" + IndexableCollection.TYPE, - offset, limit); + offset, limit, CollectionService.SOLR_SORT_FIELD, SORT_ORDER.asc); for (IndexableObject solrCollections : discoverResult.getIndexableObjects()) { Collection collection = ((IndexableCollection) solrCollections).getIndexedObject(); collections.add(collection); @@ -899,7 +901,7 @@ public class AuthorizeServiceImpl implements AuthorizeService { query = formatCustomQuery(query); DiscoverResult discoverResult = getDiscoverResult(context, query + "search.resourcetype:" + IndexableCollection.TYPE, - null, null); + null, null, null, null); return discoverResult.getTotalSearchResults(); } @@ -919,7 +921,7 @@ public class AuthorizeServiceImpl implements AuthorizeService { } try { - DiscoverResult discoverResult = getDiscoverResult(context, query, null, null); + DiscoverResult discoverResult = getDiscoverResult(context, query, null, null, null, null); if (discoverResult.getTotalSearchResults() > 0) { return true; } @@ -931,7 +933,8 @@ public class AuthorizeServiceImpl implements AuthorizeService { 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 { String groupQuery = getGroupToQuery(groupService.allMemberGroups(context, context.getCurrentUser())); @@ -947,7 +950,9 @@ public class AuthorizeServiceImpl implements AuthorizeService { if (limit != null) { discoverQuery.setMaxResults(limit); } - + if (sortField != null && sortOrder != null) { + discoverQuery.setSortField(sortField, sortOrder); + } return searchService.search(context, discoverQuery); } diff --git a/dspace-api/src/main/java/org/dspace/browse/SolrBrowseDAO.java b/dspace-api/src/main/java/org/dspace/browse/SolrBrowseDAO.java index 391ed90771..cee3ae017e 100644 --- a/dspace-api/src/main/java/org/dspace/browse/SolrBrowseDAO.java +++ b/dspace-api/src/main/java/org/dspace/browse/SolrBrowseDAO.java @@ -17,6 +17,7 @@ import java.util.UUID; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; +import org.apache.solr.client.solrj.util.ClientUtils; import org.dspace.authorize.factory.AuthorizeServiceFactory; import org.dspace.authorize.service.AuthorizeService; import org.dspace.content.Item; @@ -206,7 +207,8 @@ public class SolrBrowseDAO implements BrowseDAO { query.addFilterQueries("{!field f=" + facetField + "_partial}" + value); } 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 // indexed in the Discovery Search core diff --git a/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java index e54f609389..8488b4eaf1 100644 --- a/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java @@ -43,6 +43,7 @@ import org.dspace.core.I18nUtil; import org.dspace.core.LogHelper; import org.dspace.core.service.LicenseService; import org.dspace.discovery.DiscoverQuery; +import org.dspace.discovery.DiscoverQuery.SORT_ORDER; import org.dspace.discovery.DiscoverResult; import org.dspace.discovery.IndexableObject; import org.dspace.discovery.SearchService; @@ -946,6 +947,7 @@ public class CollectionServiceImpl extends DSpaceObjectServiceImpl i discoverQuery.setDSpaceObjectFilter(IndexableCollection.TYPE); discoverQuery.setStart(offset); discoverQuery.setMaxResults(limit); + discoverQuery.setSortField(SOLR_SORT_FIELD, SORT_ORDER.asc); DiscoverResult resp = retrieveCollectionsWithSubmit(context, discoverQuery, null, community, q); for (IndexableObject solrCollections : resp.getIndexableObjects()) { Collection c = ((IndexableCollection) solrCollections).getIndexedObject(); @@ -1025,6 +1027,7 @@ public class CollectionServiceImpl extends DSpaceObjectServiceImpl i discoverQuery.setDSpaceObjectFilter(IndexableCollection.TYPE); discoverQuery.setStart(offset); discoverQuery.setMaxResults(limit); + discoverQuery.setSortField(SOLR_SORT_FIELD, SORT_ORDER.asc); DiscoverResult resp = retrieveCollectionsWithSubmit(context, discoverQuery, entityType, community, q); for (IndexableObject solrCollections : resp.getIndexableObjects()) { diff --git a/dspace-api/src/main/java/org/dspace/content/service/CollectionService.java b/dspace-api/src/main/java/org/dspace/content/service/CollectionService.java index 522bdac224..a5b2b7d8d8 100644 --- a/dspace-api/src/main/java/org/dspace/content/service/CollectionService.java +++ b/dspace-api/src/main/java/org/dspace/content/service/CollectionService.java @@ -33,6 +33,11 @@ import org.dspace.eperson.Group; public interface CollectionService extends DSpaceObjectService, DSpaceObjectLegacySupportService { + /* + * 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. * 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, AuthorizeException; - /** * Create a new collection with the supplied handle and with a new ID. * Once created the collection is added to the given community diff --git a/dspace-api/src/main/java/org/dspace/handle/HandleServiceImpl.java b/dspace-api/src/main/java/org/dspace/handle/HandleServiceImpl.java index 0efaea75e0..aa730fe2b1 100644 --- a/dspace-api/src/main/java/org/dspace/handle/HandleServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/handle/HandleServiceImpl.java @@ -9,6 +9,7 @@ package org.dspace.handle; import java.sql.SQLException; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -211,17 +212,17 @@ public class HandleServiceImpl implements HandleService { @Override public void unbindHandle(Context context, DSpaceObject dso) throws SQLException { - List handles = getInternalHandles(context, dso); - if (CollectionUtils.isNotEmpty(handles)) { - for (Handle handle : handles) { + Iterator handles = dso.getHandles().iterator(); + if (handles.hasNext()) { + while (handles.hasNext()) { + final Handle handle = handles.next(); + handles.remove(); //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 // can verify during a restore whether the same *type* of resource // is reusing this handle! handle.setDSpaceObject(null); - //Also remove the handle from the DSO list to keep a consistent model - dso.getHandles().remove(handle); handleDAO.save(context, handle); @@ -256,7 +257,7 @@ public class HandleServiceImpl implements HandleService { @Override public String findHandle(Context context, DSpaceObject dso) throws SQLException { - List handles = getInternalHandles(context, dso); + List handles = dso.getHandles(); if (CollectionUtils.isEmpty(handles)) { return null; } else { @@ -328,20 +329,6 @@ public class HandleServiceImpl implements HandleService { //////////////////////////////////////// // 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 getInternalHandles(Context context, DSpaceObject dso) - throws SQLException { - return handleDAO.getHandlesByDSpaceObject(context, dso); - } - /** * Find the database row corresponding to handle. * diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.5_2022.12.06__index_action_resource_policy.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.5_2022.12.06__index_action_resource_policy.sql new file mode 100644 index 0000000000..696e84433d --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.5_2022.12.06__index_action_resource_policy.sql @@ -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); diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.5_2022.12.06__index_action_resource_policy.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.5_2022.12.06__index_action_resource_policy.sql new file mode 100644 index 0000000000..696e84433d --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.5_2022.12.06__index_action_resource_policy.sql @@ -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); diff --git a/dspace-api/src/test/java/org/dspace/app/util/RegexPatternUtilsTest.java b/dspace-api/src/test/java/org/dspace/app/util/RegexPatternUtilsTest.java new file mode 100644 index 0000000000..30a9100ad4 --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/app/util/RegexPatternUtilsTest.java @@ -0,0 +1,214 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.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()); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionRestRepository.java index 3f16217d76..585fc1de9f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionRestRepository.java @@ -111,7 +111,7 @@ public class VersionRestRepository extends DSpaceRestRepository idRef = new AtomicReference(); + 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 public void patchReplaceSummaryTest() throws Exception { context.turnOffAuthorisationSystem(); diff --git a/dspace/config/modules/bulkedit.cfg b/dspace/config/modules/bulkedit.cfg index 9e6ec59372..6174af53a0 100644 --- a/dspace/config/modules/bulkedit.cfg +++ b/dspace/config/modules/bulkedit.cfg @@ -32,3 +32,11 @@ # 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. 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 +