Merge remote-tracking branch '4science-bitbucket/main' into CST-6751

This commit is contained in:
Luca Giamminonni
2022-09-22 17:32:34 +02:00
55 changed files with 2140 additions and 323 deletions

View File

@@ -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.administer;
import java.io.IOException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.apache.commons.cli.ParseException;
import org.apache.commons.lang.time.DateUtils;
import org.dspace.authorize.AuthorizeException;
import org.dspace.content.ProcessStatus;
import org.dspace.core.Context;
import org.dspace.scripts.DSpaceRunnable;
import org.dspace.scripts.Process;
import org.dspace.scripts.factory.ScriptServiceFactory;
import org.dspace.scripts.service.ProcessService;
import org.dspace.services.ConfigurationService;
import org.dspace.services.factory.DSpaceServicesFactory;
import org.dspace.utils.DSpace;
/**
* Script to cleanup the old processes in the specified state.
*
* @author Luca Giamminonni (luca.giamminonni at 4science.it)
*
*/
public class ProcessCleaner extends DSpaceRunnable<ProcessCleanerConfiguration<ProcessCleaner>> {
private ConfigurationService configurationService;
private ProcessService processService;
private boolean cleanCompleted = false;
private boolean cleanFailed = false;
private boolean cleanRunning = false;
private boolean help = false;
private Integer days;
@Override
public void setup() throws ParseException {
this.configurationService = DSpaceServicesFactory.getInstance().getConfigurationService();
this.processService = ScriptServiceFactory.getInstance().getProcessService();
this.help = commandLine.hasOption('h');
this.cleanFailed = commandLine.hasOption('f');
this.cleanRunning = commandLine.hasOption('r');
this.cleanCompleted = commandLine.hasOption('c') || (!cleanFailed && !cleanRunning);
this.days = configurationService.getIntProperty("process-cleaner.days", 14);
if (this.days <= 0) {
throw new IllegalStateException("The number of days must be a positive integer.");
}
}
@Override
public void internalRun() throws Exception {
if (help) {
printHelp();
return;
}
Context context = new Context();
try {
context.turnOffAuthorisationSystem();
performDeletion(context);
} finally {
context.restoreAuthSystemState();
context.complete();
}
}
/**
* Delete the processes based on the specified statuses and the configured days
* from their creation.
*/
private void performDeletion(Context context) throws SQLException, IOException, AuthorizeException {
List<ProcessStatus> statuses = getProcessToDeleteStatuses();
Date creationDate = calculateCreationDate();
handler.logInfo("Searching for processes with status: " + statuses);
List<Process> processes = processService.findByStatusAndCreationTimeOlderThan(context, statuses, creationDate);
handler.logInfo("Found " + processes.size() + " processes to be deleted");
for (Process process : processes) {
processService.delete(context, process);
}
handler.logInfo("Process cleanup completed");
}
/**
* Returns the list of Process statuses do be deleted.
*/
private List<ProcessStatus> getProcessToDeleteStatuses() {
List<ProcessStatus> statuses = new ArrayList<ProcessStatus>();
if (cleanCompleted) {
statuses.add(ProcessStatus.COMPLETED);
}
if (cleanFailed) {
statuses.add(ProcessStatus.FAILED);
}
if (cleanRunning) {
statuses.add(ProcessStatus.RUNNING);
}
return statuses;
}
private Date calculateCreationDate() {
return DateUtils.addDays(new Date(), -days);
}
@Override
@SuppressWarnings("unchecked")
public ProcessCleanerConfiguration<ProcessCleaner> getScriptConfiguration() {
return new DSpace().getServiceManager()
.getServiceByName("process-cleaner", ProcessCleanerConfiguration.class);
}
}

View File

@@ -0,0 +1,18 @@
/**
* 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.administer;
/**
* The {@link ProcessCleaner} for CLI.
*
* @author Luca Giamminonni (luca.giamminonni at 4science.it)
*
*/
public class ProcessCleanerCli extends ProcessCleaner {
}

View File

@@ -0,0 +1,18 @@
/**
* 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.administer;
/**
* The {@link ProcessCleanerConfiguration} for CLI.
*
* @author Luca Giamminonni (luca.giamminonni at 4science.it)
*
*/
public class ProcessCleanerCliConfiguration extends ProcessCleanerConfiguration<ProcessCleanerCli> {
}

View File

@@ -0,0 +1,70 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.administer;
import java.sql.SQLException;
import org.apache.commons.cli.Options;
import org.dspace.authorize.service.AuthorizeService;
import org.dspace.core.Context;
import org.dspace.scripts.configuration.ScriptConfiguration;
import org.springframework.beans.factory.annotation.Autowired;
/**
* The {@link ScriptConfiguration} for the {@link ProcessCleaner} script.
*/
public class ProcessCleanerConfiguration<T extends ProcessCleaner> extends ScriptConfiguration<T> {
@Autowired
private AuthorizeService authorizeService;
private Class<T> 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("h", "help", false, "help");
options.addOption("r", "running", false, "delete the process with RUNNING status");
options.getOption("r").setType(boolean.class);
options.addOption("f", "failed", false, "delete the process with FAILED status");
options.getOption("f").setType(boolean.class);
options.addOption("c", "completed", false,
"delete the process with COMPLETED status (default if no statuses are specified)");
options.getOption("c").setType(boolean.class);
super.options = options;
}
return options;
}
@Override
public Class<T> getDspaceRunnableClass() {
return dspaceRunnableClass;
}
@Override
public void setDspaceRunnableClass(Class<T> dspaceRunnableClass) {
this.dspaceRunnableClass = dspaceRunnableClass;
}
}

View File

@@ -0,0 +1,46 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.requestitem;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import org.dspace.content.Collection;
import org.dspace.content.Item;
import org.dspace.core.Context;
import org.dspace.eperson.EPerson;
import org.springframework.lang.NonNull;
/**
* Derive request recipients from groups of the Collection which owns an Item.
* The list will include all members of the administrators group. If the
* resulting list is empty, delegates to {@link RequestItemHelpdeskStrategy}.
*
* @author Mark H. Wood <mwood@iupui.edu>
*/
public class CollectionAdministratorsRequestItemStrategy
extends RequestItemHelpdeskStrategy {
@Override
@NonNull
public List<RequestItemAuthor> getRequestItemAuthor(Context context,
Item item)
throws SQLException {
List<RequestItemAuthor> recipients = new ArrayList<>();
Collection collection = item.getOwningCollection();
for (EPerson admin : collection.getAdministrators().getMembers()) {
recipients.add(new RequestItemAuthor(admin));
}
if (recipients.isEmpty()) {
return super.getRequestItemAuthor(context, item);
} else {
return recipients;
}
}
}

View File

@@ -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.requestitem;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import org.dspace.content.Item;
import org.dspace.core.Context;
import org.springframework.lang.NonNull;
import org.springframework.util.Assert;
/**
* Assemble a list of recipients from the results of other strategies.
* The list of strategy classes is injected as the constructor argument
* {@code strategies}.
* If the strategy list is not configured, returns an empty List.
*
* @author Mark H. Wood <mwood@iupui.edu>
*/
public class CombiningRequestItemStrategy
implements RequestItemAuthorExtractor {
/** The strategies to combine. */
private final List<RequestItemAuthorExtractor> strategies;
/**
* Initialize a combination of strategies.
* @param strategies the author extraction strategies to combine.
*/
public CombiningRequestItemStrategy(@NonNull List<RequestItemAuthorExtractor> strategies) {
Assert.notNull(strategies, "Strategy list may not be null");
this.strategies = strategies;
}
/**
* Do not call.
* @throws IllegalArgumentException always
*/
private CombiningRequestItemStrategy() {
throw new IllegalArgumentException();
}
@Override
@NonNull
public List<RequestItemAuthor> getRequestItemAuthor(Context context, Item item)
throws SQLException {
List<RequestItemAuthor> recipients = new ArrayList<>();
for (RequestItemAuthorExtractor strategy : strategies) {
recipients.addAll(strategy.getRequestItemAuthor(context, item));
}
return recipients;
}
}

View File

@@ -27,7 +27,7 @@ import org.dspace.core.Context;
import org.dspace.core.ReloadableEntity;
/**
* Object representing an Item Request
* Object representing an Item Request.
*/
@Entity
@Table(name = "requestitem")
@@ -94,6 +94,9 @@ public class RequestItem implements ReloadableEntity<Integer> {
this.allfiles = allfiles;
}
/**
* @return {@code true} if all of the Item's files are requested.
*/
public boolean isAllfiles() {
return allfiles;
}
@@ -102,6 +105,9 @@ public class RequestItem implements ReloadableEntity<Integer> {
this.reqMessage = reqMessage;
}
/**
* @return a message from the requester.
*/
public String getReqMessage() {
return reqMessage;
}
@@ -110,6 +116,9 @@ public class RequestItem implements ReloadableEntity<Integer> {
this.reqName = reqName;
}
/**
* @return Human-readable name of the user requesting access.
*/
public String getReqName() {
return reqName;
}
@@ -118,6 +127,9 @@ public class RequestItem implements ReloadableEntity<Integer> {
this.reqEmail = reqEmail;
}
/**
* @return address of the user requesting access.
*/
public String getReqEmail() {
return reqEmail;
}
@@ -126,6 +138,9 @@ public class RequestItem implements ReloadableEntity<Integer> {
this.token = token;
}
/**
* @return a unique request identifier which can be emailed.
*/
public String getToken() {
return token;
}

View File

@@ -11,20 +11,31 @@ import org.dspace.eperson.EPerson;
/**
* Simple DTO to transfer data about the corresponding author for the Request
* Copy feature
* Copy feature.
*
* @author Andrea Bollini
*/
public class RequestItemAuthor {
private String fullName;
private String email;
private final String fullName;
private final String email;
/**
* Construct an author record from given data.
*
* @param fullName the author's full name.
* @param email the author's email address.
*/
public RequestItemAuthor(String fullName, String email) {
super();
this.fullName = fullName;
this.email = email;
}
/**
* Construct an author from an EPerson's metadata.
*
* @param ePerson the EPerson.
*/
public RequestItemAuthor(EPerson ePerson) {
super();
this.fullName = ePerson.getFullName();

View File

@@ -8,26 +8,28 @@
package org.dspace.app.requestitem;
import java.sql.SQLException;
import java.util.List;
import org.dspace.content.Item;
import org.dspace.core.Context;
import org.springframework.lang.NonNull;
/**
* Interface to abstract the strategy for select the author to contact for
* request copy
* Interface to abstract the strategy for selecting the author to contact for
* request copy.
*
* @author Andrea Bollini
*/
public interface RequestItemAuthorExtractor {
/**
* Retrieve the auhtor to contact for a request copy of the give item.
* Retrieve the author to contact for requesting a copy of the given item.
*
* @param context DSpace context object
* @param item item to request
* @return An object containing name an email address to send the request to
* or null if no valid email address was found.
* @return Names and email addresses to send the request to.
* @throws SQLException if database error
*/
public RequestItemAuthor getRequestItemAuthor(Context context, Item item) throws SQLException;
@NonNull
public List<RequestItemAuthor> getRequestItemAuthor(Context context, Item item)
throws SQLException;
}

View File

@@ -72,28 +72,48 @@ public class RequestItemEmailNotifier {
static public void sendRequest(Context context, RequestItem ri, String responseLink)
throws IOException, SQLException {
// Who is making this request?
RequestItemAuthor author = requestItemAuthorExtractor
List<RequestItemAuthor> authors = requestItemAuthorExtractor
.getRequestItemAuthor(context, ri.getItem());
String authorEmail = author.getEmail();
String authorName = author.getFullName();
// Build an email to the approver.
Email email = Email.getEmail(I18nUtil.getEmailFilename(context.getCurrentLocale(),
"request_item.author"));
email.addRecipient(authorEmail);
for (RequestItemAuthor author : authors) {
email.addRecipient(author.getEmail());
}
email.setReplyTo(ri.getReqEmail()); // Requester's address
email.addArgument(ri.getReqName()); // {0} Requester's name
email.addArgument(ri.getReqEmail()); // {1} Requester's address
email.addArgument(ri.isAllfiles() // {2} All bitstreams or just one?
? I18nUtil.getMessage("itemRequest.all") : ri.getBitstream().getName());
email.addArgument(handleService.getCanonicalForm(ri.getItem().getHandle()));
email.addArgument(handleService.getCanonicalForm(ri.getItem().getHandle())); // {3}
email.addArgument(ri.getItem().getName()); // {4} requested item's title
email.addArgument(ri.getReqMessage()); // {5} message from requester
email.addArgument(responseLink); // {6} Link back to DSpace for action
email.addArgument(authorName); // {7} corresponding author name
email.addArgument(authorEmail); // {8} corresponding author email
email.addArgument(configurationService.getProperty("dspace.name"));
email.addArgument(configurationService.getProperty("mail.helpdesk"));
StringBuilder names = new StringBuilder();
StringBuilder addresses = new StringBuilder();
for (RequestItemAuthor author : authors) {
if (names.length() > 0) {
names.append("; ");
addresses.append("; ");
}
names.append(author.getFullName());
addresses.append(author.getEmail());
}
email.addArgument(names.toString()); // {7} corresponding author name
email.addArgument(addresses.toString()); // {8} corresponding author email
email.addArgument(configurationService.getProperty("dspace.name")); // {9}
email.addArgument(configurationService.getProperty("mail.helpdesk")); // {10}
// Send the email.
try {

View File

@@ -8,6 +8,8 @@
package org.dspace.app.requestitem;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import org.dspace.content.Item;
@@ -16,11 +18,11 @@ import org.dspace.core.I18nUtil;
import org.dspace.eperson.EPerson;
import org.dspace.eperson.service.EPersonService;
import org.dspace.services.ConfigurationService;
import org.dspace.services.factory.DSpaceServicesFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.lang.NonNull;
/**
* RequestItem strategy to allow DSpace support team's helpdesk to receive requestItem request
* RequestItem strategy to allow DSpace support team's helpdesk to receive requestItem request.
* With this enabled, then the Item author/submitter doesn't receive the request, but the helpdesk instead does.
*
* Failover to the RequestItemSubmitterStrategy, which means the submitter would get the request if there is no
@@ -33,19 +35,24 @@ public class RequestItemHelpdeskStrategy extends RequestItemSubmitterStrategy {
@Autowired(required = true)
protected EPersonService ePersonService;
@Autowired(required = true)
private ConfigurationService configuration;
public RequestItemHelpdeskStrategy() {
}
@Override
public RequestItemAuthor getRequestItemAuthor(Context context, Item item) throws SQLException {
ConfigurationService configurationService
= DSpaceServicesFactory.getInstance().getConfigurationService();
boolean helpdeskOverridesSubmitter = configurationService
@NonNull
public List<RequestItemAuthor> getRequestItemAuthor(Context context, Item item)
throws SQLException {
boolean helpdeskOverridesSubmitter = configuration
.getBooleanProperty("request.item.helpdesk.override", false);
String helpDeskEmail = configurationService.getProperty("mail.helpdesk");
String helpDeskEmail = configuration.getProperty("mail.helpdesk");
if (helpdeskOverridesSubmitter && StringUtils.isNotBlank(helpDeskEmail)) {
return getHelpDeskPerson(context, helpDeskEmail);
List<RequestItemAuthor> authors = new ArrayList<>(1);
authors.add(getHelpDeskPerson(context, helpDeskEmail));
return authors;
} else {
//Fallback to default logic (author of Item) if helpdesk isn't fully enabled or setup
return super.getRequestItemAuthor(context, item);

View File

@@ -8,6 +8,8 @@
package org.dspace.app.requestitem;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
@@ -16,12 +18,13 @@ import org.dspace.content.MetadataValue;
import org.dspace.content.service.ItemService;
import org.dspace.core.Context;
import org.dspace.core.I18nUtil;
import org.dspace.services.factory.DSpaceServicesFactory;
import org.dspace.services.ConfigurationService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.lang.NonNull;
/**
* Try to look to an item metadata for the corresponding author name and email.
* Failover to the RequestItemSubmitterStrategy
* Failover to the RequestItemSubmitterStrategy.
*
* @author Andrea Bollini
*/
@@ -30,6 +33,9 @@ public class RequestItemMetadataStrategy extends RequestItemSubmitterStrategy {
protected String emailMetadata;
protected String fullNameMetadata;
@Autowired(required = true)
protected ConfigurationService configurationService;
@Autowired(required = true)
protected ItemService itemService;
@@ -37,59 +43,72 @@ public class RequestItemMetadataStrategy extends RequestItemSubmitterStrategy {
}
@Override
public RequestItemAuthor getRequestItemAuthor(Context context, Item item)
@NonNull
public List<RequestItemAuthor> getRequestItemAuthor(Context context, Item item)
throws SQLException {
RequestItemAuthor author = null;
List<RequestItemAuthor> authors;
if (emailMetadata != null) {
List<MetadataValue> vals = itemService.getMetadataByMetadataString(item, emailMetadata);
if (vals.size() > 0) {
String email = vals.iterator().next().getValue();
String fullname = null;
if (fullNameMetadata != null) {
List<MetadataValue> nameVals = itemService.getMetadataByMetadataString(item, fullNameMetadata);
if (nameVals.size() > 0) {
fullname = nameVals.iterator().next().getValue();
List<MetadataValue> nameVals;
if (null != fullNameMetadata) {
nameVals = itemService.getMetadataByMetadataString(item, fullNameMetadata);
} else {
nameVals = Collections.EMPTY_LIST;
}
boolean useNames = vals.size() == nameVals.size();
if (!vals.isEmpty()) {
authors = new ArrayList<>(vals.size());
for (int authorIndex = 0; authorIndex < vals.size(); authorIndex++) {
String email = vals.get(authorIndex).getValue();
String fullname = null;
if (useNames) {
fullname = nameVals.get(authorIndex).getValue();
}
if (StringUtils.isBlank(fullname)) {
fullname = I18nUtil.getMessage(
"org.dspace.app.requestitem.RequestItemMetadataStrategy.unnamed",
context);
}
RequestItemAuthor author = new RequestItemAuthor(
fullname, email);
authors.add(author);
}
if (StringUtils.isBlank(fullname)) {
fullname = I18nUtil
.getMessage(
"org.dspace.app.requestitem.RequestItemMetadataStrategy.unnamed",
context);
}
author = new RequestItemAuthor(fullname, email);
return author;
return authors;
} else {
return Collections.EMPTY_LIST;
}
} else {
// Uses the basic strategy to look for the original submitter
author = super.getRequestItemAuthor(context, item);
// Is the author or their email null. If so get the help desk or admin name and email
if (null == author || null == author.getEmail()) {
String email = null;
String name = null;
authors = super.getRequestItemAuthor(context, item);
// Remove from the list authors that do not have email addresses.
for (RequestItemAuthor author : authors) {
if (null == author.getEmail()) {
authors.remove(author);
}
}
if (authors.isEmpty()) { // No author email addresses! Fall back
//First get help desk name and email
email = DSpaceServicesFactory.getInstance()
.getConfigurationService().getProperty("mail.helpdesk");
name = DSpaceServicesFactory.getInstance()
.getConfigurationService().getProperty("mail.helpdesk.name");
String email = configurationService.getProperty("mail.helpdesk");
String name = configurationService.getProperty("mail.helpdesk.name");
// If help desk mail is null get the mail and name of admin
if (email == null) {
email = DSpaceServicesFactory.getInstance()
.getConfigurationService().getProperty("mail.admin");
name = DSpaceServicesFactory.getInstance()
.getConfigurationService().getProperty("mail.admin.name");
email = configurationService.getProperty("mail.admin");
name = configurationService.getProperty("mail.admin.name");
}
author = new RequestItemAuthor(name, email);
authors.add(new RequestItemAuthor(name, email));
}
return authors;
}
return author;
}
public void setEmailMetadata(String emailMetadata) {
public void setEmailMetadata(@NonNull String emailMetadata) {
this.emailMetadata = emailMetadata;
}
public void setFullNameMetadata(String fullNameMetadata) {
public void setFullNameMetadata(@NonNull String fullNameMetadata) {
this.fullNameMetadata = fullNameMetadata;
}

View File

@@ -8,10 +8,13 @@
package org.dspace.app.requestitem;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import org.dspace.content.Item;
import org.dspace.core.Context;
import org.dspace.eperson.EPerson;
import org.springframework.lang.NonNull;
/**
* Basic strategy that looks to the original submitter.
@@ -24,21 +27,23 @@ public class RequestItemSubmitterStrategy implements RequestItemAuthorExtractor
}
/**
* Returns the submitter of an Item as RequestItemAuthor or null if the
* Submitter is deleted.
* Returns the submitter of an Item as RequestItemAuthor or an empty List if
* the Submitter is deleted.
*
* @return The submitter of the item or null if the submitter is deleted
* @return The submitter of the item or empty List if the submitter is deleted
* @throws SQLException if database error
*/
@Override
public RequestItemAuthor getRequestItemAuthor(Context context, Item item)
@NonNull
public List<RequestItemAuthor> getRequestItemAuthor(Context context, Item item)
throws SQLException {
EPerson submitter = item.getSubmitter();
RequestItemAuthor author = null;
List<RequestItemAuthor> authors = new ArrayList<>(1);
if (null != submitter) {
author = new RequestItemAuthor(
submitter.getFullName(), submitter.getEmail());
RequestItemAuthor author = new RequestItemAuthor(
submitter.getFullName(), submitter.getEmail());
authors.add(author);
}
return author;
return authors;
}
}

View File

@@ -20,6 +20,7 @@ import com.rometools.modules.opensearch.OpenSearchModule;
import com.rometools.modules.opensearch.entity.OSQuery;
import com.rometools.modules.opensearch.impl.OpenSearchModuleImpl;
import com.rometools.rome.io.FeedException;
import org.apache.commons.lang.StringUtils;
import org.apache.logging.log4j.Logger;
import org.dspace.app.util.service.OpenSearchService;
import org.dspace.content.DSpaceObject;
@@ -96,7 +97,7 @@ public class OpenSearchServiceImpl implements OpenSearchService {
* Get base search UI URL (websvc.opensearch.uicontext)
*/
protected String getBaseSearchUIURL() {
return configurationService.getProperty("dspace.server.url") + "/" +
return configurationService.getProperty("dspace.ui.url") + "/" +
configurationService.getProperty("websvc.opensearch.uicontext");
}
@@ -177,7 +178,9 @@ public class OpenSearchServiceImpl implements OpenSearchService {
OSQuery osq = new OSQuery();
osq.setRole("request");
try {
osq.setSearchTerms(URLEncoder.encode(query, "UTF-8"));
if (StringUtils.isNotBlank(query)) {
osq.setSearchTerms(URLEncoder.encode(query, "UTF-8"));
}
} catch (UnsupportedEncodingException e) {
log.error(e);
}

View File

@@ -0,0 +1,48 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.authorize;
import static org.apache.commons.lang.StringUtils.isNotBlank;
import java.util.regex.Pattern;
import org.dspace.authorize.service.PasswordValidatorService;
import org.dspace.services.ConfigurationService;
import org.springframework.beans.factory.annotation.Autowired;
/**
* Implementation of {@link PasswordValidatorService} that verifies if the given
* passowrd matches the configured pattern.
*
* @author Luca Giamminonni (luca.giamminonni at 4science.it)
*/
public class RegexPasswordValidator implements PasswordValidatorService {
@Autowired
private ConfigurationService configurationService;
@Override
public boolean isPasswordValidationEnabled() {
return isNotBlank(getPasswordValidationPattern());
}
@Override
public boolean isPasswordValid(String password) {
if (!isPasswordValidationEnabled()) {
return true;
}
Pattern pattern = Pattern.compile(getPasswordValidationPattern());
return pattern.matcher(password).find();
}
private String getPasswordValidationPattern() {
return configurationService.getProperty("authentication-password.regex-validation.pattern");
}
}

View File

@@ -0,0 +1,33 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.authorize;
import java.util.List;
import org.dspace.authorize.service.PasswordValidatorService;
import org.dspace.authorize.service.ValidatePasswordService;
import org.springframework.beans.factory.annotation.Autowired;
/**
* Basic implementation for validation password robustness.
*
* @author Mykhaylo Boychuk (mykhaylo.boychuk@4science.com)
*/
public class ValidatePasswordServiceImpl implements ValidatePasswordService {
@Autowired
private List<PasswordValidatorService> validators;
@Override
public boolean isPasswordValid(String password) {
return validators.stream()
.filter(passwordValidator -> passwordValidator.isPasswordValidationEnabled())
.allMatch(passwordValidator -> passwordValidator.isPasswordValid(password));
}
}

View File

@@ -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.authorize.service;
/**
* Interface for classes that validate a given password with a specific
* strategy.
*
* @author Luca Giamminonni (luca.giamminonni at 4science.it)
*/
public interface PasswordValidatorService {
/**
* Check if the password validator is active.
*/
public boolean isPasswordValidationEnabled();
/**
* This method checks whether the password is valid
*
* @param password password to validate
*/
public boolean isPasswordValid(String password);
}

View File

@@ -0,0 +1,25 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.authorize.service;
/**
* Services to use during Validating of password.
*
* @author Mykhaylo Boychuk (mykhaylo.boychuk@4science.com)
*/
public interface ValidatePasswordService {
/**
* This method checks whether the password is valid based on the configured
* rules/strategies.
*
* @param password password to validate
*/
public boolean isPasswordValid(String password);
}

View File

@@ -8,8 +8,10 @@
package org.dspace.content.dao;
import java.sql.SQLException;
import java.util.Date;
import java.util.List;
import org.dspace.content.ProcessStatus;
import org.dspace.core.Context;
import org.dspace.core.GenericDAO;
import org.dspace.scripts.Process;
@@ -81,4 +83,18 @@ public interface ProcessDAO extends GenericDAO<Process> {
int countTotalWithParameters(Context context, ProcessQueryParameterContainer processQueryParameterContainer)
throws SQLException;
/**
* Find all the processes with one of the given status and with a creation time
* older than the specified date.
*
* @param context The relevant DSpace context
* @param statuses the statuses of the processes to search for
* @param date the creation date to search for
* @return The list of all Processes which match requirements
* @throws SQLException If something goes wrong
*/
List<Process> findByStatusAndCreationTimeOlderThan(Context context, List<ProcessStatus> statuses, Date date)
throws SQLException;
}

View File

@@ -7,7 +7,10 @@
*/
package org.dspace.content.dao.impl;
import static org.dspace.scripts.Process_.CREATION_TIME;
import java.sql.SQLException;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
@@ -17,6 +20,7 @@ import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import org.apache.commons.lang3.StringUtils;
import org.dspace.content.ProcessStatus;
import org.dspace.content.dao.ProcessDAO;
import org.dspace.core.AbstractHibernateDAO;
import org.dspace.core.Context;
@@ -147,6 +151,23 @@ public class ProcessDAOImpl extends AbstractHibernateDAO<Process> implements Pro
}
@Override
public List<Process> findByStatusAndCreationTimeOlderThan(Context context, List<ProcessStatus> statuses,
Date date) throws SQLException {
CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context);
CriteriaQuery<Process> criteriaQuery = getCriteriaQuery(criteriaBuilder, Process.class);
Root<Process> processRoot = criteriaQuery.from(Process.class);
criteriaQuery.select(processRoot);
Predicate creationTimeLessThanGivenDate = criteriaBuilder.lessThan(processRoot.get(CREATION_TIME), date);
Predicate statusIn = processRoot.get(Process_.PROCESS_STATUS).in(statuses);
criteriaQuery.where(criteriaBuilder.and(creationTimeLessThanGivenDate, statusIn));
return list(context, criteriaQuery, false, Process.class, -1, -1);
}
}

View File

@@ -5,7 +5,7 @@
*
* http://www.dspace.org/license/
*/
package org.dspace.curate;
package org.dspace.ctask.general;
import java.io.ByteArrayInputStream;
import java.io.IOException;
@@ -18,6 +18,9 @@ import java.util.Map;
import org.apache.logging.log4j.Logger;
import org.dspace.authorize.AuthorizeException;
import org.dspace.authorize.ResourcePolicy;
import org.dspace.authorize.factory.AuthorizeServiceFactory;
import org.dspace.authorize.service.ResourcePolicyService;
import org.dspace.content.Bitstream;
import org.dspace.content.Bundle;
import org.dspace.content.DSpaceObject;
@@ -26,6 +29,10 @@ import org.dspace.content.factory.ContentServiceFactory;
import org.dspace.content.service.BitstreamService;
import org.dspace.content.service.BundleService;
import org.dspace.core.Context;
import org.dspace.curate.AbstractCurationTask;
import org.dspace.curate.Curator;
import org.dspace.curate.Distributive;
import org.dspace.curate.Mutative;
import org.dspace.disseminate.factory.DisseminateServiceFactory;
import org.dspace.disseminate.service.CitationDocumentService;
@@ -67,6 +74,10 @@ public class CitationPage extends AbstractCurationTask {
protected BitstreamService bitstreamService = ContentServiceFactory.getInstance().getBitstreamService();
protected BundleService bundleService = ContentServiceFactory.getInstance().getBundleService();
protected ResourcePolicyService resourcePolicyService = AuthorizeServiceFactory.getInstance()
.getResourcePolicyService();
private Map<String,Bitstream> displayMap = new HashMap<String,Bitstream>();
/**
* {@inheritDoc}
@@ -95,10 +106,13 @@ public class CitationPage extends AbstractCurationTask {
protected void performItem(Item item) throws SQLException {
//Determine if the DISPLAY bundle exits. If not, create it.
List<Bundle> dBundles = itemService.getBundles(item, CitationPage.DISPLAY_BUNDLE_NAME);
Bundle original = itemService.getBundles(item, "ORIGINAL").get(0);
Bundle dBundle = null;
if (dBundles == null || dBundles.isEmpty()) {
try {
dBundle = bundleService.create(Curator.curationContext(), item, CitationPage.DISPLAY_BUNDLE_NAME);
// don't inherit now otherwise they will be copied over the moved bitstreams
resourcePolicyService.removeAllPolicies(Curator.curationContext(), dBundle);
} catch (AuthorizeException e) {
log.error("User not authroized to create bundle on item \"{}\": {}",
item::getName, e::getMessage);
@@ -110,7 +124,6 @@ public class CitationPage extends AbstractCurationTask {
//Create a map of the bitstreams in the displayBundle. This is used to
//check if the bundle being cited is already in the display bundle.
Map<String, Bitstream> displayMap = new HashMap<>();
for (Bitstream bs : dBundle.getBitstreams()) {
displayMap.put(bs.getName(), bs);
}
@@ -128,6 +141,8 @@ public class CitationPage extends AbstractCurationTask {
} else {
try {
pBundle = bundleService.create(Curator.curationContext(), item, CitationPage.PRESERVATION_BUNDLE_NAME);
// don't inherit now otherwise they will be copied over the moved bitstreams
resourcePolicyService.removeAllPolicies(Curator.curationContext(), pBundle);
} catch (AuthorizeException e) {
log.error("User not authroized to create bundle on item \""
+ item.getName() + "\": " + e.getMessage());
@@ -160,7 +175,10 @@ public class CitationPage extends AbstractCurationTask {
citationDocument.makeCitedDocument(Curator.curationContext(), bitstream).getLeft());
//Add the cited document to the approiate bundle
this.addCitedPageToItem(citedInputStream, bundle, pBundle,
dBundle, displayMap, item, bitstream);
dBundle, item, bitstream);
// now set the policies of the preservation and display bundle
clonePolicies(Curator.curationContext(), original, pBundle);
clonePolicies(Curator.curationContext(), original, dBundle);
} catch (Exception e) {
//Could be many things, but nothing that should be
//expected.
@@ -203,8 +221,6 @@ public class CitationPage extends AbstractCurationTask {
* @param pBundle The preservation bundle. The original document should be
* put in here if it is not already.
* @param dBundle The display bundle. The cited document gets put in here.
* @param displayMap The map of bitstream names to bitstreams in the display
* bundle.
* @param item The item containing the bundles being used.
* @param bitstream The original source bitstream.
* @throws SQLException if database error
@@ -212,7 +228,7 @@ public class CitationPage extends AbstractCurationTask {
* @throws IOException if IO error
*/
protected void addCitedPageToItem(InputStream citedDoc, Bundle bundle, Bundle pBundle,
Bundle dBundle, Map<String,Bitstream> displayMap, Item item,
Bundle dBundle, Item item,
Bitstream bitstream) throws SQLException, AuthorizeException, IOException {
//If we are modifying a file that is not in the
//preservation bundle then we have to move it there.
@@ -240,7 +256,8 @@ public class CitationPage extends AbstractCurationTask {
citedBitstream.setName(context, bitstream.getName());
bitstreamService.setFormat(context, citedBitstream, bitstream.getFormat(Curator.curationContext()));
citedBitstream.setDescription(context, bitstream.getDescription());
displayMap.put(bitstream.getName(), citedBitstream);
clonePolicies(context, bitstream, citedBitstream);
this.resBuilder.append(" Added ")
.append(citedBitstream.getName())
.append(" to the ")
@@ -252,4 +269,16 @@ public class CitationPage extends AbstractCurationTask {
itemService.update(context, item);
this.status = Curator.CURATE_SUCCESS;
}
private void clonePolicies(Context context, DSpaceObject source,DSpaceObject target)
throws SQLException, AuthorizeException {
resourcePolicyService.removeAllPolicies(context, target);
for (ResourcePolicy rp: source.getResourcePolicies()) {
ResourcePolicy newPolicy = resourcePolicyService.clone(context, rp);
newPolicy.setdSpaceObject(target);
newPolicy.setAction(rp.getAction());
resourcePolicyService.update(context, newPolicy);
}
}
}

View File

@@ -15,6 +15,7 @@ import java.util.ArrayList;
import java.util.Base64;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@@ -33,6 +34,7 @@ import org.dspace.content.dto.MetadataValueDTO;
import org.dspace.external.OpenAIRERestConnector;
import org.dspace.external.model.ExternalDataObject;
import org.dspace.external.provider.AbstractExternalDataProvider;
import org.dspace.importer.external.metadatamapping.MetadataFieldConfig;
import org.springframework.beans.factory.annotation.Autowired;
/**
@@ -40,13 +42,9 @@ import org.springframework.beans.factory.annotation.Autowired;
* will deal with the OpenAIRE External Data lookup
*
* @author paulo-graca
*
*/
public class OpenAIREFundingDataProvider extends AbstractExternalDataProvider {
/**
* log4j logger
*/
private static Logger log = org.apache.logging.log4j.LogManager.getLogger(OpenAIREFundingDataProvider.class);
/**
@@ -54,6 +52,16 @@ public class OpenAIREFundingDataProvider extends AbstractExternalDataProvider {
*/
protected static final String PREFIX = "info:eu-repo/grantAgreement";
private static final String TITLE = "dcTitle";
private static final String SUBJECT = "dcSubject";
private static final String AWARD_URI = "awardURI";
private static final String FUNDER_NAME = "funderName";
private static final String SPATIAL = "coverageSpatial";
private static final String AWARD_NUMBER = "awardNumber";
private static final String FUNDER_ID = "funderIdentifier";
private static final String FUNDING_STREAM = "fundingStream";
private static final String TITLE_ALTERNATIVE = "titleAlternative";
/**
* rows default limit
*/
@@ -69,11 +77,9 @@ public class OpenAIREFundingDataProvider extends AbstractExternalDataProvider {
*/
protected OpenAIRERestConnector connector;
/**
* required method
*/
public void init() throws IOException {
}
protected Map<String, MetadataFieldConfig> metadataFields;
public void init() throws IOException {}
@Override
public String getSourceIdentifier() {
@@ -266,14 +272,22 @@ public class OpenAIREFundingDataProvider extends AbstractExternalDataProvider {
}
}
public Map<String, MetadataFieldConfig> getMetadataFields() {
return metadataFields;
}
public void setMetadataFields(Map<String, MetadataFieldConfig> metadataFields) {
this.metadataFields = metadataFields;
}
/**
* OpenAIRE Funding External Data Builder Class
*
* @author pgraca
*
*/
public static class ExternalDataObjectBuilder {
ExternalDataObject externalDataObject;
public class ExternalDataObjectBuilder {
private ExternalDataObject externalDataObject;
public ExternalDataObjectBuilder(Project project) {
String funderIdPrefix = "urn:openaire:";
@@ -283,46 +297,42 @@ public class OpenAIREFundingDataProvider extends AbstractExternalDataProvider {
for (FundingTreeType fundingTree : projectHelper.getFundingTreeTypes()) {
FunderType funder = fundingTree.getFunder();
// Funder name
this.addFunderName(funder.getName());
this.addMetadata(metadataFields.get(FUNDER_NAME), funder.getName());
// Funder Id - convert it to an urn
this.addFunderID(funderIdPrefix + funder.getId());
this.addMetadata(metadataFields.get(FUNDER_ID), funderIdPrefix + funder.getId());
// Jurisdiction
this.addFunderJuristiction(funder.getJurisdiction());
this.addMetadata(metadataFields.get(SPATIAL), funder.getJurisdiction());
FundingHelper fundingHelper = new FundingHelper(
fundingTree.getFundingLevel2OrFundingLevel1OrFundingLevel0());
fundingTree.getFundingLevel2OrFundingLevel1OrFundingLevel0());
// Funding description
for (FundingType funding : fundingHelper.getFirstAvailableFunding()) {
this.addFundingStream(funding.getDescription());
this.addMetadata(metadataFields.get(FUNDING_STREAM), funding.getDescription());
}
}
// Title
for (String title : projectHelper.getTitles()) {
this.addAwardTitle(title);
this.addMetadata(metadataFields.get(TITLE), title);
this.setDisplayValue(title);
this.setValue(title);
}
// Code
for (String code : projectHelper.getCodes()) {
this.addAwardNumber(code);
this.addMetadata(metadataFields.get(AWARD_NUMBER), code);
}
// Website url
for (String url : projectHelper.getWebsiteUrls()) {
this.addAwardURI(url);
this.addMetadata(metadataFields.get(AWARD_URI), url);
}
// Acronyms
for (String acronym : projectHelper.getAcronyms()) {
this.addFundingItemAcronym(acronym);
this.addMetadata(metadataFields.get(TITLE_ALTERNATIVE), acronym);
}
// Keywords
for (String keyword : projectHelper.getKeywords()) {
this.addSubject(keyword);
this.addMetadata(metadataFields.get(SUBJECT), keyword);
}
}
@@ -366,7 +376,6 @@ public class OpenAIREFundingDataProvider extends AbstractExternalDataProvider {
* @return ExternalDataObjectBuilder
*/
public ExternalDataObjectBuilder setId(String id) {
// we use base64 encoding in order to use slashes / and other
// characters that must be escaped for the <:entry-id>
String base64Id = Base64.getEncoder().encodeToString(id.getBytes());
@@ -374,128 +383,10 @@ public class OpenAIREFundingDataProvider extends AbstractExternalDataProvider {
return this;
}
/**
* Add metadata dc.identifier
*
* @param metadata identifier
* @return ExternalDataObjectBuilder
*/
public ExternalDataObjectBuilder addIdentifier(String identifier) {
this.externalDataObject.addMetadata(new MetadataValueDTO("dc", "identifier", null, null, identifier));
return this;
}
/**
* Add metadata project.funder.name
*
* @param metadata funderName
* @return ExternalDataObjectBuilder
*/
public ExternalDataObjectBuilder addFunderName(String funderName) {
this.externalDataObject.addMetadata(new MetadataValueDTO("project", "funder", "name", null, funderName));
return this;
}
/**
* Add metadata project.funder.identifier
*
* @param metadata funderId
* @return ExternalDataObjectBuilder
*/
public ExternalDataObjectBuilder addFunderID(String funderID) {
this.externalDataObject
.addMetadata(new MetadataValueDTO("project", "funder", "identifier", null, funderID));
return this;
}
/**
* Add metadata dc.title
*
* @param metadata awardTitle
* @return ExternalDataObjectBuilder
*/
public ExternalDataObjectBuilder addAwardTitle(String awardTitle) {
this.externalDataObject.addMetadata(new MetadataValueDTO("dc", "title", null, null, awardTitle));
return this;
}
/**
* Add metadata oaire.fundingStream
*
* @param metadata fundingStream
* @return ExternalDataObjectBuilder
*/
public ExternalDataObjectBuilder addFundingStream(String fundingStream) {
this.externalDataObject
.addMetadata(new MetadataValueDTO("oaire", "fundingStream", null, null, fundingStream));
return this;
}
/**
* Add metadata oaire.awardNumber
*
* @param metadata awardNumber
* @return ExternalDataObjectBuilder
*/
public ExternalDataObjectBuilder addAwardNumber(String awardNumber) {
this.externalDataObject.addMetadata(new MetadataValueDTO("oaire", "awardNumber", null, null, awardNumber));
return this;
}
/**
* Add metadata oaire.awardURI
*
* @param metadata websiteUrl
* @return ExternalDataObjectBuilder
*/
public ExternalDataObjectBuilder addAwardURI(String websiteUrl) {
this.externalDataObject.addMetadata(new MetadataValueDTO("oaire", "awardURI", null, null, websiteUrl));
return this;
}
/**
* Add metadata dc.title.alternative
*
* @param metadata fundingItemAcronym
* @return ExternalDataObjectBuilder
*/
public ExternalDataObjectBuilder addFundingItemAcronym(String fundingItemAcronym) {
this.externalDataObject
.addMetadata(new MetadataValueDTO("dc", "title", "alternative", null, fundingItemAcronym));
return this;
}
/**
* Add metadata dc.coverage.spatial
*
* @param metadata funderJuristiction
* @return ExternalDataObjectBuilder
*/
public ExternalDataObjectBuilder addFunderJuristiction(String funderJuristiction) {
this.externalDataObject
.addMetadata(new MetadataValueDTO("dc", "coverage", "spatial", null, funderJuristiction));
return this;
}
/**
* Add metadata dc.description
*
* @param metadata description
* @return ExternalDataObjectBuilder
*/
public ExternalDataObjectBuilder addDescription(String description) {
this.externalDataObject.addMetadata(new MetadataValueDTO("dc", "description", null, null, description));
return this;
}
/**
* Add metadata dc.subject
*
* @param metadata subject
* @return ExternalDataObjectBuilder
*/
public ExternalDataObjectBuilder addSubject(String subject) {
this.externalDataObject.addMetadata(new MetadataValueDTO("dc", "subject", null, null, subject));
public ExternalDataObjectBuilder addMetadata(MetadataFieldConfig metadataField, String value) {
this.externalDataObject.addMetadata(new MetadataValueDTO(metadataField.getSchema(),
metadataField.getElement(),
metadataField.getQualifier(), null, value));
return this;
}
@@ -508,4 +399,5 @@ public class OpenAIREFundingDataProvider extends AbstractExternalDataProvider {
return this.externalDataObject;
}
}
}
}

View File

@@ -0,0 +1,65 @@
/**
* 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 org.dspace.importer.external.metadatamapping.MetadataFieldMapping;
import org.dspace.importer.external.metadatamapping.MetadatumDTO;
/**
* Wrapper class used to split another MetadataContributor's output into distinct values.
* The split is performed by matching a regular expression against the wrapped MetadataContributor's output.
*
* @author Philipp Rumpf (philipp.rumpf@uni-bamberg.de)
*/
public class SplitMetadataContributor<T> implements MetadataContributor<T> {
private final MetadataContributor<T> innerContributor;
private final String regex;
/**
* @param innerContributor The MetadataContributor whose output is split
* @param regex A regular expression matching the separator between different values
*/
public SplitMetadataContributor(MetadataContributor<T> innerContributor, String regex) {
this.innerContributor = innerContributor;
this.regex = regex;
}
@Override
public void setMetadataFieldMapping(MetadataFieldMapping<T, MetadataContributor<T>> rt) {
}
/**
* Each metadatum returned by the wrapped MetadataContributor is split into one or more metadata values
* based on the provided regular expression.
*
* @param t The recordType object to retrieve metadata from
* @return The collection of processed metadata values
*/
@Override
public Collection<MetadatumDTO> contributeMetadata(T t) {
Collection<MetadatumDTO> metadata = innerContributor.contributeMetadata(t);
ArrayList<MetadatumDTO> splitMetadata = new ArrayList<>();
for (MetadatumDTO metadatumDTO : metadata) {
String[] split = metadatumDTO.getValue().split(regex);
for (String splitItem : split) {
MetadatumDTO splitMetadatumDTO = new MetadatumDTO();
splitMetadatumDTO.setSchema(metadatumDTO.getSchema());
splitMetadatumDTO.setElement(metadatumDTO.getElement());
splitMetadatumDTO.setQualifier(metadatumDTO.getQualifier());
splitMetadatumDTO.setValue(splitItem);
splitMetadata.add(splitMetadatumDTO);
}
}
return splitMetadata;
}
}

View File

@@ -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.passwordvalidation.factory;
import org.dspace.authorize.service.PasswordValidatorService;
import org.dspace.services.factory.DSpaceServicesFactory;
/**
* Abstract factory to get services for the passwordvalidation package,
* use PasswordValidationFactory.getInstance() to retrieve an implementation.
*
* @author Mykhaylo Boychuk (mykhaylo.boychuk@4science.com)
*/
public abstract class PasswordValidationFactory {
public abstract PasswordValidatorService getPasswordValidationService();
public static PasswordValidationFactory getInstance() {
return DSpaceServicesFactory.getInstance()
.getServiceManager()
.getServiceByName("validationPasswordFactory", PasswordValidationFactory.class);
}
}

View File

@@ -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.passwordvalidation.factory;
import org.dspace.authorize.service.PasswordValidatorService;
import org.springframework.beans.factory.annotation.Autowired;
/**
* Factory implementation to get services for the PasswordValidation package,
* use PasswordValidationFactory.getInstance() to retrieve an implementation.
*
* @author Mykhaylo Boychuk (mykhaylo.boychuk@4science.com)
*/
public class PasswordValidationFactoryImpl extends PasswordValidationFactory {
@Autowired(required = true)
private PasswordValidatorService PasswordValidatorService;
@Override
public PasswordValidatorService getPasswordValidationService() {
return PasswordValidatorService;
}
}

View File

@@ -305,6 +305,12 @@ public class ProcessServiceImpl implements ProcessService {
tempFile.delete();
}
@Override
public List<Process> findByStatusAndCreationTimeOlderThan(Context context, List<ProcessStatus> statuses,
Date date) throws SQLException {
return this.processDAO.findByStatusAndCreationTimeOlderThan(context, statuses, date);
}
private String formatLogLine(int processId, String scriptName, String output, ProcessLogLevel processLogLevel) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
StringBuilder sb = new StringBuilder();

View File

@@ -10,11 +10,13 @@ package org.dspace.scripts.service;
import java.io.IOException;
import java.io.InputStream;
import java.sql.SQLException;
import java.util.Date;
import java.util.List;
import java.util.Set;
import org.dspace.authorize.AuthorizeException;
import org.dspace.content.Bitstream;
import org.dspace.content.ProcessStatus;
import org.dspace.core.Context;
import org.dspace.eperson.EPerson;
import org.dspace.eperson.Group;
@@ -240,4 +242,17 @@ public interface ProcessService {
*/
void createLogBitstream(Context context, Process process)
throws IOException, SQLException, AuthorizeException;
/**
* Find all the processes with one of the given status and with a creation time
* older than the specified date.
*
* @param context The relevant DSpace context
* @param statuses the statuses of the processes to search for
* @param date the creation date to search for
* @return The list of all Processes which match requirements
* @throws AuthorizeException If something goes wrong
*/
List<Process> findByStatusAndCreationTimeOlderThan(Context context, List<ProcessStatus> statuses, Date date)
throws SQLException;
}

View File

@@ -120,3 +120,4 @@ org.dspace.app.rest.exception.RESTEmptyWorkflowGroupException.message = Refused
org.dspace.app.rest.exception.EPersonNameNotProvidedException.message = The eperson.firstname and eperson.lastname values need to be filled in
org.dspace.app.rest.exception.GroupNameNotProvidedException.message = Cannot create group, no group name is provided
org.dspace.app.rest.exception.GroupHasPendingWorkflowTasksException.message = Cannot delete group, the associated workflow role still has pending tasks
org.dspace.app.rest.exception.PasswordNotValidException.message = New password is invalid. Valid passwords must be at least 8 characters long!

View File

@@ -1,6 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util.xsd"
default-lazy-init="true">
<bean id="mockOpenAIRERestConnector" class="org.dspace.external.MockOpenAIRERestConnector">
@@ -15,11 +19,71 @@
init-method="init">
<property name="sourceIdentifier" value="openAIREFunding" />
<property name="connector" ref="mockOpenAIRERestConnector" />
<property name="metadataFields" ref="mapOfmetadata"/>
<property name="supportedEntityTypes">
<list>
<value>Project</value>
</list>
</property>
</bean>
<util:map id="mapOfmetadata"
map-class="java.util.HashMap" key-type="java.lang.String" value-type="org.dspace.importer.external.metadatamapping.MetadataFieldConfig">
<entry key="awardNumber" value-ref="openaireAwardNumber" />
<entry key="fundingStream" value-ref="openaireFundingStream" />
<entry key="awardURI" value-ref="openaireAwardURI" />
<entry key="funderName" value-ref="openaireFunderName" />
<entry key="funderIdentifier" value-ref="openaireFunderIdentifier" />
<entry key="dcTitle" value-ref="openaireTitle" />
<entry key="titleAlternative" value-ref="openaireTitleAlternative" />
<entry key="dcIdentifier" value-ref="openaireIdentifier" />
<entry key="coverageSpatial" value-ref="openaireSpatial" />
<entry key="dcDescription" value-ref="openaireDescription" />
<entry key="dcSubject" value-ref="openaireSubject" />
</util:map>
<bean id="openaireAwardNumber" class="org.dspace.importer.external.metadatamapping.MetadataFieldConfig">
<constructor-arg value="oaire.awardNumber"/>
</bean>
<bean id="openaireFundingStream" class="org.dspace.importer.external.metadatamapping.MetadataFieldConfig">
<constructor-arg value="oaire.fundingStream"/>
</bean>
<bean id="openaireAwardURI" class="org.dspace.importer.external.metadatamapping.MetadataFieldConfig">
<constructor-arg value="oaire.awardURI"/>
</bean>
<bean id="openaireFunderName" class="org.dspace.importer.external.metadatamapping.MetadataFieldConfig">
<constructor-arg value="project.funder.name"/>
</bean>
<bean id="openaireFunderIdentifier" class="org.dspace.importer.external.metadatamapping.MetadataFieldConfig">
<constructor-arg value="project.funder.identifier"/>
</bean>
<bean id="openaireTitle" class="org.dspace.importer.external.metadatamapping.MetadataFieldConfig">
<constructor-arg value="dc.title"/>
</bean>
<bean id="openaireTitleAlternative" class="org.dspace.importer.external.metadatamapping.MetadataFieldConfig">
<constructor-arg value="dc.title.alternative"/>
</bean>
<bean id="openaireIdentifier" class="org.dspace.importer.external.metadatamapping.MetadataFieldConfig">
<constructor-arg value="dc.identifier"/>
</bean>
<bean id="openaireSpatial" class="org.dspace.importer.external.metadatamapping.MetadataFieldConfig">
<constructor-arg value="dc.coverage.spatial"/>
</bean>
<bean id="openaireDescription" class="org.dspace.importer.external.metadatamapping.MetadataFieldConfig">
<constructor-arg value="dc.description"/>
</bean>
<bean id="openaireSubject" class="org.dspace.importer.external.metadatamapping.MetadataFieldConfig">
<constructor-arg value="dc.subject"/>
</bean>
</beans>

View File

@@ -64,7 +64,12 @@
<property name="description" value="Perform the bulk synchronization of all the BATCH configured ORCID entities placed in the ORCID queue"/>
<property name="dspaceRunnableClass" value="org.dspace.orcid.script.OrcidBulkPush"/>
</bean>
<bean id="process-cleaner" class="org.dspace.administer.ProcessCleanerCliConfiguration">
<property name="description" value="Cleanup all the old processes in the specified state"/>
<property name="dspaceRunnableClass" value="org.dspace.administer.ProcessCleanerCli"/>
</bean>
<!-- Keep as last script; for test ScriptRestRepository#findOneScriptByNameTest -->
<bean id="mock-script" class="org.dspace.scripts.MockDSpaceRunnableScriptConfiguration" scope="prototype">
<property name="description" value="Mocking a script for testing purposes" />

View File

@@ -0,0 +1,380 @@
/**
* 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.administer;
import static org.apache.commons.lang.time.DateUtils.addDays;
import static org.dspace.content.ProcessStatus.COMPLETED;
import static org.dspace.content.ProcessStatus.FAILED;
import static org.dspace.content.ProcessStatus.RUNNING;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;
import java.sql.SQLException;
import java.util.Date;
import java.util.List;
import org.dspace.AbstractIntegrationTestWithDatabase;
import org.dspace.app.launcher.ScriptLauncher;
import org.dspace.app.scripts.handler.impl.TestDSpaceRunnableHandler;
import org.dspace.builder.ProcessBuilder;
import org.dspace.content.ProcessStatus;
import org.dspace.scripts.Process;
import org.dspace.scripts.factory.ScriptServiceFactory;
import org.dspace.scripts.service.ProcessService;
import org.dspace.services.ConfigurationService;
import org.dspace.services.factory.DSpaceServicesFactory;
import org.junit.Test;
/**
* Integration tests for {@link ProcessCleaner}.
*
* @author Luca Giamminonni (luca.giamminonni at 4science.it)
*
*/
public class ProcessCleanerIT extends AbstractIntegrationTestWithDatabase {
private ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService();
private ProcessService processService = ScriptServiceFactory.getInstance().getProcessService();
@Test
public void testWithoutProcessToDelete() throws Exception {
Process process_1 = buildProcess(COMPLETED, addDays(new Date(), -2));
Process process_2 = buildProcess(RUNNING, addDays(new Date(), -1));
Process process_3 = buildProcess(FAILED, addDays(new Date(), -3));
configurationService.setProperty("process-cleaner.days", 5);
TestDSpaceRunnableHandler testDSpaceRunnableHandler = new TestDSpaceRunnableHandler();
String[] args = new String[] { "process-cleaner" };
ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl), testDSpaceRunnableHandler, kernelImpl);
assertThat(testDSpaceRunnableHandler.getErrorMessages(), empty());
assertThat(testDSpaceRunnableHandler.getWarningMessages(), empty());
List<String> messages = testDSpaceRunnableHandler.getInfoMessages();
assertThat(messages, hasSize(3));
assertThat(messages, hasItem("Searching for processes with status: [COMPLETED]"));
assertThat(messages, hasItem("Found 0 processes to be deleted"));
assertThat(messages, hasItem("Process cleanup completed"));
assertThat(processService.find(context, process_1.getID()), notNullValue());
assertThat(processService.find(context, process_2.getID()), notNullValue());
assertThat(processService.find(context, process_3.getID()), notNullValue());
}
@Test
public void testWithoutSpecifiedStatus() throws Exception {
Process process_1 = buildProcess(COMPLETED, addDays(new Date(), -2));
Process process_2 = buildProcess(RUNNING, addDays(new Date(), -1));
Process process_3 = buildProcess(FAILED, addDays(new Date(), -3));
Process process_4 = buildProcess(COMPLETED, addDays(new Date(), -6));
Process process_5 = buildProcess(COMPLETED, addDays(new Date(), -8));
Process process_6 = buildProcess(RUNNING, addDays(new Date(), -7));
Process process_7 = buildProcess(FAILED, addDays(new Date(), -8));
configurationService.setProperty("process-cleaner.days", 5);
TestDSpaceRunnableHandler testDSpaceRunnableHandler = new TestDSpaceRunnableHandler();
String[] args = new String[] { "process-cleaner" };
ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl), testDSpaceRunnableHandler, kernelImpl);
assertThat(testDSpaceRunnableHandler.getErrorMessages(), empty());
assertThat(testDSpaceRunnableHandler.getWarningMessages(), empty());
List<String> messages = testDSpaceRunnableHandler.getInfoMessages();
assertThat(messages, hasSize(3));
assertThat(messages, hasItem("Searching for processes with status: [COMPLETED]"));
assertThat(messages, hasItem("Found 2 processes to be deleted"));
assertThat(messages, hasItem("Process cleanup completed"));
assertThat(processService.find(context, process_1.getID()), notNullValue());
assertThat(processService.find(context, process_2.getID()), notNullValue());
assertThat(processService.find(context, process_3.getID()), notNullValue());
assertThat(processService.find(context, process_4.getID()), nullValue());
assertThat(processService.find(context, process_5.getID()), nullValue());
assertThat(processService.find(context, process_6.getID()), notNullValue());
assertThat(processService.find(context, process_7.getID()), notNullValue());
}
@Test
public void testWithCompletedStatus() throws Exception {
Process process_1 = buildProcess(COMPLETED, addDays(new Date(), -2));
Process process_2 = buildProcess(RUNNING, addDays(new Date(), -1));
Process process_3 = buildProcess(FAILED, addDays(new Date(), -3));
Process process_4 = buildProcess(COMPLETED, addDays(new Date(), -6));
Process process_5 = buildProcess(COMPLETED, addDays(new Date(), -8));
Process process_6 = buildProcess(RUNNING, addDays(new Date(), -7));
Process process_7 = buildProcess(FAILED, addDays(new Date(), -8));
configurationService.setProperty("process-cleaner.days", 5);
TestDSpaceRunnableHandler testDSpaceRunnableHandler = new TestDSpaceRunnableHandler();
String[] args = new String[] { "process-cleaner", "-c" };
ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl), testDSpaceRunnableHandler, kernelImpl);
assertThat(testDSpaceRunnableHandler.getErrorMessages(), empty());
assertThat(testDSpaceRunnableHandler.getWarningMessages(), empty());
List<String> messages = testDSpaceRunnableHandler.getInfoMessages();
assertThat(messages, hasSize(3));
assertThat(messages, hasItem("Searching for processes with status: [COMPLETED]"));
assertThat(messages, hasItem("Found 2 processes to be deleted"));
assertThat(messages, hasItem("Process cleanup completed"));
assertThat(processService.find(context, process_1.getID()), notNullValue());
assertThat(processService.find(context, process_2.getID()), notNullValue());
assertThat(processService.find(context, process_3.getID()), notNullValue());
assertThat(processService.find(context, process_4.getID()), nullValue());
assertThat(processService.find(context, process_5.getID()), nullValue());
assertThat(processService.find(context, process_6.getID()), notNullValue());
assertThat(processService.find(context, process_7.getID()), notNullValue());
}
@Test
public void testWithRunningStatus() throws Exception {
Process process_1 = buildProcess(COMPLETED, addDays(new Date(), -2));
Process process_2 = buildProcess(RUNNING, addDays(new Date(), -1));
Process process_3 = buildProcess(FAILED, addDays(new Date(), -3));
Process process_4 = buildProcess(COMPLETED, addDays(new Date(), -6));
Process process_5 = buildProcess(COMPLETED, addDays(new Date(), -8));
Process process_6 = buildProcess(RUNNING, addDays(new Date(), -7));
Process process_7 = buildProcess(FAILED, addDays(new Date(), -8));
Process process_8 = buildProcess(RUNNING, addDays(new Date(), -9));
configurationService.setProperty("process-cleaner.days", 5);
TestDSpaceRunnableHandler testDSpaceRunnableHandler = new TestDSpaceRunnableHandler();
String[] args = new String[] { "process-cleaner", "-r" };
ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl), testDSpaceRunnableHandler, kernelImpl);
assertThat(testDSpaceRunnableHandler.getErrorMessages(), empty());
assertThat(testDSpaceRunnableHandler.getWarningMessages(), empty());
List<String> messages = testDSpaceRunnableHandler.getInfoMessages();
assertThat(messages, hasSize(3));
assertThat(messages, hasItem("Searching for processes with status: [RUNNING]"));
assertThat(messages, hasItem("Found 2 processes to be deleted"));
assertThat(messages, hasItem("Process cleanup completed"));
assertThat(processService.find(context, process_1.getID()), notNullValue());
assertThat(processService.find(context, process_2.getID()), notNullValue());
assertThat(processService.find(context, process_3.getID()), notNullValue());
assertThat(processService.find(context, process_4.getID()), notNullValue());
assertThat(processService.find(context, process_5.getID()), notNullValue());
assertThat(processService.find(context, process_6.getID()), nullValue());
assertThat(processService.find(context, process_7.getID()), notNullValue());
assertThat(processService.find(context, process_8.getID()), nullValue());
}
@Test
public void testWithFailedStatus() throws Exception {
Process process_1 = buildProcess(COMPLETED, addDays(new Date(), -2));
Process process_2 = buildProcess(RUNNING, addDays(new Date(), -1));
Process process_3 = buildProcess(FAILED, addDays(new Date(), -3));
Process process_4 = buildProcess(COMPLETED, addDays(new Date(), -6));
Process process_5 = buildProcess(COMPLETED, addDays(new Date(), -8));
Process process_6 = buildProcess(RUNNING, addDays(new Date(), -7));
Process process_7 = buildProcess(FAILED, addDays(new Date(), -8));
Process process_8 = buildProcess(FAILED, addDays(new Date(), -9));
configurationService.setProperty("process-cleaner.days", 5);
TestDSpaceRunnableHandler testDSpaceRunnableHandler = new TestDSpaceRunnableHandler();
String[] args = new String[] { "process-cleaner", "-f" };
ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl), testDSpaceRunnableHandler, kernelImpl);
assertThat(testDSpaceRunnableHandler.getErrorMessages(), empty());
assertThat(testDSpaceRunnableHandler.getWarningMessages(), empty());
List<String> messages = testDSpaceRunnableHandler.getInfoMessages();
assertThat(messages, hasSize(3));
assertThat(messages, hasItem("Searching for processes with status: [FAILED]"));
assertThat(messages, hasItem("Found 2 processes to be deleted"));
assertThat(messages, hasItem("Process cleanup completed"));
assertThat(processService.find(context, process_1.getID()), notNullValue());
assertThat(processService.find(context, process_2.getID()), notNullValue());
assertThat(processService.find(context, process_3.getID()), notNullValue());
assertThat(processService.find(context, process_4.getID()), notNullValue());
assertThat(processService.find(context, process_5.getID()), notNullValue());
assertThat(processService.find(context, process_6.getID()), notNullValue());
assertThat(processService.find(context, process_7.getID()), nullValue());
assertThat(processService.find(context, process_8.getID()), nullValue());
}
@Test
public void testWithCompletedAndFailedStatus() throws Exception {
Process process_1 = buildProcess(COMPLETED, addDays(new Date(), -2));
Process process_2 = buildProcess(RUNNING, addDays(new Date(), -1));
Process process_3 = buildProcess(FAILED, addDays(new Date(), -3));
Process process_4 = buildProcess(COMPLETED, addDays(new Date(), -6));
Process process_5 = buildProcess(COMPLETED, addDays(new Date(), -8));
Process process_6 = buildProcess(RUNNING, addDays(new Date(), -7));
Process process_7 = buildProcess(FAILED, addDays(new Date(), -8));
Process process_8 = buildProcess(FAILED, addDays(new Date(), -9));
configurationService.setProperty("process-cleaner.days", 5);
TestDSpaceRunnableHandler testDSpaceRunnableHandler = new TestDSpaceRunnableHandler();
String[] args = new String[] { "process-cleaner", "-c", "-f" };
ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl), testDSpaceRunnableHandler, kernelImpl);
List<String> messages = testDSpaceRunnableHandler.getInfoMessages();
assertThat(messages, hasSize(3));
assertThat(messages, hasItem("Searching for processes with status: [COMPLETED, FAILED]"));
assertThat(messages, hasItem("Found 4 processes to be deleted"));
assertThat(messages, hasItem("Process cleanup completed"));
assertThat(processService.find(context, process_1.getID()), notNullValue());
assertThat(processService.find(context, process_2.getID()), notNullValue());
assertThat(processService.find(context, process_3.getID()), notNullValue());
assertThat(processService.find(context, process_4.getID()), nullValue());
assertThat(processService.find(context, process_5.getID()), nullValue());
assertThat(processService.find(context, process_6.getID()), notNullValue());
assertThat(processService.find(context, process_7.getID()), nullValue());
assertThat(processService.find(context, process_8.getID()), nullValue());
}
@Test
public void testWithCompletedAndRunningStatus() throws Exception {
Process process_1 = buildProcess(COMPLETED, addDays(new Date(), -2));
Process process_2 = buildProcess(RUNNING, addDays(new Date(), -1));
Process process_3 = buildProcess(FAILED, addDays(new Date(), -3));
Process process_4 = buildProcess(COMPLETED, addDays(new Date(), -6));
Process process_5 = buildProcess(COMPLETED, addDays(new Date(), -8));
Process process_6 = buildProcess(RUNNING, addDays(new Date(), -7));
Process process_7 = buildProcess(FAILED, addDays(new Date(), -8));
Process process_8 = buildProcess(RUNNING, addDays(new Date(), -9));
configurationService.setProperty("process-cleaner.days", 5);
TestDSpaceRunnableHandler testDSpaceRunnableHandler = new TestDSpaceRunnableHandler();
String[] args = new String[] { "process-cleaner", "-c", "-r" };
ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl), testDSpaceRunnableHandler, kernelImpl);
List<String> messages = testDSpaceRunnableHandler.getInfoMessages();
assertThat(messages, hasSize(3));
assertThat(messages, hasItem("Searching for processes with status: [COMPLETED, RUNNING]"));
assertThat(messages, hasItem("Found 4 processes to be deleted"));
assertThat(messages, hasItem("Process cleanup completed"));
assertThat(processService.find(context, process_1.getID()), notNullValue());
assertThat(processService.find(context, process_2.getID()), notNullValue());
assertThat(processService.find(context, process_3.getID()), notNullValue());
assertThat(processService.find(context, process_4.getID()), nullValue());
assertThat(processService.find(context, process_5.getID()), nullValue());
assertThat(processService.find(context, process_6.getID()), nullValue());
assertThat(processService.find(context, process_7.getID()), notNullValue());
assertThat(processService.find(context, process_8.getID()), nullValue());
}
@Test
public void testWithFailedAndRunningStatus() throws Exception {
Process process_1 = buildProcess(COMPLETED, addDays(new Date(), -2));
Process process_2 = buildProcess(RUNNING, addDays(new Date(), -1));
Process process_3 = buildProcess(FAILED, addDays(new Date(), -3));
Process process_4 = buildProcess(COMPLETED, addDays(new Date(), -6));
Process process_5 = buildProcess(COMPLETED, addDays(new Date(), -8));
Process process_6 = buildProcess(RUNNING, addDays(new Date(), -7));
Process process_7 = buildProcess(FAILED, addDays(new Date(), -8));
Process process_8 = buildProcess(RUNNING, addDays(new Date(), -9));
configurationService.setProperty("process-cleaner.days", 5);
TestDSpaceRunnableHandler testDSpaceRunnableHandler = new TestDSpaceRunnableHandler();
String[] args = new String[] { "process-cleaner", "-f", "-r" };
ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl), testDSpaceRunnableHandler, kernelImpl);
List<String> messages = testDSpaceRunnableHandler.getInfoMessages();
assertThat(messages, hasSize(3));
assertThat(messages, hasItem("Searching for processes with status: [FAILED, RUNNING]"));
assertThat(messages, hasItem("Found 3 processes to be deleted"));
assertThat(messages, hasItem("Process cleanup completed"));
assertThat(processService.find(context, process_1.getID()), notNullValue());
assertThat(processService.find(context, process_2.getID()), notNullValue());
assertThat(processService.find(context, process_3.getID()), notNullValue());
assertThat(processService.find(context, process_4.getID()), notNullValue());
assertThat(processService.find(context, process_5.getID()), notNullValue());
assertThat(processService.find(context, process_6.getID()), nullValue());
assertThat(processService.find(context, process_7.getID()), nullValue());
assertThat(processService.find(context, process_8.getID()), nullValue());
}
@Test
public void testWithCompletedFailedAndRunningStatus() throws Exception {
Process process_1 = buildProcess(COMPLETED, addDays(new Date(), -2));
Process process_2 = buildProcess(RUNNING, addDays(new Date(), -1));
Process process_3 = buildProcess(FAILED, addDays(new Date(), -3));
Process process_4 = buildProcess(COMPLETED, addDays(new Date(), -6));
Process process_5 = buildProcess(COMPLETED, addDays(new Date(), -8));
Process process_6 = buildProcess(RUNNING, addDays(new Date(), -7));
Process process_7 = buildProcess(FAILED, addDays(new Date(), -8));
Process process_8 = buildProcess(RUNNING, addDays(new Date(), -9));
configurationService.setProperty("process-cleaner.days", 5);
TestDSpaceRunnableHandler testDSpaceRunnableHandler = new TestDSpaceRunnableHandler();
String[] args = new String[] { "process-cleaner", "-f", "-r", "-c" };
ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl), testDSpaceRunnableHandler, kernelImpl);
List<String> messages = testDSpaceRunnableHandler.getInfoMessages();
assertThat(messages, hasSize(3));
assertThat(messages, hasItem("Searching for processes with status: [COMPLETED, FAILED, RUNNING]"));
assertThat(messages, hasItem("Found 5 processes to be deleted"));
assertThat(messages, hasItem("Process cleanup completed"));
assertThat(processService.find(context, process_1.getID()), notNullValue());
assertThat(processService.find(context, process_2.getID()), notNullValue());
assertThat(processService.find(context, process_3.getID()), notNullValue());
assertThat(processService.find(context, process_4.getID()), nullValue());
assertThat(processService.find(context, process_5.getID()), nullValue());
assertThat(processService.find(context, process_6.getID()), nullValue());
assertThat(processService.find(context, process_7.getID()), nullValue());
assertThat(processService.find(context, process_8.getID()), nullValue());
}
private Process buildProcess(ProcessStatus processStatus, Date creationTime) throws SQLException {
return ProcessBuilder.createProcess(context, admin, "test", List.of())
.withProcessStatus(processStatus)
.withCreationTime(creationTime)
.build();
}
}

View File

@@ -0,0 +1,62 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.requestitem;
import static org.junit.Assert.assertEquals;
import java.util.List;
import org.dspace.content.Collection;
import org.dspace.content.Item;
import org.dspace.core.Context;
import org.dspace.eperson.EPerson;
import org.dspace.eperson.Group;
import org.junit.Test;
import org.mockito.Mockito;
/**
*
* @author Mark H. Wood <mwood@iupui.edu>
*/
public class CollectionAdministratorsRequestItemStrategyTest {
private static final String NAME = "John Q. Public";
private static final String EMAIL = "jqpublic@example.com";
/**
* Test of getRequestItemAuthor method, of class CollectionAdministratorsRequestItemStrategy.
* @throws java.lang.Exception passed through.
*/
@Test
public void testGetRequestItemAuthor()
throws Exception {
System.out.println("getRequestItemAuthor");
Context context = Mockito.mock(Context.class);
EPerson eperson1 = Mockito.mock(EPerson.class);
Mockito.when(eperson1.getEmail()).thenReturn(EMAIL);
Mockito.when(eperson1.getFullName()).thenReturn(NAME);
Group group1 = Mockito.mock(Group.class);
Mockito.when(group1.getMembers()).thenReturn(List.of(eperson1));
Collection collection1 = Mockito.mock(Collection.class);
Mockito.when(collection1.getAdministrators()).thenReturn(group1);
Item item = Mockito.mock(Item.class);
Mockito.when(item.getOwningCollection()).thenReturn(collection1);
Mockito.when(item.getSubmitter()).thenReturn(eperson1);
CollectionAdministratorsRequestItemStrategy instance = new CollectionAdministratorsRequestItemStrategy();
List<RequestItemAuthor> result = instance.getRequestItemAuthor(context,
item);
assertEquals("Should be one author", 1, result.size());
assertEquals("Name should match " + NAME, NAME, result.get(0).getFullName());
assertEquals("Email should match " + EMAIL, EMAIL, result.get(0).getEmail());
}
}

View File

@@ -0,0 +1,53 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.requestitem;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.collection.IsIterableContainingInAnyOrder.containsInAnyOrder;
import java.util.List;
import org.dspace.content.Item;
import org.dspace.core.Context;
import org.junit.Test;
import org.mockito.Mockito;
/**
*
* @author Mark H. Wood <mwood@iupui.edu>
*/
public class CombiningRequestItemStrategyTest {
/**
* Test of getRequestItemAuthor method, of class CombiningRequestItemStrategy.
* @throws java.lang.Exception passed through.
*/
@Test
public void testGetRequestItemAuthor()
throws Exception {
System.out.println("getRequestItemAuthor");
Context context = null;
Item item = Mockito.mock(Item.class);
RequestItemAuthor author1 = new RequestItemAuthor("Pat Paulsen", "ppaulsen@example.com");
RequestItemAuthor author2 = new RequestItemAuthor("Alfred E. Neuman", "aeneuman@example.com");
RequestItemAuthor author3 = new RequestItemAuthor("Alias Undercover", "aundercover@example.com");
RequestItemAuthorExtractor strategy1 = Mockito.mock(RequestItemHelpdeskStrategy.class);
Mockito.when(strategy1.getRequestItemAuthor(context, item)).thenReturn(List.of(author1));
RequestItemAuthorExtractor strategy2 = Mockito.mock(RequestItemMetadataStrategy.class);
Mockito.when(strategy2.getRequestItemAuthor(context, item)).thenReturn(List.of(author2, author3));
List<RequestItemAuthorExtractor> strategies = List.of(strategy1, strategy2);
CombiningRequestItemStrategy instance = new CombiningRequestItemStrategy(strategies);
List<RequestItemAuthor> result = instance.getRequestItemAuthor(context,
item);
assertThat(result, containsInAnyOrder(author1, author2, author3));
}
}

View File

@@ -0,0 +1,84 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.authorize;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.mockito.Mockito.when;
import org.dspace.AbstractIntegrationTest;
import org.dspace.services.ConfigurationService;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
/**
* Unit tests for {@link RegexPasswordValidator}.
*
* @author Luca Giamminonni (luca.giamminonni at 4science.it)
*/
@RunWith(MockitoJUnitRunner.class)
public class RegexPasswordValidatorTest extends AbstractIntegrationTest {
@Mock
private ConfigurationService configurationService;
@InjectMocks
private RegexPasswordValidator regexPasswordValidator;
@Before
public void setup() {
when(configurationService.getProperty("authentication-password.regex-validation.pattern"))
.thenReturn("^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[^\\da-zA-Z]).{8,15}$");
}
@Test
public void testValidPassword() {
assertThat(regexPasswordValidator.isPasswordValid("TestPassword01!"), is(true));
}
@Test
public void testInvalidPasswordForMissingSpecialCharacter() {
assertThat(regexPasswordValidator.isPasswordValid("TestPassword01"), is(false));
assertThat(regexPasswordValidator.isPasswordValid("TestPassword01?"), is(true));
}
@Test
public void testInvalidPasswordForMissingNumber() {
assertThat(regexPasswordValidator.isPasswordValid("TestPassword!"), is(false));
assertThat(regexPasswordValidator.isPasswordValid("TestPassword1!"), is(true));
}
@Test
public void testInvalidPasswordForMissingUppercaseCharacter() {
assertThat(regexPasswordValidator.isPasswordValid("testpassword01!"), is(false));
assertThat(regexPasswordValidator.isPasswordValid("testPassword01!"), is(true));
}
@Test
public void testInvalidPasswordForMissingLowercaseCharacter() {
assertThat(regexPasswordValidator.isPasswordValid("TESTPASSWORD01!"), is(false));
assertThat(regexPasswordValidator.isPasswordValid("TESTPASSWORd01!"), is(true));
}
@Test
public void testInvalidPasswordForTooShortValue() {
assertThat(regexPasswordValidator.isPasswordValid("Test01!"), is(false));
assertThat(regexPasswordValidator.isPasswordValid("Test012!"), is(true));
}
@Test
public void testInvalidPasswordForTooLongValue() {
assertThat(regexPasswordValidator.isPasswordValid("ThisIsAVeryLongPassword01!"), is(false));
assertThat(regexPasswordValidator.isPasswordValid("IsAPassword012!"), is(true));
}
}

View File

@@ -11,6 +11,7 @@ import java.io.IOException;
import java.sql.SQLException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Set;
@@ -60,6 +61,11 @@ public class ProcessBuilder extends AbstractBuilder<Process, ProcessService> {
return this;
}
public ProcessBuilder withCreationTime(Date creationTime) {
process.setCreationTime(creationTime);
return this;
}
public ProcessBuilder withStartAndEndTime(String startTime, String endTime) throws ParseException {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("dd/MM/yyyy");
process.setStartTime(simpleDateFormat.parse(startTime));

View File

@@ -163,6 +163,7 @@ public class DSpaceApiExceptionControllerAdvice extends ResponseEntityExceptionH
EPersonNameNotProvidedException.class,
GroupNameNotProvidedException.class,
GroupHasPendingWorkflowTasksException.class,
PasswordNotValidException.class,
})
protected void handleCustomUnprocessableEntityException(HttpServletRequest request, HttpServletResponse response,
TranslatableException ex) throws IOException {

View File

@@ -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.rest.exception;
import org.dspace.core.I18nUtil;
/**
* This class provides an exception to be used when trying to create an EPerson
* with password that not match regular expression configured in this
* variable "authentication-password.regex-validation.pattern" in dspace.cfg or during the patch of password.
*
* @author Mykhaylo Boychuk (mykhaylo.boychuk@4science.com)
*/
public class PasswordNotValidException extends UnprocessableEntityException implements TranslatableException {
private static final long serialVersionUID = -4294543847989250566L;
public static final String MESSAGE_KEY = "org.dspace.app.rest.exception.PasswordNotValidException.message";
public PasswordNotValidException() {
super(I18nUtil.getMessage(MESSAGE_KEY));
}
public PasswordNotValidException(Throwable cause) {
super(I18nUtil.getMessage(MESSAGE_KEY), cause);
}
public String getMessageKey() {
return MESSAGE_KEY;
}
}

View File

@@ -21,9 +21,9 @@ import org.apache.logging.log4j.Logger;
import org.dspace.app.rest.DiscoverableEndpointsService;
import org.dspace.app.rest.Parameter;
import org.dspace.app.rest.SearchRestMethod;
import org.dspace.app.rest.authorization.AuthorizationFeatureService;
import org.dspace.app.rest.exception.DSpaceBadRequestException;
import org.dspace.app.rest.exception.EPersonNameNotProvidedException;
import org.dspace.app.rest.exception.PasswordNotValidException;
import org.dspace.app.rest.exception.RESTEmptyWorkflowGroupException;
import org.dspace.app.rest.exception.UnprocessableEntityException;
import org.dspace.app.rest.model.EPersonRest;
@@ -34,7 +34,7 @@ import org.dspace.app.rest.model.patch.Patch;
import org.dspace.app.util.AuthorizeUtil;
import org.dspace.authorize.AuthorizeException;
import org.dspace.authorize.service.AuthorizeService;
import org.dspace.content.service.SiteService;
import org.dspace.authorize.service.ValidatePasswordService;
import org.dspace.core.Context;
import org.dspace.eperson.EPerson;
import org.dspace.eperson.EmptyWorkflowGroupException;
@@ -74,10 +74,7 @@ public class EPersonRestRepository extends DSpaceObjectRestRepository<EPerson, E
private AccountService accountService;
@Autowired
private AuthorizationFeatureService authorizationFeatureService;
@Autowired
private SiteService siteService;
private ValidatePasswordService validatePasswordService;
@Autowired
private RegistrationDataService registrationDataService;
@@ -129,6 +126,9 @@ public class EPersonRestRepository extends DSpaceObjectRestRepository<EPerson, E
eperson.setEmail(epersonRest.getEmail());
eperson.setNetid(epersonRest.getNetid());
if (epersonRest.getPassword() != null) {
if (!validatePasswordService.isPasswordValid(epersonRest.getPassword())) {
throw new PasswordNotValidException();
}
es.setPassword(eperson, epersonRest.getPassword());
}
es.update(context, eperson);

View File

@@ -15,7 +15,9 @@ import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.sql.SQLException;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import javax.servlet.http.HttpServletRequest;
@@ -229,14 +231,20 @@ public class RequestItemRepository
}
// Check for authorized user
RequestItemAuthor authorizer;
List<RequestItemAuthor> authorizers;
try {
authorizer = requestItemAuthorExtractor.getRequestItemAuthor(context, ri.getItem());
authorizers = requestItemAuthorExtractor.getRequestItemAuthor(context, ri.getItem());
} catch (SQLException ex) {
LOG.warn("Failed to find an authorizer: {}", ex.getMessage());
authorizer = new RequestItemAuthor("", "");
authorizers = Collections.EMPTY_LIST;
}
if (!authorizer.getEmail().equals(context.getCurrentUser().getEmail())) {
boolean authorized = false;
String requester = context.getCurrentUser().getEmail();
for (RequestItemAuthor authorizer : authorizers) {
authorized |= authorizer.getEmail().equals(requester);
}
if (!authorized) {
throw new AuthorizeException("Not authorized to approve this request");
}

View File

@@ -12,9 +12,11 @@ import java.sql.SQLException;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.Logger;
import org.dspace.app.rest.exception.DSpaceBadRequestException;
import org.dspace.app.rest.exception.PasswordNotValidException;
import org.dspace.app.rest.model.patch.Operation;
import org.dspace.app.util.AuthorizeUtil;
import org.dspace.authorize.AuthorizeException;
import org.dspace.authorize.service.ValidatePasswordService;
import org.dspace.core.Context;
import org.dspace.eperson.EPerson;
import org.dspace.eperson.factory.EPersonServiceFactory;
@@ -53,6 +55,9 @@ public class EPersonPasswordAddOperation<R> extends PatchOperation<R> {
@Autowired
private AccountService accountService;
@Autowired
private ValidatePasswordService validatePasswordService;
@Override
public R perform(Context context, R object, Operation operation) {
checkOperationValue(operation.getValue());
@@ -66,7 +71,13 @@ public class EPersonPasswordAddOperation<R> extends PatchOperation<R> {
if (StringUtils.isNotBlank(token)) {
verifyAndDeleteToken(context, eperson, token, operation);
}
ePersonService.setPassword(eperson, (String) operation.getValue());
String newPassword = (String) operation.getValue();
if (!validatePasswordService.isPasswordValid(newPassword)) {
throw new PasswordNotValidException();
}
ePersonService.setPassword(eperson, newPassword);
return object;
} else {
throw new DSpaceBadRequestException(this.getClass().getName() + " does not support this operation");

View File

@@ -54,20 +54,7 @@ public class OpenSearchControllerIT extends AbstractControllerIntegrationTest {
;
}
// HTML is an open issue in OpenSearch, so skip this test at the moment
@Test
@Ignore
public void searchHtmlTest() throws Exception {
//When we call the root endpoint
getClient().perform(get("/opensearch/search")
.param("query", "cats")
.param("format", "html"))
//The status has to be 200 OK
.andExpect(status().isOk())
//We expect the content type to be "application/atom+xml;charset=UTF-8"
.andExpect(content().contentType("text/html;charset=UTF-8"))
;
}
// there is no searchHtmlTest here as the html search is redirected to the angular UI
@Test
public void searchRssTest() throws Exception {
@@ -201,17 +188,23 @@ public class OpenSearchControllerIT extends AbstractControllerIntegrationTest {
public void serviceDocumentTest() throws Exception {
//When we call the root endpoint
getClient().perform(get("/opensearch/service"))
//The status has to be 200 OK
.andExpect(status().isOk())
// and the contentType has to be an opensearchdescription
.andExpect(content().contentType("application/opensearchdescription+xml;charset=UTF-8"))
// and there need to be some values taken from the test configuration
.andExpect(xpath("OpenSearchDescription/ShortName").string("DSpace"))
.andExpect(xpath("OpenSearchDescription/LongName").string("DSpace at My University"))
.andExpect(xpath("OpenSearchDescription/Description")
.string("DSpace at My University DSpace repository")
)
;
// The status has to be 200 OK
.andExpect(status().isOk())
// and the contentType has to be an opensearchdescription
.andExpect(content().contentType("application/opensearchdescription+xml;charset=UTF-8"))
// and there need to be some values taken from the test configuration
.andExpect(xpath("OpenSearchDescription/ShortName").string("DSpace"))
.andExpect(xpath("OpenSearchDescription/LongName").string("DSpace at My University"))
.andExpect(xpath("OpenSearchDescription/Description")
.string("DSpace at My University DSpace repository"))
.andExpect(xpath("OpenSearchDescription/Url[@type='text/html']/@template")
.string("http://localhost:4000/search?query={searchTerms}"))
.andExpect(xpath("OpenSearchDescription/Url[@type='application/atom+xml; charset=UTF-8']/@template")
.string("http://localhost/opensearch/search?"
+ "query={searchTerms}&start={startIndex?}&rpp={count?}&format=atom"))
.andExpect(xpath("OpenSearchDescription/Url[@type='application/rss+xml; charset=UTF-8']/@template")
.string("http://localhost/opensearch/search?"
+ "query={searchTerms}&start={startIndex?}&rpp={count?}&format=rss"));
/* Expected response for the service document is:
<?xml version="1.0" encoding="UTF-8"?>
<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/">
@@ -224,9 +217,9 @@ public class OpenSearchControllerIT extends AbstractControllerIntegrationTest {
<Tags>IR DSpace</Tags>
<Contact>dspace-help@myu.edu</Contact>
<Image height="16" width="16" type="image/vnd.microsoft.icon">http://www.dspace.org/images/favicon.ico</Image>
<Url type="text/html" template="http://localhost:8080/simple-search?query={searchTerms}" />
<Url type="application/atom+xml; charset=UTF-8" template="http://localhost:8080/open-search/?query={searchTerms}&amp;start={startIndex?}&amp;rpp={count?}&amp;format=atom" />
<Url type="application/rss+xml; charset=UTF-8" template="http://localhost:8080/open-search/?query={searchTerms}&amp;start={startIndex?}&amp;rpp={count?}&amp;format=rss" />
<Url type="text/html" template="http://localhost:4000/search?query={searchTerms}" />
<Url type="application/atom+xml; charset=UTF-8" template="http://localhost:8080/server/opensearch/search?query={searchTerms}&amp;start={startIndex?}&amp;rpp={count?}&amp;format=atom" />
<Url type="application/rss+xml; charset=UTF-8" template="http://localhost:8080/server/opensearch/search?query={searchTerms}&amp;start={startIndex?}&amp;rpp={count?}&amp;format=rss" />
</OpenSearchDescription>
*/
}

View File

@@ -72,17 +72,14 @@ import org.dspace.core.I18nUtil;
import org.dspace.eperson.EPerson;
import org.dspace.eperson.Group;
import org.dspace.eperson.PasswordHash;
import org.dspace.eperson.dao.RegistrationDataDAO;
import org.dspace.eperson.service.AccountService;
import org.dspace.eperson.service.EPersonService;
import org.dspace.eperson.service.RegistrationDataService;
import org.dspace.services.ConfigurationService;
import org.dspace.workflow.WorkflowService;
import org.hamcrest.Matchers;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest {
@Autowired
@@ -94,11 +91,6 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest {
@Autowired
private EPersonService ePersonService;
@Autowired
private WorkflowService workflowService;
@Autowired
private RegistrationDataDAO registrationDataDAO;
@Autowired
private ConfigurationService configurationService;
@@ -1289,7 +1281,7 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest {
@Test
public void patchPassword() throws Exception {
configurationService.setProperty("authentication-password.regex-validation.pattern", "");
context.turnOffAuthorisationSystem();
EPerson ePerson = EPersonBuilder.createEPerson(context)
@@ -1386,7 +1378,7 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest {
@Test
public void patchPasswordForNonAdminUser() throws Exception {
configurationService.setProperty("authentication-password.regex-validation.pattern", "");
context.turnOffAuthorisationSystem();
EPerson ePerson = EPersonBuilder.createEPerson(context)
@@ -1564,7 +1556,7 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest {
@Test
public void patchPasswordNotInitialised() throws Exception {
configurationService.setProperty("authentication-password.regex-validation.pattern", "");
context.turnOffAuthorisationSystem();
EPerson ePerson = EPersonBuilder.createEPerson(context)
@@ -2009,6 +2001,7 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest {
@Test
public void patchReplacePasswordWithToken() throws Exception {
configurationService.setProperty("authentication-password.regex-validation.pattern", "");
context.turnOffAuthorisationSystem();
EPerson ePerson = EPersonBuilder.createEPerson(context)
@@ -2222,7 +2215,7 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest {
@Test
public void postEPersonWithTokenWithoutEmailProperty() throws Exception {
configurationService.setProperty("authentication-password.regex-validation.pattern", "");
ObjectMapper mapper = new ObjectMapper();
String newRegisterEmail = "new-register@fake-email.com";
@@ -2286,7 +2279,7 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest {
@Test
public void postEPersonWithTokenWithEmailProperty() throws Exception {
configurationService.setProperty("authentication-password.regex-validation.pattern", "");
ObjectMapper mapper = new ObjectMapper();
String newRegisterEmail = "new-register@fake-email.com";
@@ -2348,7 +2341,7 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest {
@Test
public void postEPersonWithTokenWithEmailAndSelfRegisteredProperty() throws Exception {
configurationService.setProperty("authentication-password.regex-validation.pattern", "");
ObjectMapper mapper = new ObjectMapper();
String newRegisterEmail = "new-register@fake-email.com";
@@ -2801,7 +2794,7 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest {
@Test
public void postEPersonWithTokenWithEmailPropertyAnonUser() throws Exception {
configurationService.setProperty("authentication-password.regex-validation.pattern", "");
ObjectMapper mapper = new ObjectMapper();
String newRegisterEmail = "new-register@fake-email.com";
@@ -3129,4 +3122,199 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest {
}
}
@Test
public void validatePasswordRobustnessContainingAtLeastAnUpperCaseCharUnprocessableTest() throws Exception {
configurationService.setProperty("authentication-password.regex-validation.pattern", "^(?=.*[A-Z])");
ObjectMapper mapper = new ObjectMapper();
String newRegisterEmail = "new-register@fake-email.com";
RegistrationRest registrationRest = new RegistrationRest();
registrationRest.setEmail(newRegisterEmail);
getClient().perform(post("/api/eperson/registrations")
.contentType(MediaType.APPLICATION_JSON)
.content(mapper.writeValueAsBytes(registrationRest)))
.andExpect(status().isCreated());
String newRegisterToken = registrationDataService.findByEmail(context, newRegisterEmail).getToken();
EPersonRest ePersonRest = new EPersonRest();
MetadataRest metadataRest = new MetadataRest();
ePersonRest.setCanLogIn(true);
MetadataValueRest surname = new MetadataValueRest();
surname.setValue("Misha");
metadataRest.put("eperson.lastname", surname);
MetadataValueRest firstname = new MetadataValueRest();
firstname.setValue("Boychuk");
metadataRest.put("eperson.firstname", firstname);
ePersonRest.setMetadata(metadataRest);
ePersonRest.setPassword("lowercasepassword");
mapper.setAnnotationIntrospector(new IgnoreJacksonWriteOnlyAccess());
try {
getClient().perform(post("/api/eperson/epersons")
.param("token", newRegisterToken)
.content(mapper.writeValueAsBytes(ePersonRest))
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isUnprocessableEntity());
} finally {
context.turnOffAuthorisationSystem();
registrationDataService.deleteByToken(context, newRegisterToken);
context.restoreAuthSystemState();
}
}
@Test
public void validatePasswordRobustnessContainingAtLeastAnUpperCaseCharTest() throws Exception {
configurationService.setProperty("authentication-password.regex-validation.pattern", "^(?=.*[A-Z])");
ObjectMapper mapper = new ObjectMapper();
String newRegisterEmail = "new-register@fake-email.com";
RegistrationRest registrationRest = new RegistrationRest();
registrationRest.setEmail(newRegisterEmail);
getClient().perform(post("/api/eperson/registrations")
.contentType(MediaType.APPLICATION_JSON)
.content(mapper.writeValueAsBytes(registrationRest)))
.andExpect(status().isCreated());
String newRegisterToken = registrationDataService.findByEmail(context, newRegisterEmail).getToken();
EPersonRest ePersonRest = new EPersonRest();
MetadataRest metadataRest = new MetadataRest();
ePersonRest.setCanLogIn(true);
MetadataValueRest surname = new MetadataValueRest();
surname.setValue("Boychuk");
metadataRest.put("eperson.lastname", surname);
MetadataValueRest firstname = new MetadataValueRest();
firstname.setValue("Misha");
metadataRest.put("eperson.firstname", firstname);
ePersonRest.setMetadata(metadataRest);
ePersonRest.setPassword("Lowercasepassword");
AtomicReference<UUID> idRef = new AtomicReference<UUID>();
mapper.setAnnotationIntrospector(new IgnoreJacksonWriteOnlyAccess());
try {
getClient().perform(post("/api/eperson/epersons")
.param("token", newRegisterToken)
.content(mapper.writeValueAsBytes(ePersonRest))
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isCreated())
.andExpect(jsonPath("$", Matchers.allOf(
hasJsonPath("$.uuid", not(empty())),
hasJsonPath("$.type", is("eperson")),
hasJsonPath("$._links.self.href", not(empty())),
hasJsonPath("$.metadata", Matchers.allOf(
matchMetadata("eperson.firstname", "Misha"),
matchMetadata("eperson.lastname", "Boychuk"))))))
.andDo(result -> idRef.set(UUID.fromString(read(result.getResponse().getContentAsString(), "$.id"))));
EPerson createdEPerson = ePersonService.find(context, UUID.fromString(String.valueOf(idRef.get())));
assertTrue(ePersonService.checkPassword(context, createdEPerson, "Lowercasepassword"));
assertNull(registrationDataService.findByToken(context, newRegisterToken));
} finally {
context.turnOffAuthorisationSystem();
registrationDataService.deleteByToken(context, newRegisterToken);
context.restoreAuthSystemState();
EPersonBuilder.deleteEPerson(idRef.get());
}
}
@Test
public void validatePasswordRobustnessContainingAtLeastAnUppercaseCharPatchUnprocessableTest() throws Exception {
context.turnOffAuthorisationSystem();
EPerson ePerson = EPersonBuilder.createEPerson(context)
.withNameInMetadata("John", "Doe")
.withEmail("Johndoe@example.com")
.withPassword("TestPassword")
.build();
context.restoreAuthSystemState();
configurationService.setProperty("authentication-password.regex-validation.pattern", "^(?=.*[A-Z])");
String newPassword = "newpassword";
List<Operation> ops = new ArrayList<Operation>();
AddOperation addOperation = new AddOperation("/password", newPassword);
ops.add(addOperation);
String patchBody = getPatchContent(ops);
String token = getAuthToken(admin.getEmail(), password);
// updates password
getClient(token).perform(patch("/api/eperson/epersons/" + ePerson.getID())
.content(patchBody)
.contentType(MediaType.APPLICATION_JSON_PATCH_JSON))
.andExpect(status().isUnprocessableEntity());
// can't login with new password
token = getAuthToken(ePerson.getEmail(), newPassword);
getClient(token).perform(get("/api/authn/status"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.okay", is(true)))
.andExpect(jsonPath("$.authenticated", is(false)))
.andExpect(jsonPath("$.type", is("status")));
// login with origin password
token = getAuthToken(ePerson.getEmail(), "TestPassword");
getClient(token).perform(get("/api/authn/status"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.okay", is(true)))
.andExpect(jsonPath("$.authenticated", is(true)))
.andExpect(jsonPath("$.type", is("status")));
}
@Test
public void validatePasswordRobustnessContainingAtLeastAnUppercaseCharPatchTest() throws Exception {
configurationService.setProperty("authentication-password.regex-validation.pattern", "^(?=.*[A-Z])");
context.turnOffAuthorisationSystem();
EPerson ePerson = EPersonBuilder.createEPerson(context)
.withNameInMetadata("John", "Doe")
.withEmail("Johndoe@example.com")
.withPassword("TestPassword")
.build();
context.restoreAuthSystemState();
String newPassword = "Newpassword";
List<Operation> ops = new ArrayList<Operation>();
AddOperation addOperation = new AddOperation("/password", newPassword);
ops.add(addOperation);
String patchBody = getPatchContent(ops);
String token = getAuthToken(admin.getEmail(), password);
// updates password
getClient(token).perform(patch("/api/eperson/epersons/" + ePerson.getID())
.content(patchBody)
.contentType(MediaType.APPLICATION_JSON_PATCH_JSON))
.andExpect(status().isOk());
// login with new password
token = getAuthToken(ePerson.getEmail(), newPassword);
getClient(token).perform(get("/api/authn/status"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.okay", is(true)))
.andExpect(jsonPath("$.authenticated", is(true)))
.andExpect(jsonPath("$.type", is("status")));
// can't login with old password
token = getAuthToken(ePerson.getEmail(), "TestPassword");
getClient(token).perform(get("/api/authn/status"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.okay", is(true)))
.andExpect(jsonPath("$.authenticated", is(false)))
.andExpect(jsonPath("$.type", is("status")));
}
}

View File

@@ -994,6 +994,7 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration
}
bibtex.close();
}
@Test
/**
* Test the creation of workspaceitems POSTing to the resource collection endpoint a bibtex file
@@ -1187,6 +1188,112 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration
bibtex.close();
}
@Test
/**
* Test the creation of workspaceitems POSTing to the resource collection endpoint a bibtex file
*
* @throws Exception
*/
public void createSingleWorkspaceItemFromBibtexFileWithMultipleAuthorsTest() throws Exception {
context.turnOffAuthorisationSystem();
//** GIVEN **
//1. A community-collection structure with one parent community with sub-community and two collections.
parentCommunity = CommunityBuilder.createCommunity(context)
.withName("Parent Community")
.build();
Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity)
.withName("Sub Community")
.build();
Collection col1 = CollectionBuilder.createCollection(context, child1)
.withName("Collection 1")
.withSubmitterGroup(eperson)
.build();
Collection col2 = CollectionBuilder.createCollection(context, child1)
.withName("Collection 2")
.withSubmitterGroup(eperson)
.build();
InputStream bibtex = getClass().getResourceAsStream("bibtex-test-multiple-authors.bib");
final MockMultipartFile bibtexFile = new MockMultipartFile("file",
"/local/path/bibtex-test-multiple-authors.bib",
"application/x-bibtex", bibtex);
context.restoreAuthSystemState();
AtomicReference<List<Integer>> idRef = new AtomicReference<>();
String authToken = getAuthToken(eperson.getEmail(), password);
try {
// create a workspaceitem from a single bibliographic entry file explicitly in the default collection (col1)
getClient(authToken).perform(multipart("/api/submission/workspaceitems")
.file(bibtexFile))
// create should return 200, 201 (created) is better for single resource
.andExpect(status().isOk())
.andExpect(jsonPath("$._embedded.workspaceitems[0]" +
".sections.traditionalpageone['dc.title'][0].value",
is("My Article")))
.andExpect(jsonPath("$._embedded.workspaceitems[0]" +
".sections.traditionalpageone['dc.contributor.author'][0].value",
is("A. Nauthor")))
.andExpect(jsonPath("$._embedded.workspaceitems[0]" +
".sections.traditionalpageone['dc.contributor.author'][1].value",
is("A. Nother")))
.andExpect(jsonPath("$._embedded.workspaceitems[0]" +
".sections.traditionalpageone['dc.contributor.author'][2].value",
is("A. Third")))
.andExpect(
jsonPath("$._embedded.workspaceitems[0]._embedded.collection.id",
is(col1.getID().toString())))
.andExpect(jsonPath("$._embedded.workspaceitems[0].sections.upload.files[0]"
+ ".metadata['dc.source'][0].value",
is("/local/path/bibtex-test-multiple-authors.bib")))
.andExpect(jsonPath("$._embedded.workspaceitems[0].sections.upload.files[0]"
+ ".metadata['dc.title'][0].value",
is("bibtex-test-multiple-authors.bib")))
.andExpect(
jsonPath("$._embedded.workspaceitems[*]._embedded.upload").doesNotExist())
.andDo(result -> idRef.set(read(result.getResponse().getContentAsString(),
"$._embedded.workspaceitems[*].id")));
} finally {
if (idRef != null && idRef.get() != null) {
for (int i : idRef.get()) {
WorkspaceItemBuilder.deleteWorkspaceItem(i);
}
}
}
// create a workspaceitem from a single bibliographic entry file explicitly in the col2
try {
getClient(authToken).perform(multipart("/api/submission/workspaceitems")
.file(bibtexFile)
.param("owningCollection", col2.getID().toString()))
.andExpect(status().isOk())
.andExpect(jsonPath("$._embedded.workspaceitems[0]" +
".sections.traditionalpageone['dc.title'][0].value",
is("My Article")))
.andExpect(
jsonPath("$._embedded.workspaceitems[0]._embedded.collection.id",
is(col2.getID().toString())))
.andExpect(jsonPath("$._embedded.workspaceitems[0].sections.upload.files[0]"
+ ".metadata['dc.source'][0].value",
is("/local/path/bibtex-test-multiple-authors.bib")))
.andExpect(jsonPath("$._embedded.workspaceitems[0].sections.upload"
+ ".files[0].metadata['dc.title'][0].value",
is("bibtex-test-multiple-authors.bib")))
.andExpect(
jsonPath("$._embedded.workspaceitems[*]._embedded.upload").doesNotExist())
.andDo(result -> idRef.set(read(result.getResponse().getContentAsString(),
"$._embedded.workspaceitems[*].id")));
} finally {
if (idRef != null && idRef.get() != null) {
for (int i : idRef.get()) {
WorkspaceItemBuilder.deleteWorkspaceItem(i);
}
}
}
bibtex.close();
}
@Test
/**
* Test the creation of workspaceitems POSTing to the resource collection endpoint a csv file

View File

@@ -142,10 +142,10 @@ public class DeleteEPersonSubmitterIT extends AbstractControllerIntegrationTest
Item item = itemService.find(context, installItem.getID());
RequestItemAuthor requestItemAuthor = requestItemAuthorExtractor.getRequestItemAuthor(context, item);
List<RequestItemAuthor> requestItemAuthor = requestItemAuthorExtractor.getRequestItemAuthor(context, item);
assertEquals("Help Desk", requestItemAuthor.getFullName());
assertEquals("dspace-help@myu.edu", requestItemAuthor.getEmail());
assertEquals("Help Desk", requestItemAuthor.get(0).getFullName());
assertEquals("dspace-help@myu.edu", requestItemAuthor.get(0).getEmail());
}
/**
@@ -171,7 +171,7 @@ public class DeleteEPersonSubmitterIT extends AbstractControllerIntegrationTest
Item item = installItemService.installItem(context, wsi);
List<Operation> opsToWithDraw = new ArrayList<Operation>();
List<Operation> opsToWithDraw = new ArrayList<>();
ReplaceOperation replaceOperationToWithDraw = new ReplaceOperation("/withdrawn", true);
opsToWithDraw.add(replaceOperationToWithDraw);
String patchBodyToWithdraw = getPatchContent(opsToWithDraw);
@@ -191,7 +191,7 @@ public class DeleteEPersonSubmitterIT extends AbstractControllerIntegrationTest
assertNull(retrieveItemSubmitter(item.getID()));
List<Operation> opsToReinstate = new ArrayList<Operation>();
List<Operation> opsToReinstate = new ArrayList<>();
ReplaceOperation replaceOperationToReinstate = new ReplaceOperation("/withdrawn", false);
opsToReinstate.add(replaceOperationToReinstate);
String patchBodyToReinstate = getPatchContent(opsToReinstate);

View File

@@ -0,0 +1,4 @@
@misc{ Nobody01,
author = "A. Nauthor and A. Nother and A. Third",
title = "My Article",
year = "2006" }

View File

@@ -1320,10 +1320,10 @@ webui.feed.item.author = dc.contributor.author
# so even if Syndication Feeds are not enabled, they must be configured
# enable open search
websvc.opensearch.enable = true
# url used in service document
websvc.opensearch.svccontext = opensearch
# context for html request URLs - change only for non-standard servlet mapping
websvc.opensearch.uicontext = simple-search
websvc.opensearch.uicontext = search
# context for xml request URLs - change only for non-standard servlet mapping
websvc.opensearch.svccontext = opensearch/search
# present autodiscovery link in every page head
websvc.opensearch.autolink = true
# number of hours to retain results before recalculating
@@ -1342,8 +1342,8 @@ websvc.opensearch.samplequery = photosynthesis
# tags used to describe search service
websvc.opensearch.tags = IR DSpace
# result formats offered - use 1 or more comma-separated from: html,atom,rss
# NB: html is not supported in DSpace7, use normal search module instead
websvc.opensearch.formats = atom,rss
# html uses the normal search module
websvc.opensearch.formats = html,atom,rss
#### Content Inline Disposition Threshold ####
@@ -1557,6 +1557,13 @@ solr-database-resync.time-until-reindex = 600000
# Keep in mind, changing the schedule requires rebooting your servlet container, e.g. Tomcat.
solr-database-resync.cron = 0 15 2 * * ?
#----------------------------------------------------------#
#----------PROCESS CLEANER SCRIPT CONFIGURATION------------#
#----------------------------------------------------------#
# Processes older than this number of days will be deleted when the "process-cleaner" script is next run.
# Default is 14 (i.e. processes that are two weeks or older will be deleted)
# process-cleaner.days = 14
#------------------------------------------------------------------#
#-------------------MODULE CONFIGURATIONS--------------------------#
#------------------------------------------------------------------#

View File

@@ -26,3 +26,24 @@
# SHA-256, SHA-384, and SHA-512 should be available, but you may have
# installed others. If not set, SHA-512 will be used.
# authentication-password.digestAlgorithm = SHA-512
###### Validate Password Robustness Configuration ######
# (by default is enabled, to disable, either comment out this configuration or set it to an empty value)
# This regular expression is used to validate password during creation of EPerson
# or during the patch of password.
# NOTE: when you configure a custom regex, you will also need to update the text of
# "org.dspace.app.rest.exception.PasswordNotValidException.message" in Messages.properties to describe the minimum requirements.
#
# The following regex applies subsequent rules: ^(?=.*?[a-z])(?=.*?[A-Z])(?=\\S*?[0-9])(?=\\S*?[!?$@#$%^&+=]).{8\,15}$
# 1) (?=.*?[a-z]) - the password must contain at least one lowercase character
# 2) (?=.*?[A-Z]) - the password must contain at least one uppercase character
# 3) (?=\\S*?[0-9]) - the password must contain at least one numeric character
# 4) (?=\\S*?[!?$@#$%^&+=]) - the password must contain at least one of the following special character: !?$@#$%^&+=
# 5) {8\,15} - the password must be at least 8 and at most 15 characters long
# REMARK: {8\,15} - the slash in this regex is an exception of the Apache library, as "," is a special character,
# consequently to interpret it correctly you have to add the slash in front
# By default, DSpace just requires a password of 8 or more characters.
# However, we recommend most sites consider either increasing the required length or complexity (see example above)
authentication-password.regex-validation.pattern = ^.{8\,}$

View File

@@ -15,6 +15,7 @@ plugin.named.org.dspace.curate.CurationTask = org.dspace.ctask.general.RequiredM
#plugin.named.org.dspace.curate.CurationTask = org.dspace.ctask.general.MicrosoftTranslator = translate
plugin.named.org.dspace.curate.CurationTask = org.dspace.ctask.general.MetadataValueLinkChecker = checklinks
plugin.named.org.dspace.curate.CurationTask = org.dspace.ctask.general.RegisterDOI = registerdoi
#plugin.named.org.dspace.curate.CurationTask = org.dspace.ctask.general.CitationPage = citationpage
# add new tasks here (or in additional config files)
## task queue implementation

View File

@@ -40,9 +40,14 @@
<property name="key" value="journal" />
</bean>
<bean id="bibtexAuthorsContrib" class="org.dspace.importer.external.metadatamapping.contributor.SimpleMetadataContributor">
<property name="field" ref="dcAuthors"/>
<property name="key" value="author" />
<bean id="bibtexAuthorsContrib" class="org.dspace.importer.external.metadatamapping.contributor.SplitMetadataContributor">
<constructor-arg name="innerContributor">
<bean class="org.dspace.importer.external.metadatamapping.contributor.SimpleMetadataContributor">
<property name="field" ref="dcAuthors"/>
<property name="key" value="author" />
</bean>
</constructor-arg>
<constructor-arg name="regex" value="\sand\s"/>
</bean>
<bean id="bibtexTitleContrib" class="org.dspace.importer.external.metadatamapping.contributor.SimpleMetadataContributor">

View File

@@ -44,6 +44,8 @@
<bean id="versionServiceFactory" class="org.dspace.versioning.factory.VersionServiceFactoryImpl"/>
<bean id="validationPasswordFactory" class="org.dspace.passwordvalidation.factory.PasswordValidationFactoryImpl"/>
<!--Configurable workflow services -->
<bean id="workflowServiceFactory" class="org.dspace.xmlworkflow.factory.XmlWorkflowServiceFactoryImpl"/>

View File

@@ -143,5 +143,8 @@
<bean class="org.dspace.authenticate.OidcAuthenticationBean" id="oidcAuthentication"/>
<bean class="org.dspace.authenticate.oidc.impl.OidcClientImpl" />
<bean class="org.dspace.authorize.ValidatePasswordServiceImpl"/>
<bean class="org.dspace.authorize.RegexPasswordValidator" />
</beans>

View File

@@ -1,6 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util.xsd"
default-lazy-init="true">
<bean id="openAIRERestConnector" class="org.dspace.external.OpenAIRERestConnector">
@@ -10,17 +14,65 @@
<property name="clientId" value="${openaire.token.clientId}"/>
<property name="clientSecret" value="${openaire.token.clientSecret}"/>
</bean>
<bean
class="org.dspace.external.provider.impl.OpenAIREFundingDataProvider"
init-method="init">
<bean class="org.dspace.external.provider.impl.OpenAIREFundingDataProvider" init-method="init">
<property name="sourceIdentifier" value="openAIREFunding" />
<property name="connector" ref="openAIRERestConnector" />
<property name="metadataFields" ref="mapOfmetadata"/>
<property name="supportedEntityTypes">
<list>
<value>Project</value>
</list>
</property>
</bean>
</beans>
<util:map id="mapOfmetadata"
map-class="java.util.HashMap" key-type="java.lang.String" value-type="org.dspace.importer.external.metadatamapping.MetadataFieldConfig">
<entry key="awardNumber" value-ref="openaireAwardNumber" />
<entry key="fundingStream" value-ref="openaireFundingStream" />
<entry key="awardURI" value-ref="openaireAwardURI" />
<entry key="funderName" value-ref="openaireFunderName" />
<entry key="funderIdentifier" value-ref="openaireFunderIdentifier" />
<entry key="dcTitle" value-ref="openaireTitle" />
<entry key="titleAlternative" value-ref="openaireTitleAlternative" />
<entry key="coverageSpatial" value-ref="openaireSpatial" />
<entry key="dcSubject" value-ref="openaireSubject" />
</util:map>
<bean id="openaireAwardNumber" class="org.dspace.importer.external.metadatamapping.MetadataFieldConfig">
<constructor-arg value="oaire.awardNumber"/>
</bean>
<bean id="openaireFundingStream" class="org.dspace.importer.external.metadatamapping.MetadataFieldConfig">
<constructor-arg value="oaire.fundingStream"/>
</bean>
<bean id="openaireAwardURI" class="org.dspace.importer.external.metadatamapping.MetadataFieldConfig">
<constructor-arg value="oaire.awardURI"/>
</bean>
<bean id="openaireFunderName" class="org.dspace.importer.external.metadatamapping.MetadataFieldConfig">
<constructor-arg value="project.funder.name"/>
</bean>
<bean id="openaireFunderIdentifier" class="org.dspace.importer.external.metadatamapping.MetadataFieldConfig">
<constructor-arg value="project.funder.identifier"/>
</bean>
<bean id="openaireTitle" class="org.dspace.importer.external.metadatamapping.MetadataFieldConfig">
<constructor-arg value="dc.title"/>
</bean>
<bean id="openaireTitleAlternative" class="org.dspace.importer.external.metadatamapping.MetadataFieldConfig">
<constructor-arg value="dc.title.alternative"/>
</bean>
<bean id="openaireSpatial" class="org.dspace.importer.external.metadatamapping.MetadataFieldConfig">
<constructor-arg value="dc.coverage.spatial"/>
</bean>
<bean id="openaireSubject" class="org.dspace.importer.external.metadatamapping.MetadataFieldConfig">
<constructor-arg value="dc.subject"/>
</bean>
</beans>

View File

@@ -8,25 +8,65 @@
http://www.springframework.org/schema/context/spring-context-2.5.xsd"
default-autowire-candidates="*Service,*DAO,javax.sql.DataSource">
<description>
Strategies for determining who receives Request Copy emails.
A copy request "strategy" class produces a list of addresses to which a
request email should be sent. Each strategy gets its addresses from a
different source. Select the one that meets your need, or use the
CombiningRequestItemStrategy to meld the lists from two or more other
strategies.
</description>
<context:annotation-config /> <!-- allows us to use Spring annotations in beans -->
<bean class="org.dspace.app.requestitem.RequestItemMetadataStrategy"
id="org.dspace.app.requestitem.RequestItemAuthorExtractor"
autowire-candidate='true'>
<!--
Uncomment these properties if you want lookup in metadata the email and
the name of the author to contact for request copy.
If you don't configure that or if the requested item doesn't have these
metadata, the submitter data are used as fail over.
<!-- Select the implementation to be used. -->
<alias alias='org.dspace.app.requestitem.RequestItemAuthorExtractor'
name='org.dspace.app.requestitem.RequestItemMetadataStrategy'/>
<property name="emailMetadata" value="schema.element.qualifier" />
<property name="fullNameMetadata" value="schema.element.qualifier" />
-->
<!-- Get recipients from an item metadata field. -->
<bean class="org.dspace.app.requestitem.RequestItemMetadataStrategy"
id="org.dspace.app.requestitem.RequestItemMetadataStrategy"
autowire-candidate="true">
<!--
Uncomment these properties if you want lookup in metadata the email
and the name of the author to contact for request copy.
If you don't configure that or if the requested item doesn't have
these metadata, the submitter data are used as fail over.
-->
<!--
<property name="emailMetadata"
value="schema.element.qualifier" />
<property name="fullNameMetadata"
value="schema.element.qualifier" />
-->
</bean>
<!-- HelpDesk to instead get RequestItem emails-->
<!--<bean class="org.dspace.app.requestitem.RequestItemHelpdeskStrategy"
id="org.dspace.app.requestitem.RequestItemAuthorExtractor"
autowire-candidate='true'></bean>-->
<!--
<bean class="org.dspace.app.requestitem.RequestItemHelpdeskStrategy"
id="org.dspace.app.requestitem.RequestItemHelpdeskStrategy"/>
-->
<!-- Send request emails to administrators of an Item's owning Collection. -->
<!--
<bean class='org.dspace.app.requestitem.CollectionAdministratorsRequestItemStrategy'
id='org.dspace.app.requestitem.CollectionAdministratorsRequestItemStrategy'/>
-->
<!-- Execute multiple strategies and concatenate their lists of recipients.
Mail will go to all members of the combined list. -->
<!--
<bean class='org.dspace.app.requestitem.CombiningRequestItemStrategy'
id='org.dspace.app.requestitem.CombiningRequestItemStrategy'
autowire='no'>
<constructor-arg>
<description>A list of references to RequestItemAuthorExtractor beans</description>
<list>
<ref bean='org.dspace.app.requestitem.RequestItemMetadataStrategy'/>
<ref bean='org.dspace.app.requestitem.CollectionAdministratorsRequestItemStrategy'/>
</list>
</constructor-arg>
</bean>
-->
</beans>

View File

@@ -50,6 +50,11 @@
<property name="description" value="Manage the OAI-PMH harvesting of external collections"/>
<property name="dspaceRunnableClass" value="org.dspace.app.harvest.HarvestCli"/>
</bean>
<bean id="process-cleaner" class="org.dspace.administer.ProcessCleanerCliConfiguration">
<property name="description" value="Cleanup all the old processes in the specified state"/>
<property name="dspaceRunnableClass" value="org.dspace.administer.ProcessCleanerCli"/>
</bean>
<bean id="filter-media" class="org.dspace.app.mediafilter.MediaFilterScriptConfiguration">
<property name="description" value="Perform the media filtering to extract full text from documents and to create thumbnails"/>

View File

@@ -39,6 +39,11 @@
<property name="dspaceRunnableClass" value="org.dspace.app.harvest.Harvest"/>
</bean>
<bean id="process-cleaner" class="org.dspace.administer.ProcessCleanerConfiguration" primary="true">
<property name="description" value="Cleanup all the old processes in the specified state"/>
<property name="dspaceRunnableClass" value="org.dspace.administer.ProcessCleaner"/>
</bean>
<bean id="orcid-bulk-push" class="org.dspace.orcid.script.OrcidBulkPushScriptConfiguration" primary="true">
<property name="description" value="Perform the bulk synchronization of all the BATCH configured ORCID entities placed in the ORCID queue"/>
<property name="dspaceRunnableClass" value="org.dspace.orcid.script.OrcidBulkPush"/>