Merge branch 'main' into w2p-99200_request-copy-bugfix

This commit is contained in:
jensvannerum
2023-02-14 14:24:30 +01:00
committed by GitHub
174 changed files with 10953 additions and 1324 deletions

View File

@@ -0,0 +1,54 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.alerts;
/**
* Enum representing the options for allowing sessions:
* ALLOW_ALL_SESSIONS - Will allow all users to log in and continue their sessions
* ALLOW_CURRENT_SESSIONS_ONLY - Will prevent non admin users from logging in, however logged-in users
* will remain logged in
* ALLOW_ADMIN_SESSIONS_ONLY - Only admin users can log in, non admin sessions will be interrupted
*
* NOTE: This functionality can be stored in the database, but no support is present right now to interrupt and prevent
* sessions.
*/
public enum AllowSessionsEnum {
ALLOW_ALL_SESSIONS("all"),
ALLOW_CURRENT_SESSIONS_ONLY("current"),
ALLOW_ADMIN_SESSIONS_ONLY("admin");
private String allowSessionsType;
AllowSessionsEnum(String allowSessionsType) {
this.allowSessionsType = allowSessionsType;
}
public String getValue() {
return allowSessionsType;
}
public static AllowSessionsEnum fromString(String alertAllowSessionType) {
if (alertAllowSessionType == null) {
return AllowSessionsEnum.ALLOW_ALL_SESSIONS;
}
switch (alertAllowSessionType) {
case "all":
return AllowSessionsEnum.ALLOW_ALL_SESSIONS;
case "current":
return AllowSessionsEnum.ALLOW_CURRENT_SESSIONS_ONLY;
case "admin" :
return AllowSessionsEnum.ALLOW_ADMIN_SESSIONS_ONLY;
default:
throw new IllegalArgumentException("No corresponding enum value for provided string: "
+ alertAllowSessionType);
}
}
}

View File

@@ -0,0 +1,179 @@
/**
* 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.alerts;
import java.util.Date;
import javax.persistence.Cacheable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.dspace.core.ReloadableEntity;
import org.hibernate.annotations.CacheConcurrencyStrategy;
/**
* Database object representing system-wide alerts
*/
@Entity
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE, include = "non-lazy")
@Table(name = "systemwidealert")
public class SystemWideAlert implements ReloadableEntity<Integer> {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "alert_id_seq")
@SequenceGenerator(name = "alert_id_seq", sequenceName = "alert_id_seq", allocationSize = 1)
@Column(name = "alert_id", unique = true, nullable = false)
private Integer alertId;
@Column(name = "message", nullable = false)
private String message;
@Column(name = "allow_sessions")
private String allowSessions;
@Column(name = "countdown_to")
@Temporal(TemporalType.TIMESTAMP)
private Date countdownTo;
@Column(name = "active")
private boolean active;
protected SystemWideAlert() {
}
/**
* This method returns the ID that the system-wide alert holds within the database
*
* @return The ID that the system-wide alert holds within the database
*/
@Override
public Integer getID() {
return alertId;
}
/**
* Set the ID for the system-wide alert
*
* @param alertID The ID to set
*/
public void setID(final Integer alertID) {
this.alertId = alertID;
}
/**
* Retrieve the message of the system-wide alert
*
* @return the message of the system-wide alert
*/
public String getMessage() {
return message;
}
/**
* Set the message of the system-wide alert
*
* @param message The message to set
*/
public void setMessage(final String message) {
this.message = message;
}
/**
* Retrieve what kind of sessions are allowed while the system-wide alert is active
*
* @return what kind of sessions are allowed while the system-wide alert is active
*/
public AllowSessionsEnum getAllowSessions() {
return AllowSessionsEnum.fromString(allowSessions);
}
/**
* Set what kind of sessions are allowed while the system-wide alert is active
*
* @param allowSessions Integer representing what kind of sessions are allowed
*/
public void setAllowSessions(AllowSessionsEnum allowSessions) {
this.allowSessions = allowSessions.getValue();
}
/**
* Retrieve the date to which will be count down when the system-wide alert is active
*
* @return the date to which will be count down when the system-wide alert is active
*/
public Date getCountdownTo() {
return countdownTo;
}
/**
* Set the date to which will be count down when the system-wide alert is active
*
* @param countdownTo The date to which will be count down
*/
public void setCountdownTo(final Date countdownTo) {
this.countdownTo = countdownTo;
}
/**
* Retrieve whether the system-wide alert is active
*
* @return whether the system-wide alert is active
*/
public boolean isActive() {
return active;
}
/**
* Set whether the system-wide alert is active
*
* @param active Whether the system-wide alert is active
*/
public void setActive(final boolean active) {
this.active = active;
}
/**
* Return <code>true</code> if <code>other</code> is the same SystemWideAlert
* as this object, <code>false</code> otherwise
*
* @param other object to compare to
* @return <code>true</code> if object passed in represents the same
* system-wide alert as this object
*/
@Override
public boolean equals(Object other) {
return (other instanceof SystemWideAlert &&
new EqualsBuilder().append(this.getID(), ((SystemWideAlert) other).getID())
.append(this.getMessage(), ((SystemWideAlert) other).getMessage())
.append(this.getAllowSessions(), ((SystemWideAlert) other).getAllowSessions())
.append(this.getCountdownTo(), ((SystemWideAlert) other).getCountdownTo())
.append(this.isActive(), ((SystemWideAlert) other).isActive())
.isEquals());
}
@Override
public int hashCode() {
return new HashCodeBuilder(17, 37)
.append(this.getID())
.append(this.getMessage())
.append(this.getAllowSessions())
.append(this.getCountdownTo())
.append(this.isActive())
.toHashCode();
}
}

View File

@@ -0,0 +1,129 @@
/**
* 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.alerts;
import java.io.IOException;
import java.sql.SQLException;
import java.util.Date;
import java.util.List;
import org.apache.logging.log4j.Logger;
import org.dspace.alerts.dao.SystemWideAlertDAO;
import org.dspace.alerts.service.SystemWideAlertService;
import org.dspace.authorize.AuthorizeException;
import org.dspace.authorize.service.AuthorizeService;
import org.dspace.core.Context;
import org.dspace.core.LogHelper;
import org.dspace.eperson.EPerson;
import org.springframework.beans.factory.annotation.Autowired;
/**
* The implementation for the {@link SystemWideAlertService} class
*/
public class SystemWideAlertServiceImpl implements SystemWideAlertService {
private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(SystemWideAlertService.class);
@Autowired
private SystemWideAlertDAO systemWideAlertDAO;
@Autowired
private AuthorizeService authorizeService;
@Override
public SystemWideAlert create(final Context context, final String message,
final AllowSessionsEnum allowSessionsType,
final Date countdownTo, final boolean active) throws SQLException,
AuthorizeException {
if (!authorizeService.isAdmin(context)) {
throw new AuthorizeException(
"Only administrators can create a system-wide alert");
}
SystemWideAlert systemWideAlert = new SystemWideAlert();
systemWideAlert.setMessage(message);
systemWideAlert.setAllowSessions(allowSessionsType);
systemWideAlert.setCountdownTo(countdownTo);
systemWideAlert.setActive(active);
SystemWideAlert createdAlert = systemWideAlertDAO.create(context, systemWideAlert);
log.info(LogHelper.getHeader(context, "system_wide_alert_create",
"System Wide Alert has been created with message: '" + message + "' and ID "
+ createdAlert.getID() + " and allowSessionsType " + allowSessionsType +
" and active set to " + active));
return createdAlert;
}
@Override
public SystemWideAlert find(final Context context, final int alertId) throws SQLException {
return systemWideAlertDAO.findByID(context, SystemWideAlert.class, alertId);
}
@Override
public List<SystemWideAlert> findAll(final Context context) throws SQLException {
return systemWideAlertDAO.findAll(context, SystemWideAlert.class);
}
@Override
public List<SystemWideAlert> findAll(final Context context, final int limit, final int offset) throws SQLException {
return systemWideAlertDAO.findAll(context, limit, offset);
}
@Override
public List<SystemWideAlert> findAllActive(final Context context, final int limit, final int offset)
throws SQLException {
return systemWideAlertDAO.findAllActive(context, limit, offset);
}
@Override
public void delete(final Context context, final SystemWideAlert systemWideAlert)
throws SQLException, IOException, AuthorizeException {
if (!authorizeService.isAdmin(context)) {
throw new AuthorizeException(
"Only administrators can create a system-wide alert");
}
systemWideAlertDAO.delete(context, systemWideAlert);
log.info(LogHelper.getHeader(context, "system_wide_alert_create",
"System Wide Alert with ID " + systemWideAlert.getID() + " has been deleted"));
}
@Override
public void update(final Context context, final SystemWideAlert systemWideAlert)
throws SQLException, AuthorizeException {
if (!authorizeService.isAdmin(context)) {
throw new AuthorizeException(
"Only administrators can create a system-wide alert");
}
systemWideAlertDAO.save(context, systemWideAlert);
}
@Override
public boolean canNonAdminUserLogin(Context context) throws SQLException {
List<SystemWideAlert> active = findAllActive(context, 1, 0);
if (active == null || active.isEmpty()) {
return true;
}
return active.get(0).getAllowSessions() == AllowSessionsEnum.ALLOW_ALL_SESSIONS;
}
@Override
public boolean canUserMaintainSession(Context context, EPerson ePerson) throws SQLException {
if (authorizeService.isAdmin(context, ePerson)) {
return true;
}
List<SystemWideAlert> active = findAllActive(context, 1, 0);
if (active == null || active.isEmpty()) {
return true;
}
return active.get(0).getAllowSessions() != AllowSessionsEnum.ALLOW_ADMIN_SESSIONS_ONLY;
}
}

View File

@@ -0,0 +1,45 @@
/**
* 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.alerts.dao;
import java.sql.SQLException;
import java.util.List;
import org.dspace.alerts.SystemWideAlert;
import org.dspace.core.Context;
import org.dspace.core.GenericDAO;
/**
* This is the Data Access Object for the {@link SystemWideAlert} object
*/
public interface SystemWideAlertDAO extends GenericDAO<SystemWideAlert> {
/**
* Returns a list of all SystemWideAlert objects in the database
*
* @param context The relevant DSpace context
* @param limit The limit for the amount of SystemWideAlerts returned
* @param offset The offset for the Processes to be returned
* @return The list of all SystemWideAlert objects in the Database
* @throws SQLException If something goes wrong
*/
List<SystemWideAlert> findAll(Context context, int limit, int offset) throws SQLException;
/**
* Returns a list of all active SystemWideAlert objects in the database
*
* @param context The relevant DSpace context
* @param limit The limit for the amount of SystemWideAlerts returned
* @param offset The offset for the Processes to be returned
* @return The list of all SystemWideAlert objects in the Database
* @throws SQLException If something goes wrong
*/
List<SystemWideAlert> findAllActive(Context context, int limit, int offset) throws SQLException;
}

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.alerts.dao.impl;
import java.sql.SQLException;
import java.util.List;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root;
import org.dspace.alerts.SystemWideAlert;
import org.dspace.alerts.SystemWideAlert_;
import org.dspace.alerts.dao.SystemWideAlertDAO;
import org.dspace.core.AbstractHibernateDAO;
import org.dspace.core.Context;
/**
* Implementation class for the {@link SystemWideAlertDAO}
*/
public class SystemWideAlertDAOImpl extends AbstractHibernateDAO<SystemWideAlert> implements SystemWideAlertDAO {
public List<SystemWideAlert> findAll(final Context context, final int limit, final int offset) throws SQLException {
CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context);
CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, SystemWideAlert.class);
Root<SystemWideAlert> alertRoot = criteriaQuery.from(SystemWideAlert.class);
criteriaQuery.select(alertRoot);
return list(context, criteriaQuery, false, SystemWideAlert.class, limit, offset);
}
public List<SystemWideAlert> findAllActive(final Context context, final int limit, final int offset)
throws SQLException {
CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context);
CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, SystemWideAlert.class);
Root<SystemWideAlert> alertRoot = criteriaQuery.from(SystemWideAlert.class);
criteriaQuery.select(alertRoot);
criteriaQuery.where(criteriaBuilder.equal(alertRoot.get(SystemWideAlert_.active), true));
return list(context, criteriaQuery, false, SystemWideAlert.class, limit, offset);
}
}

View File

@@ -0,0 +1,118 @@
/**
* 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.alerts.service;
import java.io.IOException;
import java.sql.SQLException;
import java.util.Date;
import java.util.List;
import org.dspace.alerts.AllowSessionsEnum;
import org.dspace.alerts.SystemWideAlert;
import org.dspace.authorize.AuthorizeException;
import org.dspace.core.Context;
import org.dspace.eperson.EPerson;
/**
* An interface for the SystemWideAlertService with methods regarding the SystemWideAlert workload
*/
public interface SystemWideAlertService {
/**
* This method will create a SystemWideAlert object in the database
*
* @param context The relevant DSpace context
* @param message The message of the system-wide alert
* @param allowSessionsType Which sessions need to be allowed for the system-wide alert
* @param countdownTo The date to which to count down to when the system-wide alert is active
* @param active Whether the system-wide alert os active
* @return The created SystemWideAlert object
* @throws SQLException If something goes wrong
*/
SystemWideAlert create(Context context, String message, AllowSessionsEnum allowSessionsType,
Date countdownTo, boolean active
) throws SQLException, AuthorizeException;
/**
* This method will retrieve a SystemWideAlert object from the Database with the given ID
*
* @param context The relevant DSpace context
* @param alertId The alert id on which we'll search for in the database
* @return The system-wide alert that holds the given alert id
* @throws SQLException If something goes wrong
*/
SystemWideAlert find(Context context, int alertId) throws SQLException;
/**
* Returns a list of all SystemWideAlert objects in the database
*
* @param context The relevant DSpace context
* @return The list of all SystemWideAlert objects in the Database
* @throws SQLException If something goes wrong
*/
List<SystemWideAlert> findAll(Context context) throws SQLException;
/**
* Returns a list of all SystemWideAlert objects in the database
*
* @param context The relevant DSpace context
* @param limit The limit for the amount of system-wide alerts returned
* @param offset The offset for the system-wide alerts to be returned
* @return The list of all SystemWideAlert objects in the Database
* @throws SQLException If something goes wrong
*/
List<SystemWideAlert> findAll(Context context, int limit, int offset) throws SQLException;
/**
* Returns a list of all active SystemWideAlert objects in the database
*
* @param context The relevant DSpace context
* @return The list of all active SystemWideAlert objects in the database
* @throws SQLException If something goes wrong
*/
List<SystemWideAlert> findAllActive(Context context, int limit, int offset) throws SQLException;
/**
* This method will delete the given SystemWideAlert object from the database
*
* @param context The relevant DSpace context
* @param systemWideAlert The SystemWideAlert object to be deleted
* @throws SQLException If something goes wrong
*/
void delete(Context context, SystemWideAlert systemWideAlert)
throws SQLException, IOException, AuthorizeException;
/**
* This method will be used to update the given SystemWideAlert object in the database
*
* @param context The relevant DSpace context
* @param systemWideAlert The SystemWideAlert object to be updated
* @throws SQLException If something goes wrong
*/
void update(Context context, SystemWideAlert systemWideAlert) throws SQLException, AuthorizeException;
/**
* Verifies if the user connected to the current context can retain its session
*
* @param context The relevant DSpace context
* @return if the user connected to the current context can retain its session
*/
boolean canUserMaintainSession(Context context, EPerson ePerson) throws SQLException;
/**
* Verifies if a non admin user can log in
*
* @param context The relevant DSpace context
* @return if a non admin user can log in
*/
boolean canNonAdminUserLogin(Context context) throws SQLException;
}

View File

@@ -736,7 +736,7 @@ public class CollectionServiceImpl extends DSpaceObjectServiceImpl<Collection> i
collection.getID(), collection.getHandle(), getIdentifiers(context, collection)));
// remove subscriptions - hmm, should this be in Subscription.java?
subscribeService.deleteByCollection(context, collection);
subscribeService.deleteByDspaceObject(context, collection);
// Remove Template Item
removeTemplateItem(context, collection);

View File

@@ -36,6 +36,7 @@ import org.dspace.core.I18nUtil;
import org.dspace.core.LogHelper;
import org.dspace.eperson.Group;
import org.dspace.eperson.service.GroupService;
import org.dspace.eperson.service.SubscribeService;
import org.dspace.event.Event;
import org.dspace.identifier.IdentifierException;
import org.dspace.identifier.service.IdentifierService;
@@ -73,7 +74,8 @@ public class CommunityServiceImpl extends DSpaceObjectServiceImpl<Community> imp
protected SiteService siteService;
@Autowired(required = true)
protected IdentifierService identifierService;
@Autowired(required = true)
protected SubscribeService subscribeService;
protected CommunityServiceImpl() {
super();
@@ -217,12 +219,12 @@ public class CommunityServiceImpl extends DSpaceObjectServiceImpl<Community> imp
@Override
public Bitstream setLogo(Context context, Community community, InputStream is)
throws AuthorizeException, IOException, SQLException {
throws AuthorizeException, IOException, SQLException {
// Check authorisation
// authorized to remove the logo when DELETE rights
// authorized when canEdit
if (!((is == null) && authorizeService.authorizeActionBoolean(
context, community, Constants.DELETE))) {
context, community, Constants.DELETE))) {
canEdit(context, community);
}
@@ -242,7 +244,7 @@ public class CommunityServiceImpl extends DSpaceObjectServiceImpl<Community> imp
// now create policy for logo bitstream
// to match our READ policy
List<ResourcePolicy> policies = authorizeService
.getPoliciesActionFilter(context, community, Constants.READ);
.getPoliciesActionFilter(context, community, Constants.READ);
authorizeService.addPolicies(context, policies, newLogo);
log.info(LogHelper.getHeader(context, "set_logo",
@@ -549,6 +551,8 @@ public class CommunityServiceImpl extends DSpaceObjectServiceImpl<Community> imp
context.addEvent(new Event(Event.DELETE, Constants.COMMUNITY, community.getID(), community.getHandle(),
getIdentifiers(context, community)));
subscribeService.deleteByDspaceObject(context, community);
// Remove collections
Iterator<Collection> collections = community.getCollections().iterator();

View File

@@ -10,9 +10,14 @@ package org.dspace.content;
import java.io.IOException;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.dspace.authorize.AuthorizeException;
import org.dspace.content.factory.ContentServiceFactory;
import org.dspace.content.logic.Filter;
import org.dspace.content.logic.FilterUtils;
import org.dspace.content.service.CollectionService;
import org.dspace.content.service.InstallItemService;
import org.dspace.content.service.ItemService;
@@ -20,6 +25,7 @@ import org.dspace.core.Constants;
import org.dspace.core.Context;
import org.dspace.embargo.service.EmbargoService;
import org.dspace.event.Event;
import org.dspace.identifier.Identifier;
import org.dspace.identifier.IdentifierException;
import org.dspace.identifier.service.IdentifierService;
import org.springframework.beans.factory.annotation.Autowired;
@@ -42,9 +48,11 @@ public class InstallItemServiceImpl implements InstallItemService {
protected IdentifierService identifierService;
@Autowired(required = true)
protected ItemService itemService;
@Autowired(required = false)
Logger log = LogManager.getLogger(InstallItemServiceImpl.class);
protected InstallItemServiceImpl() {
}
@Override
@@ -59,10 +67,14 @@ public class InstallItemServiceImpl implements InstallItemService {
AuthorizeException {
Item item = is.getItem();
Collection collection = is.getCollection();
// Get map of filters to use for identifier types.
Map<Class<? extends Identifier>, Filter> filters = FilterUtils.getIdentifierFilters(false);
try {
if (suppliedHandle == null) {
identifierService.register(c, item);
// Register with the filters we've set up
identifierService.register(c, item, filters);
} else {
// This will register the handle but a pending DOI won't be compatible and so won't be registered
identifierService.register(c, item, suppliedHandle);
}
} catch (IdentifierException e) {

View File

@@ -61,6 +61,7 @@ import org.dspace.discovery.indexobject.IndexableItem;
import org.dspace.eperson.EPerson;
import org.dspace.eperson.Group;
import org.dspace.eperson.service.GroupService;
import org.dspace.eperson.service.SubscribeService;
import org.dspace.event.Event;
import org.dspace.harvest.HarvestedItem;
import org.dspace.harvest.service.HarvestedItemService;
@@ -163,6 +164,9 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl<Item> implements It
@Autowired(required = true)
private RequestItemService requestItemService;
@Autowired(required = true)
protected SubscribeService subscribeService;
protected ItemServiceImpl() {
super();
}
@@ -769,7 +773,8 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl<Item> implements It
log.info(LogHelper.getHeader(context, "delete_item", "item_id="
+ item.getID()));
//remove subscription related with it
subscribeService.deleteByDspaceObject(context, item);
// Remove relationships
for (Relationship relationship : relationshipService.findByItem(context, item, -1, -1, false, false)) {
relationshipService.forceDelete(context, relationship, false, false);

View File

@@ -24,6 +24,8 @@ import org.dspace.authorize.AuthorizeException;
import org.dspace.authorize.ResourcePolicy;
import org.dspace.authorize.service.AuthorizeService;
import org.dspace.content.dao.WorkspaceItemDAO;
import org.dspace.content.logic.Filter;
import org.dspace.content.logic.FilterUtils;
import org.dspace.content.service.CollectionService;
import org.dspace.content.service.ItemService;
import org.dspace.content.service.WorkspaceItemService;
@@ -32,6 +34,13 @@ import org.dspace.core.Context;
import org.dspace.core.LogHelper;
import org.dspace.eperson.EPerson;
import org.dspace.event.Event;
import org.dspace.identifier.DOI;
import org.dspace.identifier.DOIIdentifierProvider;
import org.dspace.identifier.Identifier;
import org.dspace.identifier.IdentifierException;
import org.dspace.identifier.factory.IdentifierServiceFactory;
import org.dspace.identifier.service.DOIService;
import org.dspace.services.factory.DSpaceServicesFactory;
import org.dspace.workflow.WorkflowItem;
import org.dspace.workflow.WorkflowService;
import org.springframework.beans.factory.annotation.Autowired;
@@ -58,6 +67,8 @@ public class WorkspaceItemServiceImpl implements WorkspaceItemService {
protected ItemService itemService;
@Autowired(required = true)
protected WorkflowService workflowService;
@Autowired(required = true)
protected DOIService doiService;
protected WorkspaceItemServiceImpl() {
@@ -160,6 +171,26 @@ public class WorkspaceItemServiceImpl implements WorkspaceItemService {
}
itemService.update(context, item);
// If configured, register identifiers (eg handle, DOI) now. This is typically used with the Show Identifiers
// submission step which previews minted handles and DOIs during the submission process. Default: false
if (DSpaceServicesFactory.getInstance().getConfigurationService()
.getBooleanProperty("identifiers.submission.register", false)) {
try {
// Get map of filters to use for identifier types, while the item is in progress
Map<Class<? extends Identifier>, Filter> filters = FilterUtils.getIdentifierFilters(true);
IdentifierServiceFactory.getInstance().getIdentifierService().register(context, item, filters);
// Look for a DOI and move it to PENDING
DOI doi = doiService.findDOIByDSpaceObject(context, item);
if (doi != null) {
doi.setStatus(DOIIdentifierProvider.PENDING);
doiService.update(context, doi);
}
} catch (IdentifierException e) {
log.error("Could not register identifier(s) for item {}: {}", item.getID(), e.getMessage());
}
}
workspaceItem.setItem(item);
log.info(LogHelper.getHeader(context, "create_workspace_item",

View File

@@ -0,0 +1,80 @@
/**
* 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.content.crosswalk;
import static org.dspace.content.Item.ANY;
import java.io.OutputStream;
import java.io.PrintStream;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import org.dspace.content.DSpaceObject;
import org.dspace.content.Item;
import org.dspace.content.service.ItemService;
import org.dspace.core.Constants;
import org.dspace.core.Context;
import org.dspace.handle.factory.HandleServiceFactory;
import org.springframework.beans.factory.annotation.Autowired;
/**
* Creates a String to be sent as email body for subscriptions
*
* @author Alba Aliu
*/
public class SubscriptionDsoMetadataForEmailCompose implements StreamDisseminationCrosswalk {
private List<String> metadata = new ArrayList<>();
@Autowired
private ItemService itemService;
@Override
public boolean canDisseminate(Context context, DSpaceObject dso) {
return Objects.nonNull(dso) && dso.getType() == Constants.ITEM;
}
@Override
public void disseminate(Context context, DSpaceObject dso, OutputStream out) throws SQLException {
if (dso.getType() == Constants.ITEM) {
Item item = (Item) dso;
PrintStream printStream = new PrintStream(out);
for (String actualMetadata : metadata) {
String[] splitted = actualMetadata.split("\\.");
String qualifier = null;
if (splitted.length == 1) {
qualifier = splitted[2];
}
var metadataValue = itemService.getMetadataFirstValue(item, splitted[0], splitted[1], qualifier, ANY);
printStream.print(metadataValue + " ");
}
String itemURL = HandleServiceFactory.getInstance()
.getHandleService()
.resolveToURL(context, item.getHandle());
printStream.print(itemURL);
printStream.print("\n");
printStream.close();
}
}
@Override
public String getMIMEType() {
return "text/plain";
}
public List<String> getMetadata() {
return metadata;
}
public void setMetadata(List<String> metadata) {
this.metadata = metadata;
}
}

View File

@@ -33,6 +33,7 @@ import org.dspace.content.service.RelationshipTypeService;
import org.dspace.content.service.SiteService;
import org.dspace.content.service.SupervisedItemService;
import org.dspace.content.service.WorkspaceItemService;
import org.dspace.eperson.service.SubscribeService;
import org.dspace.services.factory.DSpaceServicesFactory;
import org.dspace.workflow.factory.WorkflowServiceFactory;
@@ -75,6 +76,8 @@ public abstract class ContentServiceFactory {
public abstract SiteService getSiteService();
public abstract SubscribeService getSubscribeService();
/**
* Return the implementation of the RelationshipTypeService interface
*
@@ -114,11 +117,7 @@ public abstract class ContentServiceFactory {
}
public <T extends DSpaceObject> DSpaceObjectService<T> getDSpaceObjectService(T dso) {
// No need to worry when supressing, as long as our "getDSpaceObjectManager" method is properly implemented
// no casting issues should occur
@SuppressWarnings("unchecked")
DSpaceObjectService<T> manager = getDSpaceObjectService(dso.getType());
return manager;
return getDSpaceObjectService(dso.getType());
}
@SuppressWarnings("unchecked")

View File

@@ -30,6 +30,7 @@ import org.dspace.content.service.RelationshipTypeService;
import org.dspace.content.service.SiteService;
import org.dspace.content.service.SupervisedItemService;
import org.dspace.content.service.WorkspaceItemService;
import org.dspace.eperson.service.SubscribeService;
import org.springframework.beans.factory.annotation.Autowired;
/**
@@ -71,7 +72,8 @@ public class ContentServiceFactoryImpl extends ContentServiceFactory {
private SupervisedItemService supervisedItemService;
@Autowired(required = true)
private SiteService siteService;
@Autowired(required = true)
private SubscribeService subscribeService;
@Autowired(required = true)
private RelationshipService relationshipService;
@Autowired(required = true)
@@ -158,6 +160,11 @@ public class ContentServiceFactoryImpl extends ContentServiceFactory {
return siteService;
}
@Override
public SubscribeService getSubscribeService() {
return subscribeService ;
}
@Override
public RelationshipTypeService getRelationshipTypeService() {
return relationshipTypeService;

View File

@@ -18,10 +18,10 @@ import org.dspace.core.Context;
* statement as a property (unlike an operator) and takes no parameters (unlike a condition)
*
* @author Kim Shepherd
* @version $Revision$
*/
public class DefaultFilter implements Filter {
private LogicalStatement statement;
private String name;
private final static Logger log = LogManager.getLogger();
/**
@@ -44,4 +44,15 @@ public class DefaultFilter implements Filter {
public boolean getResult(Context context, Item item) throws LogicalStatementException {
return this.statement.getResult(context, item);
}
@Override
public void setBeanName(String name) {
log.debug("Initialize bean " + name);
this.name = name;
}
@Override
public String getName() {
return name;
}
}

View File

@@ -9,6 +9,7 @@ package org.dspace.content.logic;
import org.dspace.content.Item;
import org.dspace.core.Context;
import org.springframework.beans.factory.BeanNameAware;
/**
* The interface for Filter currently doesn't add anything to LogicalStatement but inherits from it
@@ -22,7 +23,7 @@ import org.dspace.core.Context;
* @author Kim Shepherd
* @see org.dspace.content.logic.DefaultFilter
*/
public interface Filter extends LogicalStatement {
public interface Filter extends LogicalStatement, BeanNameAware {
/**
* Get the result of logical evaluation for an item
* @param context DSpace context
@@ -32,4 +33,11 @@ public interface Filter extends LogicalStatement {
*/
@Override
boolean getResult(Context context, Item item) throws LogicalStatementException;
/**
* Get the name of a filter. This can be used by filters which make use of BeanNameAware
* to return the bean name.
* @return the id/name of this spring bean
*/
String getName();
}

View File

@@ -0,0 +1,85 @@
/**
* 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.content.logic;
import java.util.HashMap;
import java.util.Map;
import org.dspace.identifier.DOI;
import org.dspace.identifier.Handle;
import org.dspace.identifier.Identifier;
import org.dspace.services.ConfigurationService;
import org.dspace.services.factory.DSpaceServicesFactory;
import org.springframework.beans.factory.annotation.Autowired;
/**
* General utility methods for logical item filtering
*
* @author Kim Shepherd
*/
public class FilterUtils {
@Autowired(required = true)
ConfigurationService configurationService;
/**
* Get a Filter by configuration property name
* For example, if a module has implemented "my-feature.filter" configuration property
* this method will return a filter with the ID specified by the configuration property
* @param property DSpace configuration property name (Apache Commons config)
* @return Filter object, with a bean ID configured for this property key, or null
*/
public static Filter getFilterFromConfiguration(String property) {
String filterName = DSpaceServicesFactory.getInstance().getConfigurationService().getProperty(property);
if (filterName != null) {
return DSpaceServicesFactory.getInstance().getServiceManager().getServiceByName(filterName, Filter.class);
}
return null;
}
/**
* Get a Filter by configuration property name
* For example, if a module has implemented "my-feature.filter" configuration property
* this method will return a filter with the ID specified by the configuration property
* @param property DSpace configuration property name (Apache Commons config)
* @return Filter object, with a bean ID configured for this property key, or default filter
*/
public static Filter getFilterFromConfiguration(String property, Filter defaultFilter) {
Filter filter = getFilterFromConfiguration(property);
if (filter != null) {
return filter;
}
return defaultFilter;
}
/**
* Get a map of identifier types and filters to use when creating workspace or archived items
* This is used by services installing new archived or workspace items to filter by identifier type
* as some filters should apply to DOI creation but not Handle creation, and so on.
* The in progress or archived status will be used to load the appropriate filter from configuration
* <p>
* @param inProgress
* @return
*/
public static Map<Class<? extends Identifier>, Filter> getIdentifierFilters(boolean inProgress) {
String configurationSuffix = "install";
if (inProgress) {
configurationSuffix = "workspace";
}
Map<Class<? extends Identifier>, Filter> filters = new HashMap<>();
// Put DOI 'can we create DOI on install / workspace?' filter
Filter filter = FilterUtils.getFilterFromConfiguration("identifiers.submission.filter." + configurationSuffix);
// A null filter should be handled safely by the identifier provier (default, or "always true")
filters.put(DOI.class, filter);
// This won't have an affect until handle providers implement filtering, but is an example of
// how the filters can be used for other types
filters.put(Handle.class, DSpaceServicesFactory.getInstance().getServiceManager().getServiceByName(
"always_true_filter", TrueFilter.class));
return filters;
}
}

View File

@@ -17,7 +17,6 @@ import org.dspace.core.Context;
* used as sub-statements in other Filters and Operators.
*
* @author Kim Shepherd
* @version $Revision$
*/
public interface LogicalStatement {
/**

View File

@@ -12,7 +12,6 @@ package org.dspace.content.logic;
* defined as spring beans.
*
* @author Kim Shepherd
* @version $Revision$
*/
public class LogicalStatementException extends RuntimeException {

View File

@@ -33,7 +33,6 @@ import org.dspace.services.factory.DSpaceServicesFactory;
* A command-line runner used for testing a logical filter against an item, or all items
*
* @author Kim Shepherd
* @version $Revision$
*/
public class TestLogicRunner {

View File

@@ -0,0 +1,41 @@
/**
* 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.content.logic;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.dspace.content.Item;
import org.dspace.core.Context;
/**
* Extremely simple filter that always returns true!
* Useful to pass to methods that expect a filter, in order to effectively say "all items".
* This could be configured in Spring XML but it is more stable and reliable to have it hard-coded here
* so that any broken configuration doesn't silently break parts of DSpace that expect it to work.
*
* @author Kim Shepherd
*/
public class TrueFilter implements Filter {
private String name;
private final static Logger log = LogManager.getLogger();
public boolean getResult(Context context, Item item) throws LogicalStatementException {
return true;
}
@Override
public void setBeanName(String name) {
log.debug("Initialize bean " + name);
this.name = name;
}
@Override
public String getName() {
return name;
}
}

View File

@@ -23,7 +23,6 @@ import org.springframework.beans.factory.annotation.Autowired;
* Abstract class for conditions, to implement the basic getter and setter parameters
*
* @author Kim Shepherd
* @version $Revision$
*/
public abstract class AbstractCondition implements Condition {

View File

@@ -18,7 +18,6 @@ import org.dspace.core.Context;
* A condition to evaluate an item based on how many bitstreams it has in a particular bundle
*
* @author Kim Shepherd
* @version $Revision$
*/
public class BitstreamCountCondition extends AbstractCondition {
/**

View File

@@ -22,7 +22,6 @@ import org.dspace.core.Context;
* operator is not a condition but also a logical statement.
*
* @author Kim Shepherd
* @version $Revision$
*/
public interface Condition extends LogicalStatement {

View File

@@ -23,7 +23,6 @@ import org.dspace.core.Context;
* if the item belongs to any of them.
*
* @author Kim Shepherd
* @version $Revision$
*/
public class InCollectionCondition extends AbstractCondition {
private static Logger log = LogManager.getLogger(InCollectionCondition.class);

View File

@@ -24,7 +24,6 @@ import org.dspace.core.Context;
* if the item belongs to any of them.
*
* @author Kim Shepherd
* @version $Revision$
*/
public class InCommunityCondition extends AbstractCondition {
private final static Logger log = LogManager.getLogger();

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.content.logic.condition;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.dspace.content.Item;
import org.dspace.content.logic.LogicalStatementException;
import org.dspace.core.Context;
/**
* A condition that returns true if the item is archived
*
* @author Kim Shepherd
*/
public class IsArchivedCondition extends AbstractCondition {
private final static Logger log = LogManager.getLogger();
/**
* Return true if item is archived
* Return false if not
* @param context DSpace context
* @param item Item to evaluate
* @return boolean result of evaluation
* @throws LogicalStatementException
*/
@Override
public boolean getResult(Context context, Item item) throws LogicalStatementException {
log.debug("Result of isArchived is " + item.isArchived());
return item.isArchived();
}
}

View File

@@ -17,7 +17,6 @@ import org.dspace.core.Context;
* A condition that returns true if the item is withdrawn
*
* @author Kim Shepherd
* @version $Revision$
*/
public class IsWithdrawnCondition extends AbstractCondition {
private final static Logger log = LogManager.getLogger();

View File

@@ -23,7 +23,6 @@ import org.dspace.core.Context;
* in a given metadata field
*
* @author Kim Shepherd
* @version $Revision$
*/
public class MetadataValueMatchCondition extends AbstractCondition {

View File

@@ -23,7 +23,6 @@ import org.dspace.core.Context;
* in a given metadata field
*
* @author Kim Shepherd
* @version $Revision$
*/
public class MetadataValuesMatchCondition extends AbstractCondition {

View File

@@ -25,7 +25,6 @@ import org.dspace.core.Context;
* can perform the action on a given item
*
* @author Kim Shepherd
* @version $Revision$
*/
public class ReadableByGroupCondition extends AbstractCondition {
private final static Logger log = LogManager.getLogger();

View File

@@ -22,7 +22,6 @@ import org.dspace.core.Context;
* as a logical result
*
* @author Kim Shepherd
* @version $Revision$
*/
public abstract class AbstractOperator implements LogicalStatement {

View File

@@ -19,7 +19,6 @@ import org.dspace.core.Context;
* true if all sub-statements return true
*
* @author Kim Shepherd
* @version $Revision$
*/
public class And extends AbstractOperator {

View File

@@ -18,7 +18,6 @@ import org.dspace.core.Context;
* An operator that implements NAND by negating an AND operation
*
* @author Kim Shepherd
* @version $Revision$
*/
public class Nand extends AbstractOperator {

View File

@@ -19,7 +19,6 @@ import org.dspace.core.Context;
* Not can have one sub-statement only, while and, or, nor, ... can have multiple sub-statements.
*
* @author Kim Shepherd
* @version $Revision$
*/
public class Not implements LogicalStatement {

View File

@@ -19,7 +19,6 @@ import org.dspace.core.Context;
* true if one or more sub-statements return true
*
* @author Kim Shepherd
* @version $Revision$
*/
public class Or extends AbstractOperator {

View File

@@ -13,11 +13,15 @@ import java.sql.SQLException;
import org.apache.logging.log4j.Logger;
import org.dspace.content.DSpaceObject;
import org.dspace.content.Item;
import org.dspace.content.logic.Filter;
import org.dspace.content.logic.FilterUtils;
import org.dspace.content.logic.TrueFilter;
import org.dspace.curate.AbstractCurationTask;
import org.dspace.curate.Curator;
import org.dspace.identifier.DOIIdentifierProvider;
import org.dspace.identifier.IdentifierException;
import org.dspace.identifier.doi.DOIIdentifierNotApplicableException;
import org.dspace.services.factory.DSpaceServicesFactory;
import org.dspace.utils.DSpace;
/**
@@ -39,6 +43,7 @@ public class RegisterDOI extends AbstractCurationTask {
private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(RegisterDOI.class);
// DOI provider
private DOIIdentifierProvider provider;
private Filter trueFilter;
/**
* Initialise the curation task and read configuration, instantiate the DOI provider
@@ -46,14 +51,14 @@ public class RegisterDOI extends AbstractCurationTask {
@Override
public void init(Curator curator, String taskId) throws IOException {
super.init(curator, taskId);
// Get 'skip filter' behaviour from configuration, with a default value of 'true'
skipFilter = configurationService.getBooleanProperty(PLUGIN_PREFIX + ".skip-filter", true);
// Get distribution behaviour from configuration, with a default value of 'false'
distributed = configurationService.getBooleanProperty(PLUGIN_PREFIX + ".distributed", false);
log.debug("PLUGIN_PREFIX = " + PLUGIN_PREFIX + ", skipFilter = " + skipFilter +
", distributed = " + distributed);
// Instantiate DOI provider singleton
provider = new DSpace().getSingletonService(DOIIdentifierProvider.class);
trueFilter = DSpaceServicesFactory.getInstance().getServiceManager().getServiceByName(
"always_true_filter", TrueFilter.class);
}
/**
@@ -118,8 +123,9 @@ public class RegisterDOI extends AbstractCurationTask {
String doi = null;
// Attempt DOI registration and report successes and failures
try {
log.debug("Registering DOI with skipFilter = " + skipFilter);
doi = provider.register(Curator.curationContext(), item, skipFilter);
Filter filter = FilterUtils.getFilterFromConfiguration("identifiers.submission.filter.curation",
trueFilter);
doi = provider.register(Curator.curationContext(), item, filter);
if (doi != null) {
String message = "New DOI minted in database for item " + item.getHandle() + ": " + doi
+ ". This DOI will be registered online with the DOI provider when the queue is next run";

View File

@@ -139,4 +139,23 @@ public class DiscoveryConfigurationService {
}
}
}
/**
* Retrieves a list of all DiscoveryConfiguration objects where key starts with prefixConfigurationName
*
* @param prefixConfigurationName string as prefix key
*/
public List<DiscoveryConfiguration> getDiscoveryConfigurationWithPrefixName(final String prefixConfigurationName) {
List<DiscoveryConfiguration> discoveryConfigurationList = new ArrayList<>();
if (StringUtils.isNotBlank(prefixConfigurationName)) {
for (String key : map.keySet()) {
if (key.equals(prefixConfigurationName) || key.startsWith(prefixConfigurationName)) {
DiscoveryConfiguration config = map.get(key);
discoveryConfigurationList.add(config);
}
}
}
return discoveryConfigurationList;
}
}

View File

@@ -0,0 +1,16 @@
/**
* 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.discovery.configuration;
/**
* This class extends {@link DiscoveryConfiguration} and add method for set parameters
* to filter query list
*
* @author Danilo Di Nuzzo (danilo.dinuzzo at 4science.it)
*/
public class DiscoveryRelatedItemConfiguration extends DiscoveryConfiguration {}

View File

@@ -0,0 +1,66 @@
/**
* 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.discovery.configuration;
import java.io.Serializable;
import java.text.MessageFormat;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
/**
*
* Extension of {@link DiscoverySortFieldConfiguration} used to configure sorting
* taking advantage of solr function feature.
*
* Order is evaluated by mean of function parameter value and passed in arguments as input.
*
* @author Corrado Lombardi (corrado.lombardi at 4science.it)
*
*/
public class DiscoverySortFunctionConfiguration extends DiscoverySortFieldConfiguration {
public static final String SORT_FUNCTION = "sort_function";
private String function;
private List<String> arguments;
private String id;
public void setFunction(final String function) {
this.function = function;
}
public void setArguments(final List<String> arguments) {
this.arguments = arguments;
}
@Override
public String getType() {
return SORT_FUNCTION;
}
@Override
public String getMetadataField() {
return id;
}
public void setId(final String id) {
this.id = id;
}
/**
* Returns the function to be used by solr to sort result
* @param functionArgs variable arguments to be inserted in function
* @return
*/
public String getFunction(final Serializable... functionArgs) {
final String args = String.join(",", Optional.ofNullable(arguments).orElse(Collections.emptyList()));
final String result = function + "(" + args + ")";
return MessageFormat.format(result, functionArgs);
}
}

View File

@@ -0,0 +1,81 @@
/**
* 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.eperson;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Calendar;
import org.apache.commons.codec.binary.StringUtils;
/**
* This enum holds all the possible frequency types
* that can be used in "subscription-send" script
*
* @author Mykhaylo Boychuk (mykhaylo.boychuk@4science.com)
*/
public enum FrequencyType {
DAY("D"),
WEEK("W"),
MONTH("M");
private String shortName;
private FrequencyType(String shortName) {
this.shortName = shortName;
}
public static String findLastFrequency(String frequency) {
String startDate = "";
String endDate = "";
Calendar cal = Calendar.getInstance();
// Full ISO 8601 is e.g.
SimpleDateFormat fullIsoStart = new SimpleDateFormat("yyyy-MM-dd'T'00:00:00'Z'");
SimpleDateFormat fullIsoEnd = new SimpleDateFormat("yyyy-MM-dd'T'23:59:59'Z'");
switch (frequency) {
case "D":
cal.add(Calendar.DAY_OF_MONTH, -1);
endDate = fullIsoEnd.format(cal.getTime());
startDate = fullIsoStart.format(cal.getTime());
break;
case "M":
int dayOfMonth = cal.get(Calendar.DAY_OF_MONTH);
cal.add(Calendar.DAY_OF_MONTH, -dayOfMonth);
endDate = fullIsoEnd.format(cal.getTime());
cal.add(Calendar.MONTH, -1);
cal.add(Calendar.DAY_OF_MONTH, 1);
startDate = fullIsoStart.format(cal.getTime());
break;
case "W":
cal.add(Calendar.DAY_OF_WEEK, -1);
int dayOfWeek = cal.get(Calendar.DAY_OF_WEEK) - 1;
cal.add(Calendar.DAY_OF_WEEK, -dayOfWeek);
endDate = fullIsoEnd.format(cal.getTime());
cal.add(Calendar.DAY_OF_WEEK, -6);
startDate = fullIsoStart.format(cal.getTime());
break;
default:
return null;
}
return "[" + startDate + " TO " + endDate + "]";
}
public static boolean isSupportedFrequencyType(String value) {
for (FrequencyType ft : Arrays.asList(FrequencyType.values())) {
if (StringUtils.equals(ft.getShortName(), value)) {
return true;
}
}
return false;
}
public String getShortName() {
return shortName;
}
}

View File

@@ -1,432 +0,0 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.eperson;
import java.io.IOException;
import java.sql.SQLException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.ResourceBundle;
import java.util.TimeZone;
import javax.mail.MessagingException;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.Logger;
import org.dspace.content.Collection;
import org.dspace.content.DCDate;
import org.dspace.content.Item;
import org.dspace.content.MetadataSchemaEnum;
import org.dspace.content.MetadataValue;
import org.dspace.content.factory.ContentServiceFactory;
import org.dspace.content.service.ItemService;
import org.dspace.core.Context;
import org.dspace.core.Email;
import org.dspace.core.I18nUtil;
import org.dspace.core.LogHelper;
import org.dspace.eperson.factory.EPersonServiceFactory;
import org.dspace.eperson.service.SubscribeService;
import org.dspace.handle.factory.HandleServiceFactory;
import org.dspace.handle.service.HandleService;
import org.dspace.search.Harvest;
import org.dspace.search.HarvestedItemInfo;
import org.dspace.services.ConfigurationService;
import org.dspace.services.factory.DSpaceServicesFactory;
/**
* CLI tool used for sending new item e-mail alerts to users
*
* @author Robert Tansley
* @version $Revision$
*/
public class SubscribeCLITool {
private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(SubscribeCLITool.class);
private static final HandleService handleService
= HandleServiceFactory.getInstance().getHandleService();
private static final ItemService itemService
= ContentServiceFactory.getInstance().getItemService();
private static final SubscribeService subscribeService
= EPersonServiceFactory.getInstance().getSubscribeService();
private static final ConfigurationService configurationService
= DSpaceServicesFactory.getInstance().getConfigurationService();
/**
* Default constructor
*/
private SubscribeCLITool() { }
/**
* Process subscriptions. This must be invoked only once a day. Messages are
* only sent out when a collection has actually received new items, so that
* people's mailboxes are not clogged with many "no new items" mails.
* <p>
* Yesterday's newly available items are included. If this is run at for
* example midday, any items that have been made available during the
* current day will not be included, but will be included in the next day's
* run.
* <p>
* For example, if today's date is 2002-10-10 (in UTC) items made available
* during 2002-10-09 (UTC) will be included.
*
* @param context The relevant DSpace Context.
* @param test If true, do a "dry run", i.e. don't actually send email, just log the attempt
* @throws SQLException An exception that provides information on a database access error or other errors.
* @throws IOException A general class of exceptions produced by failed or interrupted I/O operations.
*/
public static void processDaily(Context context, boolean test) throws SQLException,
IOException {
// Grab the subscriptions
List<Subscription> subscriptions = subscribeService.findAll(context);
EPerson currentEPerson = null;
List<Collection> collections = null; // List of Collections
// Go through the list collating subscriptions for each e-person
for (Subscription subscription : subscriptions) {
// Does this row relate to the same e-person as the last?
if ((currentEPerson == null)
|| (!subscription.getePerson().getID().equals(currentEPerson
.getID()))) {
// New e-person. Send mail for previous e-person
if (currentEPerson != null) {
try {
sendEmail(context, currentEPerson, collections, test);
} catch (MessagingException me) {
log.error("Failed to send subscription to eperson_id="
+ currentEPerson.getID());
log.error(me);
}
}
currentEPerson = subscription.getePerson();
collections = new ArrayList<>();
}
collections.add(subscription.getCollection());
}
// Process the last person
if (currentEPerson != null) {
try {
sendEmail(context, currentEPerson, collections, test);
} catch (MessagingException me) {
log.error("Failed to send subscription to eperson_id="
+ currentEPerson.getID());
log.error(me);
}
}
}
/**
* Sends an email to the given e-person with details of new items in the
* given collections, items that appeared yesterday. No e-mail is sent if
* there aren't any new items in any of the collections.
*
* @param context DSpace context object
* @param eperson eperson to send to
* @param collections List of collection IDs (Integers)
* @param test If true, do a "dry run", i.e. don't actually send email, just log the attempt
* @throws IOException A general class of exceptions produced by failed or interrupted I/O operations.
* @throws MessagingException A general class of exceptions for sending email.
* @throws SQLException An exception that provides information on a database access error or other errors.
*/
public static void sendEmail(Context context, EPerson eperson,
List<Collection> collections, boolean test) throws IOException, MessagingException,
SQLException {
// Get a resource bundle according to the eperson language preferences
Locale supportedLocale = I18nUtil.getEPersonLocale(eperson);
ResourceBundle labels = ResourceBundle.getBundle("Messages", supportedLocale);
// Get the start and end dates for yesterday
// The date should reflect the timezone as well. Otherwise we stand to lose that information
// in truncation and roll to an earlier date than intended.
Calendar cal = Calendar.getInstance(TimeZone.getDefault());
cal.setTime(new Date());
// What we actually want to pass to Harvest is "Midnight of yesterday in my current timezone"
// Truncation will actually pass in "Midnight of yesterday in UTC", which will be,
// at least in CDT, "7pm, the day before yesterday, in my current timezone".
cal.add(Calendar.HOUR, -24);
cal.set(Calendar.HOUR_OF_DAY, 0);
cal.set(Calendar.MINUTE, 0);
cal.set(Calendar.SECOND, 0);
Date midnightYesterday = cal.getTime();
// FIXME: text of email should be more configurable from an
// i18n viewpoint
StringBuilder emailText = new StringBuilder();
boolean isFirst = true;
for (int i = 0; i < collections.size(); i++) {
Collection c = collections.get(i);
try {
boolean includeAll = configurationService
.getBooleanProperty("harvest.includerestricted.subscription", true);
// we harvest all the changed item from yesterday until now
List<HarvestedItemInfo> itemInfos = Harvest
.harvest(context, c, new DCDate(midnightYesterday).toString(), null, 0, // Limit
// and
// offset
// zero,
// get
// everything
0, true, // Need item objects
false, // But not containers
false, // Or withdrawals
includeAll);
if (configurationService.getBooleanProperty("eperson.subscription.onlynew", false)) {
// get only the items archived yesterday
itemInfos = filterOutModified(itemInfos);
} else {
// strip out the item archived today or
// not archived yesterday and modified today
itemInfos = filterOutToday(itemInfos);
}
// Only add to buffer if there are new items
if (itemInfos.size() > 0) {
if (!isFirst) {
emailText
.append("\n---------------------------------------\n");
} else {
isFirst = false;
}
emailText.append(labels.getString("org.dspace.eperson.Subscribe.new-items")).append(" ").append(
c.getName()).append(": ").append(
itemInfos.size()).append("\n\n");
for (int j = 0; j < itemInfos.size(); j++) {
HarvestedItemInfo hii = (HarvestedItemInfo) itemInfos
.get(j);
String title = hii.item.getName();
emailText.append(" ").append(labels.getString("org.dspace.eperson.Subscribe.title"))
.append(" ");
if (StringUtils.isNotBlank(title)) {
emailText.append(title);
} else {
emailText.append(labels.getString("org.dspace.eperson.Subscribe.untitled"));
}
List<MetadataValue> authors = itemService
.getMetadata(hii.item, MetadataSchemaEnum.DC.getName(), "contributor", Item.ANY, Item.ANY);
if (authors.size() > 0) {
emailText.append("\n ").append(labels.getString("org.dspace.eperson.Subscribe.authors"))
.append(" ").append(
authors.get(0).getValue());
for (int k = 1; k < authors.size(); k++) {
emailText.append("\n ").append(
authors.get(k).getValue());
}
}
emailText.append("\n ").append(labels.getString("org.dspace.eperson.Subscribe.id"))
.append(" ").append(
handleService.getCanonicalForm(hii.handle)).append(
"\n\n");
}
}
} catch (ParseException pe) {
// This should never get thrown as the Dates are auto-generated
}
}
// Send an e-mail if there were any new items
if (emailText.length() > 0) {
if (test) {
log.info(LogHelper.getHeader(context, "subscription:", "eperson=" + eperson.getEmail()));
log.info(LogHelper.getHeader(context, "subscription:", "text=" + emailText.toString()));
} else {
Email email = Email.getEmail(I18nUtil.getEmailFilename(supportedLocale, "subscription"));
email.addRecipient(eperson.getEmail());
email.addArgument(emailText.toString());
email.send();
log.info(LogHelper.getHeader(context, "sent_subscription", "eperson_id=" + eperson.getID()));
}
}
}
/**
* Method for invoking subscriptions via the command line
*
* @param argv the command line arguments given
*/
public static void main(String[] argv) {
String usage = "org.dspace.eperson.Subscribe [-t] or nothing to send out subscriptions.";
Options options = new Options();
HelpFormatter formatter = new HelpFormatter();
CommandLine line = null;
{
Option opt = new Option("t", "test", false, "Run test session");
opt.setRequired(false);
options.addOption(opt);
}
{
Option opt = new Option("h", "help", false, "Print this help message");
opt.setRequired(false);
options.addOption(opt);
}
try {
line = new DefaultParser().parse(options, argv);
} catch (org.apache.commons.cli.ParseException e) {
// automatically generate the help statement
formatter.printHelp(usage, e.getMessage(), options, "");
System.exit(1);
}
if (line.hasOption("h")) {
// automatically generate the help statement
formatter.printHelp(usage, options);
System.exit(1);
}
boolean test = line.hasOption("t");
Context context = null;
try {
context = new Context(Context.Mode.READ_ONLY);
processDaily(context, test);
context.complete();
} catch (IOException | SQLException e) {
log.fatal(e);
} finally {
if (context != null && context.isValid()) {
// Nothing is actually written
context.abort();
}
}
}
private static List<HarvestedItemInfo> filterOutToday(List<HarvestedItemInfo> completeList) {
log.debug("Filtering out all today item to leave new items list size="
+ completeList.size());
List<HarvestedItemInfo> filteredList = new ArrayList<>();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
String today = sdf.format(new Date());
// Get the start and end dates for yesterday
Date thisTimeYesterday = new Date(System.currentTimeMillis()
- (24 * 60 * 60 * 1000));
String yesterday = sdf.format(thisTimeYesterday);
for (HarvestedItemInfo infoObject : completeList) {
Date lastUpdate = infoObject.item.getLastModified();
String lastUpdateStr = sdf.format(lastUpdate);
// has the item modified today?
if (lastUpdateStr.equals(today)) {
List<MetadataValue> dateAccArr = itemService.getMetadata(infoObject.item, "dc",
"date", "accessioned", Item.ANY);
// we need only the item archived yesterday
if (dateAccArr != null && dateAccArr.size() > 0) {
for (MetadataValue date : dateAccArr) {
if (date != null && date.getValue() != null) {
// if it hasn't been archived today
if (date.getValue().startsWith(yesterday)) {
filteredList.add(infoObject);
log.debug("adding : " + dateAccArr.get(0).getValue()
+ " : " + today + " : "
+ infoObject.handle);
break;
} else {
log.debug("ignoring : " + dateAccArr.get(0).getValue()
+ " : " + today + " : "
+ infoObject.handle);
}
}
}
} else {
log.debug("no date accessioned, adding : "
+ infoObject.handle);
filteredList.add(infoObject);
}
} else {
// the item has been modified yesterday...
filteredList.add(infoObject);
}
}
return filteredList;
}
private static List<HarvestedItemInfo> filterOutModified(List<HarvestedItemInfo> completeList) {
log.debug("Filtering out all modified to leave new items list size=" + completeList.size());
List<HarvestedItemInfo> filteredList = new ArrayList<>();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
// Get the start and end dates for yesterday
Date thisTimeYesterday = new Date(System.currentTimeMillis()
- (24 * 60 * 60 * 1000));
String yesterday = sdf.format(thisTimeYesterday);
for (HarvestedItemInfo infoObject : completeList) {
List<MetadataValue> dateAccArr = itemService
.getMetadata(infoObject.item, "dc", "date", "accessioned", Item.ANY);
if (dateAccArr != null && dateAccArr.size() > 0) {
for (MetadataValue date : dateAccArr) {
if (date != null && date.getValue() != null) {
// if it has been archived yesterday
if (date.getValue().startsWith(yesterday)) {
filteredList.add(infoObject);
log.debug("adding : " + dateAccArr.get(0)
.getValue() + " : " + yesterday + " : " + infoObject
.handle);
break;
} else {
log.debug("ignoring : " + dateAccArr.get(0)
.getValue() + " : " + yesterday + " : " + infoObject
.handle);
}
}
}
} else {
log.debug("no date accessioned, adding : " + infoObject.handle);
filteredList.add(infoObject);
}
}
return filteredList;
}
}

View File

@@ -9,11 +9,16 @@ package org.dspace.eperson;
import java.sql.SQLException;
import java.util.List;
import java.util.Objects;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.dspace.authorize.AuthorizeException;
import org.dspace.authorize.service.AuthorizeService;
import org.dspace.content.Collection;
import org.dspace.content.Community;
import org.dspace.content.DSpaceObject;
import org.dspace.content.service.CollectionService;
import org.dspace.core.Constants;
import org.dspace.core.Context;
@@ -29,106 +34,177 @@ import org.springframework.beans.factory.annotation.Autowired;
* @version $Revision$
*/
public class SubscribeServiceImpl implements SubscribeService {
/**
* log4j logger
*/
private Logger log = org.apache.logging.log4j.LogManager.getLogger(SubscribeServiceImpl.class);
private Logger log = LogManager.getLogger(SubscribeServiceImpl.class);
@Autowired(required = true)
protected SubscriptionDAO subscriptionDAO;
private SubscriptionDAO subscriptionDAO;
@Autowired(required = true)
protected AuthorizeService authorizeService;
private AuthorizeService authorizeService;
@Autowired(required = true)
protected CollectionService collectionService;
protected SubscribeServiceImpl() {
}
private CollectionService collectionService;
@Override
public List<Subscription> findAll(Context context) throws SQLException {
return subscriptionDAO.findAllOrderedByEPerson(context);
}
@Override
public void subscribe(Context context, EPerson eperson,
Collection collection) throws SQLException, AuthorizeException {
// Check authorisation. Must be administrator, or the eperson.
if (authorizeService.isAdmin(context)
|| ((context.getCurrentUser() != null) && (context
.getCurrentUser().getID().equals(eperson.getID())))) {
if (!isSubscribed(context, eperson, collection)) {
Subscription subscription = subscriptionDAO.create(context, new Subscription());
subscription.setCollection(collection);
subscription.setePerson(eperson);
}
public List<Subscription> findAll(Context context, String resourceType, Integer limit, Integer offset)
throws Exception {
if (StringUtils.isBlank(resourceType)) {
return subscriptionDAO.findAllOrderedByDSO(context, limit, offset);
} else {
throw new AuthorizeException(
"Only admin or e-person themselves can subscribe");
if (resourceType.equals(Collection.class.getSimpleName()) ||
resourceType.equals(Community.class.getSimpleName())) {
return subscriptionDAO.findAllOrderedByIDAndResourceType(context, resourceType, limit, offset);
} else {
log.error("Resource type must be Collection or Community");
throw new Exception("Resource type must be Collection or Community");
}
}
}
@Override
public void unsubscribe(Context context, EPerson eperson,
Collection collection) throws SQLException, AuthorizeException {
public Subscription subscribe(Context context, EPerson eperson,
DSpaceObject dSpaceObject,
List<SubscriptionParameter> subscriptionParameterList,
String type) throws SQLException, AuthorizeException {
// Check authorisation. Must be administrator, or the eperson.
if (authorizeService.isAdmin(context)
|| ((context.getCurrentUser() != null) && (context
.getCurrentUser().getID().equals(eperson.getID())))) {
if (collection == null) {
|| ((context.getCurrentUser() != null) && (context
.getCurrentUser().getID().equals(eperson.getID())))) {
Subscription newSubscription = subscriptionDAO.create(context, new Subscription());
subscriptionParameterList.forEach(subscriptionParameter ->
newSubscription.addParameter(subscriptionParameter));
newSubscription.setEPerson(eperson);
newSubscription.setDSpaceObject(dSpaceObject);
newSubscription.setSubscriptionType(type);
return newSubscription;
} else {
throw new AuthorizeException("Only admin or e-person themselves can subscribe");
}
}
@Override
public void unsubscribe(Context context, EPerson eperson, DSpaceObject dSpaceObject)
throws SQLException, AuthorizeException {
// Check authorisation. Must be administrator, or the eperson.
if (authorizeService.isAdmin(context)
|| ((context.getCurrentUser() != null) && (context
.getCurrentUser().getID().equals(eperson.getID())))) {
if (dSpaceObject == null) {
// Unsubscribe from all
subscriptionDAO.deleteByEPerson(context, eperson);
} else {
subscriptionDAO.deleteByCollectionAndEPerson(context, collection, eperson);
subscriptionDAO.deleteByDSOAndEPerson(context, dSpaceObject, eperson);
log.info(LogHelper.getHeader(context, "unsubscribe",
"eperson_id=" + eperson.getID() + ",collection_id="
+ collection.getID()));
+ dSpaceObject.getID()));
}
} else {
throw new AuthorizeException(
"Only admin or e-person themselves can unsubscribe");
throw new AuthorizeException("Only admin or e-person themselves can unsubscribe");
}
}
@Override
public List<Subscription> getSubscriptions(Context context, EPerson eperson)
throws SQLException {
return subscriptionDAO.findByEPerson(context, eperson);
public List<Subscription> findSubscriptionsByEPerson(Context context, EPerson eperson, Integer limit,Integer offset)
throws SQLException {
return subscriptionDAO.findByEPerson(context, eperson, limit, offset);
}
@Override
public List<Collection> getAvailableSubscriptions(Context context)
throws SQLException {
return getAvailableSubscriptions(context, null);
public List<Subscription> findSubscriptionsByEPersonAndDso(Context context, EPerson eperson,
DSpaceObject dSpaceObject,
Integer limit, Integer offset) throws SQLException {
return subscriptionDAO.findByEPersonAndDso(context, eperson, dSpaceObject, limit, offset);
}
@Override
public List<Collection> getAvailableSubscriptions(Context context, EPerson eperson)
throws SQLException {
List<Collection> collections;
if (eperson != null) {
public List<Collection> findAvailableSubscriptions(Context context) throws SQLException {
return findAvailableSubscriptions(context, null);
}
@Override
public List<Collection> findAvailableSubscriptions(Context context, EPerson eperson) throws SQLException {
if (Objects.nonNull(eperson)) {
context.setCurrentUser(eperson);
}
collections = collectionService.findAuthorized(context, null, Constants.ADD);
return collections;
return collectionService.findAuthorized(context, null, Constants.ADD);
}
@Override
public boolean isSubscribed(Context context, EPerson eperson,
Collection collection) throws SQLException {
return subscriptionDAO.findByCollectionAndEPerson(context, eperson, collection) != null;
public boolean isSubscribed(Context context, EPerson eperson, DSpaceObject dSpaceObject) throws SQLException {
return subscriptionDAO.findByEPersonAndDso(context, eperson, dSpaceObject, -1, -1) != null;
}
@Override
public void deleteByCollection(Context context, Collection collection) throws SQLException {
subscriptionDAO.deleteByCollection(context, collection);
public void deleteByDspaceObject(Context context, DSpaceObject dSpaceObject) throws SQLException {
subscriptionDAO.deleteByDspaceObject(context, dSpaceObject);
}
@Override
public void deleteByEPerson(Context context, EPerson ePerson) throws SQLException {
subscriptionDAO.deleteByEPerson(context, ePerson);
}
@Override
public Subscription findById(Context context, int id) throws SQLException {
return subscriptionDAO.findByID(context, Subscription.class, id);
}
@Override
public Subscription updateSubscription(Context context, Integer id, String subscriptionType,
List<SubscriptionParameter> subscriptionParameterList)
throws SQLException {
Subscription subscriptionDB = subscriptionDAO.findByID(context, Subscription.class, id);
subscriptionDB.removeParameterList();
subscriptionDB.setSubscriptionType(subscriptionType);
subscriptionParameterList.forEach(x -> subscriptionDB.addParameter(x));
subscriptionDAO.save(context, subscriptionDB);
return subscriptionDB;
}
@Override
public Subscription addSubscriptionParameter(Context context, Integer id, SubscriptionParameter subscriptionParam)
throws SQLException {
Subscription subscriptionDB = subscriptionDAO.findByID(context, Subscription.class, id);
subscriptionDB.addParameter(subscriptionParam);
subscriptionDAO.save(context, subscriptionDB);
return subscriptionDB;
}
@Override
public Subscription removeSubscriptionParameter(Context context,Integer id, SubscriptionParameter subscriptionParam)
throws SQLException {
Subscription subscriptionDB = subscriptionDAO.findByID(context, Subscription.class, id);
subscriptionDB.removeParameter(subscriptionParam);
subscriptionDAO.save(context, subscriptionDB);
return subscriptionDB;
}
@Override
public void deleteSubscription(Context context, Subscription subscription) throws SQLException {
subscriptionDAO.delete(context, subscription);
}
@Override
public List<Subscription> findAllSubscriptionsBySubscriptionTypeAndFrequency(Context context,
String subscriptionType, String frequencyValue) throws SQLException {
return subscriptionDAO.findAllSubscriptionsBySubscriptionTypeAndFrequency(context, subscriptionType,
frequencyValue);
}
@Override
public Long countAll(Context context) throws SQLException {
return subscriptionDAO.countAll(context);
}
@Override
public Long countSubscriptionsByEPerson(Context context, EPerson ePerson) throws SQLException {
return subscriptionDAO.countAllByEPerson(context, ePerson);
}
@Override
public Long countByEPersonAndDSO(Context context, EPerson ePerson, DSpaceObject dSpaceObject)
throws SQLException {
return subscriptionDAO.countAllByEPersonAndDso(context, ePerson, dSpaceObject);
}
}

View File

@@ -7,6 +7,9 @@
*/
package org.dspace.eperson;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
@@ -15,10 +18,11 @@ import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import org.dspace.content.Collection;
import org.dspace.content.DSpaceObject;
import org.dspace.core.Context;
import org.dspace.core.ReloadableEntity;
@@ -37,40 +41,78 @@ public class Subscription implements ReloadableEntity<Integer> {
@SequenceGenerator(name = "subscription_seq", sequenceName = "subscription_seq", allocationSize = 1)
private Integer id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "collection_id")
private Collection collection;
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "dspace_object_id")
private DSpaceObject dSpaceObject;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "eperson_id")
private EPerson ePerson;
/**
* Protected constructor, create object using:
* {@link org.dspace.eperson.service.SubscribeService#subscribe(Context, EPerson, Collection)}
* Represent subscription type, for example, "content" or "statistics".
*
* NOTE: Currently, in DSpace we use only one "content"
*/
protected Subscription() {
@Column(name = "type")
private String subscriptionType;
}
@OneToMany(fetch = FetchType.LAZY, mappedBy = "subscription", cascade = CascadeType.ALL, orphanRemoval = true)
private List<SubscriptionParameter> subscriptionParameterList = new ArrayList<>();
/**
* Protected constructor, create object using:
* {@link org.dspace.eperson.service.SubscribeService#subscribe(Context, EPerson, DSpaceObject, List, String)}
*/
protected Subscription() {}
@Override
public Integer getID() {
return id;
}
public Collection getCollection() {
return collection;
public DSpaceObject getDSpaceObject() {
return this.dSpaceObject;
}
void setCollection(Collection collection) {
this.collection = collection;
void setDSpaceObject(DSpaceObject dSpaceObject) {
this.dSpaceObject = dSpaceObject;
}
public EPerson getePerson() {
public EPerson getEPerson() {
return ePerson;
}
void setePerson(EPerson ePerson) {
public void setEPerson(EPerson ePerson) {
this.ePerson = ePerson;
}
}
public String getSubscriptionType() {
return subscriptionType;
}
public void setSubscriptionType(String subscriptionType) {
this.subscriptionType = subscriptionType;
}
public List<SubscriptionParameter> getSubscriptionParameterList() {
return subscriptionParameterList;
}
public void setSubscriptionParameterList(List<SubscriptionParameter> subscriptionList) {
this.subscriptionParameterList = subscriptionList;
}
public void addParameter(SubscriptionParameter subscriptionParameter) {
subscriptionParameterList.add(subscriptionParameter);
subscriptionParameter.setSubscription(this);
}
public void removeParameterList() {
subscriptionParameterList.clear();
}
public void removeParameter(SubscriptionParameter subscriptionParameter) {
subscriptionParameterList.remove(subscriptionParameter);
}
}

View File

@@ -0,0 +1,98 @@
/**
* 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.eperson;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import org.dspace.core.ReloadableEntity;
/**
* Database entity representation of the subscription_parameter table
* SubscriptionParameter represents a frequency with which an user wants to be notified.
*
* @author Alba Aliu at atis.al
*/
@Entity
@Table(name = "subscription_parameter")
public class SubscriptionParameter implements ReloadableEntity<Integer> {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "subscription_parameter_seq")
@SequenceGenerator(name = "subscription_parameter_seq", sequenceName = "subscription_parameter_seq",
allocationSize = 1)
@Column(name = "subscription_parameter_id", unique = true)
private Integer id;
@ManyToOne
@JoinColumn(name = "subscription_id", nullable = false)
private Subscription subscription;
/*
* Currently, we have only one use case for this attribute: "frequency"
*/
@Column
private String name;
/*
* Currently, we use this attribute only with following values: "D", "W", "M".
* Where D stand for Day, W stand for Week and M stand for Month
*/
@Column
private String value;
public SubscriptionParameter() {}
public SubscriptionParameter(Integer id, Subscription subscription, String name, String value) {
this.id = id;
this.subscription = subscription;
this.name = name;
this.value = value;
}
public Subscription getSubscription() {
return subscription;
}
public void setSubscription(Subscription subscription) {
this.subscription = subscription;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
@Override
public Integer getID() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
}

View File

@@ -10,7 +10,7 @@ package org.dspace.eperson.dao;
import java.sql.SQLException;
import java.util.List;
import org.dspace.content.Collection;
import org.dspace.content.DSpaceObject;
import org.dspace.core.Context;
import org.dspace.core.GenericDAO;
import org.dspace.eperson.EPerson;
@@ -26,17 +26,125 @@ import org.dspace.eperson.Subscription;
*/
public interface SubscriptionDAO extends GenericDAO<Subscription> {
public void deleteByCollection(Context context, Collection collection) throws SQLException;
/**
* Delete all subscription of provided dSpaceObject
*
* @param context DSpace context object
* @param dSpaceObject DSpace resource
* @throws SQLException If database error
*/
public void deleteByDspaceObject(Context context, DSpaceObject dSpaceObject) throws SQLException;
public List<Subscription> findByEPerson(Context context, EPerson eperson) throws SQLException;
/**
* Return a paginated list of all subscriptions of the eperson
*
* @param context DSpace context object
* @param eperson ePerson whose subscriptions want to find
* @param limit Paging limit
* @param offset The position of the first result to return
* @return
* @throws SQLException If database error
*/
public List<Subscription> findByEPerson(Context context, EPerson eperson, Integer limit, Integer offset)
throws SQLException;
public Subscription findByCollectionAndEPerson(Context context, EPerson eperson, Collection collection)
throws SQLException;
/**
* Return a paginated list of subscriptions related to a DSpaceObject belong to an ePerson
*
* @param context DSpace context object
* @param eperson ePerson whose subscriptions want to find
* @param dSpaceObject DSpaceObject of whom subscriptions want to find
* @param limit Paging limit
* @param offset The position of the first result to return
* @return
* @throws SQLException If database error
*/
public List<Subscription> findByEPersonAndDso(Context context, EPerson eperson, DSpaceObject dSpaceObject,
Integer limit, Integer offset) throws SQLException;
/**
* Delete all subscription of provided ePerson
*
* @param context DSpace context object
* @param eperson ePerson whose subscriptions want to delete
* @throws SQLException If database error
*/
public void deleteByEPerson(Context context, EPerson eperson) throws SQLException;
public void deleteByCollectionAndEPerson(Context context, Collection collection, EPerson eperson)
throws SQLException;
/**
* Delete all subscriptions related to a DSpaceObject belong to an ePerson
*
* @param context DSpace context object
* @param dSpaceObject DSpaceObject of whom subscriptions want to delete
* @param eperson ePerson whose subscriptions want to delete
* @throws SQLException If database error
*/
public void deleteByDSOAndEPerson(Context context, DSpaceObject dSpaceObject, EPerson eperson) throws SQLException;
/**
* Return a paginated list of all subscriptions ordered by ID and resourceType
*
* @param context DSpace context object
* @param resourceType Could be Collection or Community
* @param limit Paging limit
* @param offset The position of the first result to return
* @return
* @throws SQLException If database error
*/
public List<Subscription> findAllOrderedByIDAndResourceType(Context context, String resourceType,
Integer limit, Integer offset) throws SQLException;
/**
* Return a paginated list of subscriptions ordered by DSpaceObject
*
* @param context DSpace context object
* @param limit Paging limit
* @param offset The position of the first result to return
* @return
* @throws SQLException If database error
*/
public List<Subscription> findAllOrderedByDSO(Context context, Integer limit, Integer offset) throws SQLException;
/**
* Return a list of all subscriptions by subscriptionType and frequency
*
* @param context DSpace context object
* @param subscriptionType Could be "content" or "statistics". NOTE: in DSpace we have only "content"
* @param frequencyValue Could be "D" stand for Day, "W" stand for Week, and "M" stand for Month
* @return
* @throws SQLException If database error
*/
public List<Subscription> findAllSubscriptionsBySubscriptionTypeAndFrequency(Context context,
String subscriptionType, String frequencyValue) throws SQLException;
/**
* Count all subscriptions
*
* @param context DSpace context object
* @return Total of all subscriptions
* @throws SQLException If database error
*/
public Long countAll(Context context) throws SQLException;
/**
* Count all subscriptions belong to an ePerson
*
* @param context DSpace context object
* @param ePerson ePerson whose subscriptions want count
* @return Total of all subscriptions belong to an ePerson
* @throws SQLException If database error
*/
public Long countAllByEPerson(Context context, EPerson ePerson) throws SQLException;
/**
* Count all subscriptions related to a DSpaceObject belong to an ePerson
*
* @param context DSpace context object
* @param ePerson ePerson whose subscriptions want count
* @param dSpaceObject DSpaceObject of whom subscriptions want count
* @return
* @throws SQLException If database error
*/
public Long countAllByEPersonAndDso(Context context, EPerson ePerson,DSpaceObject dSpaceObject) throws SQLException;
public List<Subscription> findAllOrderedByEPerson(Context context) throws SQLException;
}

View File

@@ -0,0 +1,22 @@
/**
* 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.eperson.dao;
import org.dspace.core.GenericDAO;
import org.dspace.eperson.SubscriptionParameter;
/**
* Database Access Object interface class for the SubscriptionParamter object.
* The implementation of this class is responsible for all database calls for the SubscriptionParameter object and is
* autowired by spring
* This class should only be accessed from a single service and should never be exposed outside of the API
*
* @author Alba Aliu at atis.al
*/
public interface SubscriptionParameterDAO extends GenericDAO<SubscriptionParameter> {
}

View File

@@ -9,17 +9,21 @@ package org.dspace.eperson.dao.impl;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import javax.persistence.Query;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.Root;
import org.dspace.content.Collection;
import org.dspace.content.DSpaceObject;
import org.dspace.core.AbstractHibernateDAO;
import org.dspace.core.Context;
import org.dspace.eperson.EPerson;
import org.dspace.eperson.Subscription;
import org.dspace.eperson.SubscriptionParameter;
import org.dspace.eperson.SubscriptionParameter_;
import org.dspace.eperson.Subscription_;
import org.dspace.eperson.dao.SubscriptionDAO;
@@ -31,42 +35,50 @@ import org.dspace.eperson.dao.SubscriptionDAO;
* @author kevinvandevelde at atmire.com
*/
public class SubscriptionDAOImpl extends AbstractHibernateDAO<Subscription> implements SubscriptionDAO {
protected SubscriptionDAOImpl() {
super();
}
@Override
public List<Subscription> findByEPerson(Context context, EPerson eperson) throws SQLException {
public List<Subscription> findByEPerson(Context context, EPerson eperson, Integer limit, Integer offset)
throws SQLException {
CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context);
javax.persistence.criteria.CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, Subscription.class);
Root<Subscription> subscriptionRoot = criteriaQuery.from(Subscription.class);
criteriaQuery.select(subscriptionRoot);
criteriaQuery.where(criteriaBuilder.equal(subscriptionRoot.get(Subscription_.ePerson), eperson));
return list(context, criteriaQuery, false, Subscription.class, -1, -1);
List<javax.persistence.criteria.Order> orderList = new LinkedList<>();
orderList.add(criteriaBuilder.asc(subscriptionRoot.get(Subscription_.dSpaceObject)));
criteriaQuery.orderBy(orderList);
return list(context, criteriaQuery, false, Subscription.class, limit, offset);
}
@Override
public Subscription findByCollectionAndEPerson(Context context, EPerson eperson, Collection collection)
throws SQLException {
public List<Subscription> findByEPersonAndDso(Context context, EPerson eperson,
DSpaceObject dSpaceObject,
Integer limit, Integer offset) throws SQLException {
CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context);
javax.persistence.criteria.CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, Subscription.class);
javax.persistence.criteria.CriteriaQuery criteriaQuery =
getCriteriaQuery(criteriaBuilder, Subscription.class);
Root<Subscription> subscriptionRoot = criteriaQuery.from(Subscription.class);
criteriaQuery.select(subscriptionRoot);
criteriaQuery
.where(criteriaBuilder.and(criteriaBuilder.equal(subscriptionRoot.get(Subscription_.ePerson), eperson),
criteriaBuilder.equal(subscriptionRoot.get(Subscription_.collection), collection)
)
);
return singleResult(context, criteriaQuery);
criteriaQuery.where(criteriaBuilder.and(criteriaBuilder.equal(
subscriptionRoot.get(Subscription_.ePerson), eperson),
criteriaBuilder.equal(subscriptionRoot.get(Subscription_.dSpaceObject), dSpaceObject)
));
List<javax.persistence.criteria.Order> orderList = new LinkedList<>();
orderList.add(criteriaBuilder.asc(subscriptionRoot.get(Subscription_.dSpaceObject)));
criteriaQuery.orderBy(orderList);
return list(context, criteriaQuery, false, Subscription.class, limit, offset);
}
@Override
public void deleteByCollection(Context context, Collection collection) throws SQLException {
String hqlQuery = "delete from Subscription where collection=:collection";
public void deleteByDspaceObject(Context context, DSpaceObject dSpaceObject) throws SQLException {
String hqlQuery = "delete from Subscription where dSpaceObject=:dSpaceObject";
Query query = createQuery(context, hqlQuery);
query.setParameter("collection", collection);
query.setParameter("dSpaceObject", dSpaceObject);
query.executeUpdate();
}
@@ -79,28 +91,98 @@ public class SubscriptionDAOImpl extends AbstractHibernateDAO<Subscription> impl
}
@Override
public void deleteByCollectionAndEPerson(Context context, Collection collection, EPerson eperson)
throws SQLException {
String hqlQuery = "delete from Subscription where collection=:collection AND ePerson=:ePerson";
public void deleteByDSOAndEPerson(Context context, DSpaceObject dSpaceObject, EPerson eperson)
throws SQLException {
String hqlQuery = "delete from Subscription where dSpaceObject=:dSpaceObject AND ePerson=:ePerson";
Query query = createQuery(context, hqlQuery);
query.setParameter("collection", collection);
query.setParameter("dSpaceObject", dSpaceObject);
query.setParameter("ePerson", eperson);
query.executeUpdate();
}
@Override
public List<Subscription> findAllOrderedByEPerson(Context context) throws SQLException {
public List<Subscription> findAllOrderedByIDAndResourceType(Context context, String resourceType,
Integer limit, Integer offset) throws SQLException {
String hqlQuery = "select s from Subscription s join %s dso " +
"ON dso.id = s.dSpaceObject ORDER BY subscription_id";
if (resourceType != null) {
hqlQuery = String.format(hqlQuery, resourceType);
}
Query query = createQuery(context, hqlQuery);
if (limit != -1) {
query.setMaxResults(limit);
}
if (offset != -1) {
query.setFirstResult(offset);
}
query.setHint("org.hibernate.cacheable", false);
return query.getResultList();
}
@Override
public List<Subscription> findAllOrderedByDSO(Context context, Integer limit, Integer offset) throws SQLException {
CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context);
CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, Subscription.class);
Root<Subscription> subscriptionRoot = criteriaQuery.from(Subscription.class);
criteriaQuery.select(subscriptionRoot);
List<javax.persistence.criteria.Order> orderList = new LinkedList<>();
orderList.add(criteriaBuilder.asc(subscriptionRoot.get(Subscription_.dSpaceObject)));
criteriaQuery.orderBy(orderList);
return list(context, criteriaQuery, false, Subscription.class, limit, offset);
}
@Override
public List<Subscription> findAllSubscriptionsBySubscriptionTypeAndFrequency(Context context,
String subscriptionType, String frequencyValue) throws SQLException {
CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context);
CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, Subscription.class);
Root<Subscription> subscriptionRoot = criteriaQuery.from(Subscription.class);
criteriaQuery.select(subscriptionRoot);
Join<Subscription, SubscriptionParameter> childJoin = subscriptionRoot.join("subscriptionParameterList");
criteriaQuery.where(
criteriaBuilder.and(
criteriaBuilder.equal(subscriptionRoot.get(Subscription_.SUBSCRIPTION_TYPE), subscriptionType),
criteriaBuilder.equal(childJoin.get(SubscriptionParameter_.name), "frequency"),
criteriaBuilder.equal(childJoin.get(SubscriptionParameter_.value), frequencyValue)
));
List<javax.persistence.criteria.Order> orderList = new ArrayList<>(1);
orderList.add(criteriaBuilder.asc(subscriptionRoot.get(Subscription_.ePerson)));
orderList.add(criteriaBuilder.asc(subscriptionRoot.get(Subscription_.id)));
criteriaQuery.orderBy(orderList);
return list(context, criteriaQuery, false, Subscription.class, -1, -1);
return list(context, criteriaQuery, false, Subscription.class, 10000, -1);
}
@Override
public Long countAll(Context context) throws SQLException {
CriteriaBuilder qb = getCriteriaBuilder(context);
CriteriaQuery<Long> cq = qb.createQuery(Long.class);
cq.select(qb.count(cq.from(Subscription.class)));
Query query = this.getHibernateSession(context).createQuery(cq);
return (Long) query.getSingleResult();
}
@Override
public Long countAllByEPerson(Context context, EPerson ePerson) throws SQLException {
CriteriaBuilder qb = getCriteriaBuilder(context);
CriteriaQuery<Long> cq = qb.createQuery(Long.class);
Root<Subscription> subscriptionRoot = cq.from(Subscription.class);
cq.select(qb.count(subscriptionRoot));
cq.where(qb.equal(subscriptionRoot.get(Subscription_.ePerson), ePerson));
Query query = this.getHibernateSession(context).createQuery(cq);
return (Long) query.getSingleResult();
}
@Override
public Long countAllByEPersonAndDso(Context context,
EPerson ePerson, DSpaceObject dSpaceObject) throws SQLException {
CriteriaBuilder qb = getCriteriaBuilder(context);
CriteriaQuery<Long> cq = qb.createQuery(Long.class);
Root<Subscription> subscriptionRoot = cq.from(Subscription.class);
cq.select(qb.count(subscriptionRoot));
cq.where(qb.and(qb.equal(subscriptionRoot.get(Subscription_.ePerson)
, ePerson), qb.equal(subscriptionRoot.get(Subscription_.dSpaceObject), dSpaceObject)));
Query query = this.getHibernateSession(context).createQuery(cq);
return (Long) query.getSingleResult();
}
}

View File

@@ -0,0 +1,28 @@
/**
* 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.eperson.dao.impl;
import org.dspace.core.AbstractHibernateDAO;
import org.dspace.eperson.SubscriptionParameter;
import org.dspace.eperson.dao.SubscriptionParameterDAO;
/**
* Hibernate implementation of the Database Access Object interface class for the SubscriptionParameter object.
* This class is responsible for all database calls for the SubscriptionParameter object and is autowired by spring
* This class should never be accessed directly.
*
* @author Alba Aliu at atis.al
*/
public class SubscriptionParameterDAOImpl extends AbstractHibernateDAO<SubscriptionParameter>
implements SubscriptionParameterDAO {
protected SubscriptionParameterDAOImpl() {
super();
}
}

View File

@@ -12,9 +12,11 @@ import java.util.List;
import org.dspace.authorize.AuthorizeException;
import org.dspace.content.Collection;
import org.dspace.content.DSpaceObject;
import org.dspace.core.Context;
import org.dspace.eperson.EPerson;
import org.dspace.eperson.Subscription;
import org.dspace.eperson.SubscriptionParameter;
/**
* Service interface class for the Subscription object.
@@ -31,49 +33,74 @@ public interface SubscribeService {
* new item appears in the collection.
*
* @param context DSpace context
* @param limit Number of subscriptions to return
* @param offset Offset number
* @return list of Subscription objects
* @throws SQLException An exception that provides information on a database access error or other errors.
*/
public List<Subscription> findAll(Context context) throws SQLException;
public List<Subscription> findAll(Context context, String resourceType, Integer limit, Integer offset)
throws Exception;
/**
* Subscribe an e-person to a collection. An e-mail will be sent every day a
* new item appears in the collection.
*
* @param context DSpace context
* @param eperson EPerson to subscribe
* @param collection Collection to subscribe to
* @throws SQLException An exception that provides information on a database access error or other errors.
* @throws AuthorizeException Exception indicating the current user of the context does not have permission
* to perform a particular action.
* Subscribe an EPerson to a dSpaceObject (Collection or Community). An e-mail will be sent every day a
* new item appears in the Collection or Community.
*
* @param context DSpace context object
* @param eperson EPerson to subscribe
* @param dSpaceObject DSpaceObject to subscribe
* @param subscriptionParameters list of @SubscriptionParameter
* @param subscriptionType Currently supported only "content"
* @return
* @throws SQLException An exception that provides information on a database access error or other errors.
* @throws AuthorizeException Exception indicating the current user of the context does not have permission
* to perform a particular action.
*/
public void subscribe(Context context, EPerson eperson,
Collection collection) throws SQLException, AuthorizeException;
public Subscription subscribe(Context context, EPerson eperson, DSpaceObject dSpaceObject,
List<SubscriptionParameter> subscriptionParameters,
String subscriptionType) throws SQLException, AuthorizeException;
/**
* Unsubscribe an e-person to a collection. Passing in <code>null</code>
* for the collection unsubscribes the e-person from all collections they
* are subscribed to.
*
* @param context DSpace context
* @param eperson EPerson to unsubscribe
* @param collection Collection to unsubscribe from
* @param context DSpace context
* @param eperson EPerson to unsubscribe
* @param dSpaceObject DSpaceObject to unsubscribe from
* @throws SQLException An exception that provides information on a database access error or other errors.
* @throws AuthorizeException Exception indicating the current user of the context does not have permission
* to perform a particular action.
*/
public void unsubscribe(Context context, EPerson eperson,
Collection collection) throws SQLException, AuthorizeException;
public void unsubscribe(Context context, EPerson eperson, DSpaceObject dSpaceObject)
throws SQLException, AuthorizeException;
/**
* Find out which collections an e-person is subscribed to
*
* @param context DSpace context
* @param eperson EPerson
* @param limit Number of subscriptions to return
* @param offset Offset number
* @return array of collections e-person is subscribed to
* @throws SQLException An exception that provides information on a database access error or other errors.
*/
public List<Subscription> getSubscriptions(Context context, EPerson eperson) throws SQLException;
public List<Subscription> findSubscriptionsByEPerson(Context context, EPerson eperson, Integer limit,Integer offset)
throws SQLException;
/**
* Find out which collections an e-person is subscribed to and related with dso
*
* @param context DSpace context
* @param eperson EPerson
* @param dSpaceObject DSpaceObject
* @param limit Number of subscriptions to return
* @param offset Offset number
* @return array of collections e-person is subscribed to and related with dso
* @throws SQLException An exception that provides information on a database access error or other errors.
*/
public List<Subscription> findSubscriptionsByEPersonAndDso(Context context, EPerson eperson,
DSpaceObject dSpaceObject,
Integer limit, Integer offset) throws SQLException;
/**
* Find out which collections the currently logged in e-person can subscribe to
@@ -82,8 +109,7 @@ public interface SubscribeService {
* @return array of collections the currently logged in e-person can subscribe to
* @throws SQLException An exception that provides information on a database access error or other errors.
*/
public List<Collection> getAvailableSubscriptions(Context context)
throws SQLException;
public List<Collection> findAvailableSubscriptions(Context context) throws SQLException;
/**
* Find out which collections an e-person can subscribe to
@@ -93,29 +119,27 @@ public interface SubscribeService {
* @return array of collections e-person can subscribe to
* @throws SQLException An exception that provides information on a database access error or other errors.
*/
public List<Collection> getAvailableSubscriptions(Context context, EPerson eperson)
throws SQLException;
public List<Collection> findAvailableSubscriptions(Context context, EPerson eperson) throws SQLException;
/**
* Is that e-person subscribed to that collection?
*
* @param context DSpace context
* @param eperson find out if this e-person is subscribed
* @param collection find out if subscribed to this collection
* @param context DSpace context
* @param eperson find out if this e-person is subscribed
* @param dSpaceObject find out if subscribed to this dSpaceObject
* @return <code>true</code> if they are subscribed
* @throws SQLException An exception that provides information on a database access error or other errors.
*/
public boolean isSubscribed(Context context, EPerson eperson,
Collection collection) throws SQLException;
public boolean isSubscribed(Context context, EPerson eperson, DSpaceObject dSpaceObject) throws SQLException;
/**
* Delete subscription by collection.
*
* @param context DSpace context
* @param collection find out if subscribed to this collection
* @param context DSpace context
* @param dSpaceObject find out if subscribed to this dSpaceObject
* @throws SQLException An exception that provides information on a database access error or other errors.
*/
public void deleteByCollection(Context context, Collection collection) throws SQLException;
public void deleteByDspaceObject(Context context, DSpaceObject dSpaceObject) throws SQLException;
/**
* Delete subscription by eperson (subscriber).
@@ -125,4 +149,92 @@ public interface SubscribeService {
* @throws SQLException An exception that provides information on a database access error or other errors.
*/
public void deleteByEPerson(Context context, EPerson ePerson) throws SQLException;
}
/**
* Finds a subscription by id
*
* @param context DSpace context
* @param id the id of subscription to be searched
* @throws SQLException An exception that provides information on a database access error or other errors.
*/
public Subscription findById(Context context, int id) throws SQLException;
/**
* Updates a subscription by id
*
* @param context DSpace context
* @param id Integer id
* @param subscriptionParameterList List<SubscriptionParameter> subscriptionParameterList
* @param subscriptionType type
* @throws SQLException An exception that provides information on a database access error or other errors.
*/
public Subscription updateSubscription(Context context, Integer id, String subscriptionType,
List<SubscriptionParameter> subscriptionParameterList) throws SQLException;
/**
* Adds a parameter to a subscription
*
* @param context DSpace context
* @param id Integer id
* @param subscriptionParameter SubscriptionParameter subscriptionParameter
* @throws SQLException An exception that provides information on a database access error or other errors.
*/
public Subscription addSubscriptionParameter(Context context,Integer id,
SubscriptionParameter subscriptionParameter) throws SQLException;
/**
* Deletes a parameter from subscription
*
* @param context DSpace context
* @param id Integer id
* @param subscriptionParam SubscriptionParameter subscriptionParameter
* @throws SQLException An exception that provides information on a database access error or other errors.
*/
public Subscription removeSubscriptionParameter(Context context, Integer id,
SubscriptionParameter subscriptionParam) throws SQLException;
/**
* Deletes a subscription
*
* @param context DSpace context
* @param subscription The subscription to delete
* @throws SQLException An exception that provides information on a database access error or other errors.
*/
public void deleteSubscription(Context context, Subscription subscription) throws SQLException;
/**
* Finds all subscriptions by subscriptionType and frequency
*
* @param context DSpace context
* @param subscriptionType Could be "content" or "statistics". NOTE: in DSpace we have only "content"
* @param frequencyValue Could be "D" stand for Day, "W" stand for Week, and "M" stand for Month
* @throws SQLException An exception that provides information on a database access error or other errors.
*/
public List<Subscription> findAllSubscriptionsBySubscriptionTypeAndFrequency(Context context,
String subscriptionType, String frequencyValue) throws SQLException;
/**
* Counts all subscriptions
*
* @param context DSpace context
*/
public Long countAll(Context context) throws SQLException;
/**
* Counts all subscriptions by ePerson
*
* @param context DSpace context
* @param ePerson EPerson ePerson
*/
public Long countSubscriptionsByEPerson(Context context, EPerson ePerson) throws SQLException;
/**
* Counts all subscriptions by ePerson and DSO
*
* @param context DSpace context
* @param ePerson EPerson ePerson
* @param dSpaceObject DSpaceObject dSpaceObject
*/
public Long countByEPersonAndDSO(Context context, EPerson ePerson, DSpaceObject dSpaceObject) throws SQLException;
}

View File

@@ -21,6 +21,7 @@ import org.dspace.content.MetadataValue;
import org.dspace.content.factory.ContentServiceFactory;
import org.dspace.content.logic.Filter;
import org.dspace.content.logic.LogicalStatementException;
import org.dspace.content.logic.TrueFilter;
import org.dspace.content.service.ItemService;
import org.dspace.core.Constants;
import org.dspace.core.Context;
@@ -28,6 +29,7 @@ import org.dspace.identifier.doi.DOIConnector;
import org.dspace.identifier.doi.DOIIdentifierException;
import org.dspace.identifier.doi.DOIIdentifierNotApplicableException;
import org.dspace.identifier.service.DOIService;
import org.dspace.services.factory.DSpaceServicesFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@@ -44,6 +46,7 @@ import org.springframework.beans.factory.annotation.Autowired;
* <p>Any identifier a method of this class returns is a string in the following format: doi:10.123/456.</p>
*
* @author Pascal-Nicolas Becker
* @author Kim Shepherd
*/
public class DOIIdentifierProvider extends FilteredIdentifierProvider {
private static final Logger log = LoggerFactory.getLogger(DOIIdentifierProvider.class);
@@ -71,16 +74,44 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider {
public static final String MD_SCHEMA = "dc";
public static final String DOI_ELEMENT = "identifier";
public static final String DOI_QUALIFIER = "uri";
// The DOI is queued for registered with the service provider
public static final Integer TO_BE_REGISTERED = 1;
// The DOI is queued for reservation with the service provider
public static final Integer TO_BE_RESERVED = 2;
// The DOI has been registered online
public static final Integer IS_REGISTERED = 3;
// The DOI has been reserved online
public static final Integer IS_RESERVED = 4;
// The DOI is reserved and requires an updated metadata record to be sent to the service provider
public static final Integer UPDATE_RESERVED = 5;
// The DOI is registered and requires an updated metadata record to be sent to the service provider
public static final Integer UPDATE_REGISTERED = 6;
// The DOI metadata record should be updated before performing online registration
public static final Integer UPDATE_BEFORE_REGISTRATION = 7;
// The DOI will be deleted locally and marked as deleted in the DOI service provider
public static final Integer TO_BE_DELETED = 8;
// The DOI has been deleted and is no longer associated with an item
public static final Integer DELETED = 9;
// The DOI is created in the database and is waiting for either successful filter check on item install or
// manual intervention by an administrator to proceed to reservation or registration
public static final Integer PENDING = 10;
// The DOI is created in the database, but no more context is known
public static final Integer MINTED = 11;
public static final String[] statusText = {
"UNKNOWN", // 0
"TO_BE_REGISTERED", // 1
"TO_BE_RESERVED", // 2
"IS_REGISTERED", // 3
"IS_RESERVED", // 4
"UPDATE_RESERVED", // 5
"UPDATE_REGISTERED", // 6
"UPDATE_BEFORE_REGISTRATION", // 7
"TO_BE_DELETED", // 8
"DELETED", // 9
"PENDING", // 10
"MINTED", // 11
};
@Autowired(required = true)
protected DOIService doiService;
@@ -89,8 +120,6 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider {
@Autowired(required = true)
protected ItemService itemService;
protected Filter filterService;
/**
* Empty / default constructor for Spring
*/
@@ -153,16 +182,6 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider {
this.connector = connector;
}
/**
* Set the Filter to use when testing items to see if a DOI should be registered
* Spring will use this setter to set the filter from the configured property in identifier-services.xml
* @param filterService - an object implementing the org.dspace.content.logic.Filter interface
*/
@Override
public void setFilterService(Filter filterService) {
this.filterService = filterService;
}
/**
* This identifier provider supports identifiers of type
* {@link org.dspace.identifier.DOI}.
@@ -206,7 +225,7 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider {
@Override
public String register(Context context, DSpaceObject dso)
throws IdentifierException {
return register(context, dso, false);
return register(context, dso, this.filter);
}
/**
@@ -219,29 +238,29 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider {
@Override
public void register(Context context, DSpaceObject dso, String identifier)
throws IdentifierException {
register(context, dso, identifier, false);
register(context, dso, identifier, this.filter);
}
/**
* Register a new DOI for a given DSpaceObject
* @param context - DSpace context
* @param dso - DSpaceObject identified by the new DOI
* @param skipFilter - boolean indicating whether to skip any filtering of items before performing registration
* @param filter - Logical item filter to determine whether this identifier should be registered
* @throws IdentifierException
*/
@Override
public String register(Context context, DSpaceObject dso, boolean skipFilter)
public String register(Context context, DSpaceObject dso, Filter filter)
throws IdentifierException {
if (!(dso instanceof Item)) {
// DOI are currently assigned only to Item
return null;
}
String doi = mint(context, dso, skipFilter);
String doi = mint(context, dso, filter);
// register tries to reserve doi if it's not already.
// So we don't have to reserve it here.
register(context, dso, doi, skipFilter);
register(context, dso, doi, filter);
return doi;
}
@@ -250,11 +269,11 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider {
* @param context - DSpace context
* @param dso - DSpaceObject identified by the new DOI
* @param identifier - String containing the DOI to register
* @param skipFilter - boolean indicating whether to skip any filtering of items before performing registration
* @param filter - Logical item filter to determine whether this identifier should be registered
* @throws IdentifierException
*/
@Override
public void register(Context context, DSpaceObject dso, String identifier, boolean skipFilter)
public void register(Context context, DSpaceObject dso, String identifier, Filter filter)
throws IdentifierException {
if (!(dso instanceof Item)) {
// DOI are currently assigned only to Item
@@ -265,7 +284,7 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider {
// search DOI in our db
try {
doiRow = loadOrCreateDOI(context, dso, doi, skipFilter);
doiRow = loadOrCreateDOI(context, dso, doi, filter);
} catch (SQLException ex) {
log.error("Error in databse connection: " + ex.getMessage());
throw new RuntimeException("Error in database conncetion.", ex);
@@ -277,7 +296,6 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider {
+ "is marked as DELETED.", DOIIdentifierException.DOI_IS_DELETED);
}
// Check status of DOI
if (IS_REGISTERED.equals(doiRow.getStatus())) {
return;
}
@@ -290,6 +308,7 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider {
log.warn("SQLException while changing status of DOI {} to be registered.", doi);
throw new RuntimeException(sqle);
}
}
/**
@@ -309,7 +328,7 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider {
@Override
public void reserve(Context context, DSpaceObject dso, String identifier)
throws IdentifierException, IllegalArgumentException {
reserve(context, dso, identifier, false);
reserve(context, dso, identifier, this.filter);
}
/**
@@ -317,20 +336,18 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider {
* @param context - DSpace context
* @param dso - DSpaceObject identified by this DOI
* @param identifier - String containing the DOI to reserve
* @param skipFilter - boolean indicating whether to skip any filtering of items before performing reservation
* @param filter - Logical item filter to determine whether this identifier should be reserved
* @throws IdentifierException
* @throws IllegalArgumentException
*/
@Override
public void reserve(Context context, DSpaceObject dso, String identifier, boolean skipFilter)
public void reserve(Context context, DSpaceObject dso, String identifier, Filter filter)
throws IdentifierException, IllegalArgumentException {
String doi = doiService.formatIdentifier(identifier);
DOI doiRow = null;
try {
// if the doi is in our db already loadOrCreateDOI just returns.
// if it is not loadOrCreateDOI safes the doi.
doiRow = loadOrCreateDOI(context, dso, doi, skipFilter);
doiRow = loadOrCreateDOI(context, dso, doi, filter);
} catch (SQLException sqle) {
throw new RuntimeException(sqle);
}
@@ -359,7 +376,7 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider {
*/
public void reserveOnline(Context context, DSpaceObject dso, String identifier)
throws IdentifierException, IllegalArgumentException, SQLException {
reserveOnline(context, dso, identifier, false);
reserveOnline(context, dso, identifier, this.filter);
}
/**
@@ -367,16 +384,16 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider {
* @param context - DSpace context
* @param dso - DSpaceObject identified by this DOI
* @param identifier - String containing the DOI to reserve
* @param skipFilter - skip the filters for {@link checkMintable(Context, DSpaceObject)}
* @param filter - Logical item filter to determine whether this identifier should be reserved online
* @throws IdentifierException
* @throws IllegalArgumentException
* @throws SQLException
*/
public void reserveOnline(Context context, DSpaceObject dso, String identifier, boolean skipFilter)
public void reserveOnline(Context context, DSpaceObject dso, String identifier, Filter filter)
throws IdentifierException, IllegalArgumentException, SQLException {
String doi = doiService.formatIdentifier(identifier);
// get TableRow and ensure DOI belongs to dso regarding our db
DOI doiRow = loadOrCreateDOI(context, dso, doi, skipFilter);
DOI doiRow = loadOrCreateDOI(context, dso, doi, filter);
if (DELETED.equals(doiRow.getStatus()) || TO_BE_DELETED.equals(doiRow.getStatus())) {
throw new DOIIdentifierException("You tried to reserve a DOI that "
@@ -402,7 +419,7 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider {
public void registerOnline(Context context, DSpaceObject dso, String identifier)
throws IdentifierException, IllegalArgumentException, SQLException {
registerOnline(context, dso, identifier, false);
registerOnline(context, dso, identifier, this.filter);
}
@@ -411,18 +428,17 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider {
* @param context - DSpace context
* @param dso - DSpaceObject identified by this DOI
* @param identifier - String containing the DOI to register
* @param skipFilter - skip filters for {@link checkMintable(Context, DSpaceObject)}
* @param filter - Logical item filter to determine whether this identifier should be registered online
* @throws IdentifierException
* @throws IllegalArgumentException
* @throws SQLException
*/
public void registerOnline(Context context, DSpaceObject dso, String identifier, boolean skipFilter)
public void registerOnline(Context context, DSpaceObject dso, String identifier, Filter filter)
throws IdentifierException, IllegalArgumentException, SQLException {
log.debug("registerOnline: skipFilter is " + skipFilter);
String doi = doiService.formatIdentifier(identifier);
// get TableRow and ensure DOI belongs to dso regarding our db
DOI doiRow = loadOrCreateDOI(context, dso, doi, skipFilter);
DOI doiRow = loadOrCreateDOI(context, dso, doi, filter);
if (DELETED.equals(doiRow.getStatus()) || TO_BE_DELETED.equals(doiRow.getStatus())) {
throw new DOIIdentifierException("You tried to register a DOI that "
@@ -435,7 +451,7 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider {
} catch (DOIIdentifierException die) {
// do we have to reserve DOI before we can register it?
if (die.getCode() == DOIIdentifierException.RESERVE_FIRST) {
this.reserveOnline(context, dso, identifier, skipFilter);
this.reserveOnline(context, dso, identifier, filter);
connector.registerDOI(context, dso, doi);
} else {
throw die;
@@ -471,17 +487,23 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider {
throws IdentifierException, IllegalArgumentException, SQLException {
String doi = doiService.formatIdentifier(identifier);
boolean skipFilter = false;
// Use the default filter unless we find the object
Filter updateFilter = this.filter;
if (doiService.findDOIByDSpaceObject(context, dso) != null) {
// We can skip the filter here since we know the DOI already exists for the item
log.debug("updateMetadata: found DOIByDSpaceObject: " +
doiService.findDOIByDSpaceObject(context, dso).getDoi());
skipFilter = true;
updateFilter = DSpaceServicesFactory.getInstance().getServiceManager().getServiceByName(
"always_true_filter", TrueFilter.class);
}
DOI doiRow = loadOrCreateDOI(context, dso, doi, skipFilter);
DOI doiRow = loadOrCreateDOI(context, dso, doi, updateFilter);
if (PENDING.equals(doiRow.getStatus()) || MINTED.equals(doiRow.getStatus())) {
log.info("Not updating metadata for PENDING or MINTED doi: " + doi);
return;
}
if (DELETED.equals(doiRow.getStatus()) || TO_BE_DELETED.equals(doiRow.getStatus())) {
throw new DOIIdentifierException("You tried to register a DOI that "
@@ -571,19 +593,19 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider {
@Override
public String mint(Context context, DSpaceObject dso)
throws IdentifierException {
return mint(context, dso, false);
return mint(context, dso, this.filter);
}
/**
* Mint a new DOI in DSpace - this is usually the first step of registration
* @param context - DSpace context
* @param dso - DSpaceObject identified by the new identifier
* @param skipFilter - boolean indicating whether to skip any filtering of items before minting.
* @param filter - Logical item filter to determine whether this identifier should be registered
* @return a String containing the new identifier
* @throws IdentifierException
*/
@Override
public String mint(Context context, DSpaceObject dso, boolean skipFilter) throws IdentifierException {
public String mint(Context context, DSpaceObject dso, Filter filter) throws IdentifierException {
String doi = null;
try {
@@ -597,7 +619,7 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider {
}
if (null == doi) {
try {
DOI doiRow = loadOrCreateDOI(context, dso, null, skipFilter);
DOI doiRow = loadOrCreateDOI(context, dso, null, filter);
doi = DOI.SCHEME + doiRow.getDoi();
} catch (SQLException e) {
@@ -895,7 +917,7 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider {
*/
protected DOI loadOrCreateDOI(Context context, DSpaceObject dso, String doiIdentifier)
throws SQLException, DOIIdentifierException, IdentifierNotApplicableException {
return loadOrCreateDOI(context, dso, doiIdentifier, false);
return loadOrCreateDOI(context, dso, doiIdentifier, this.filter);
}
/**
@@ -910,13 +932,13 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider {
* @param context - DSpace context
* @param dso - DSpaceObject to identify
* @param doiIdentifier - DOI to load or create (null to mint a new one)
* @param skipFilter - Whether or not to skip the filters for the checkMintable() check
* @param filter - Logical item filter to determine whether this identifier should be registered
* @return
* @throws SQLException
* @throws DOIIdentifierException
* @throws org.dspace.identifier.IdentifierNotApplicableException passed through.
*/
protected DOI loadOrCreateDOI(Context context, DSpaceObject dso, String doiIdentifier, boolean skipFilter)
protected DOI loadOrCreateDOI(Context context, DSpaceObject dso, String doiIdentifier, Filter filter)
throws SQLException, DOIIdentifierException, IdentifierNotApplicableException {
DOI doi = null;
@@ -954,6 +976,8 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider {
// doi is assigned to a DSO; is it assigned to our specific dso?
// check if DOI already belongs to dso
if (dso.getID().equals(doi.getDSpaceObject().getID())) {
// Before we return this, check the filter
checkMintable(context, filter, dso);
return doi;
} else {
throw new DOIIdentifierException("Trying to create a DOI " +
@@ -963,15 +987,8 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider {
}
}
// we did not find the doi in the database or shall reassign it. Before doing so, we should check if a
// filter is in place to prevent the creation of new DOIs for certain items.
if (skipFilter) {
log.warn("loadOrCreateDOI: Skipping default item filter");
} else {
// Find out if we're allowed to create a DOI
// throws an exception if creation of a new DOI is prohibited by a filter
checkMintable(context, dso);
}
// Check if this item is eligible for minting. An IdentifierNotApplicableException will be thrown if not.
checkMintable(context, filter, dso);
// check prefix
if (!doiIdentifier.startsWith(this.getPrefix() + "/")) {
@@ -984,15 +1001,8 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider {
doi = doiService.create(context);
}
} else {
// We need to generate a new DOI. Before doing so, we should check if a
// filter is in place to prevent the creation of new DOIs for certain items.
if (skipFilter) {
log.warn("loadOrCreateDOI: Skipping default item filter");
} else {
// Find out if we're allowed to create a DOI
// throws an exception if creation of a new DOI is prohibited by a filter
checkMintable(context, dso);
}
// Check if this item is eligible for minting. An IdentifierNotApplicableException will be thrown if not.
checkMintable(context, filter, dso);
doi = doiService.create(context);
doiIdentifier = this.getPrefix() + "/" + this.getNamespaceSeparator() +
@@ -1002,7 +1012,7 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider {
// prepare new doiRow
doi.setDoi(doiIdentifier);
doi.setDSpaceObject(dso);
doi.setStatus(null);
doi.setStatus(MINTED);
try {
doiService.update(context, doi);
} catch (SQLException e) {
@@ -1102,20 +1112,32 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider {
/**
* Checks to see if an item can have a DOI minted, using the configured logical filter
* @param context
* @param filter Logical item filter to apply
* @param dso The item to be evaluated
* @throws DOIIdentifierNotApplicableException
*/
@Override
public void checkMintable(Context context, DSpaceObject dso) throws DOIIdentifierNotApplicableException {
public void checkMintable(Context context, Filter filter, DSpaceObject dso)
throws DOIIdentifierNotApplicableException {
if (filter == null) {
Filter trueFilter = DSpaceServicesFactory.getInstance().getServiceManager().getServiceByName(
"always_true_filter", TrueFilter.class);
// If a null filter was passed, and we have a good default filter to apply, apply it.
// Otherwise, set to TrueFilter which means "no filtering"
if (this.filter != null) {
filter = this.filter;
} else {
filter = trueFilter;
}
}
// If the check fails, an exception will be thrown to be caught by the calling method
if (this.filterService != null && contentServiceFactory
.getDSpaceObjectService(dso).getTypeText(dso).equals("ITEM")) {
if (contentServiceFactory.getDSpaceObjectService(dso).getTypeText(dso).equals("ITEM")) {
try {
boolean result = filterService.getResult(context, (Item) dso);
boolean result = filter.getResult(context, (Item) dso);
log.debug("Result of filter for " + dso.getHandle() + " is " + result);
if (!result) {
throw new DOIIdentifierNotApplicableException("Item " + dso.getHandle() +
" was evaluated as 'false' by the item filter, not minting");
" was evaluated as 'false' by the item filter, not minting");
}
} catch (LogicalStatementException e) {
log.error("Error evaluating item with logical filter: " + e.getLocalizedMessage());
@@ -1125,4 +1147,16 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider {
log.debug("DOI Identifier Provider: filterService is null (ie. don't prevent DOI minting)");
}
}
/**
* Checks to see if an item can have a DOI minted, using the configured logical filter
* @param context
* @param dso The item to be evaluated
* @throws DOIIdentifierNotApplicableException
*/
@Override
public void checkMintable(Context context, DSpaceObject dso) throws DOIIdentifierNotApplicableException {
checkMintable(context, this.filter, dso);
}
}

View File

@@ -12,8 +12,9 @@ import java.sql.SQLException;
import org.dspace.content.DSpaceObject;
import org.dspace.content.logic.Filter;
import org.dspace.content.logic.TrueFilter;
import org.dspace.core.Context;
import org.springframework.beans.factory.annotation.Autowired;
import org.dspace.services.factory.DSpaceServicesFactory;
/**
* This abstract class adds extra method signatures so that implementing IdentifierProviders can
@@ -24,26 +25,28 @@ import org.springframework.beans.factory.annotation.Autowired;
*/
public abstract class FilteredIdentifierProvider extends IdentifierProvider {
protected Filter filterService;
protected Filter filter = DSpaceServicesFactory.getInstance()
.getServiceManager().getServiceByName("always_true_filter", TrueFilter.class);
/**
* Setter for spring to set the filter service from the property in configuration XML
* @param filterService - an object implementing the org.dspace.content.logic.Filter interface
* Setter for spring to set the default filter from the property in configuration XML
* @param filter - an object implementing the org.dspace.content.logic.Filter interface
*/
@Autowired
public void setFilterService(Filter filterService) {
this.filterService = filterService;
public void setFilter(Filter filter) {
if (filter != null) {
this.filter = filter;
}
}
/**
* Register a new identifier for a given DSpaceObject
* @param context - DSpace context
* @param dso - DSpaceObject to use for identifier registration
* @param skipFilter - boolean indicating whether to skip any filtering of items before performing registration
* @param filter - Logical item filter to determine whether this identifier should be registered
* @return identifier
* @throws IdentifierException
*/
public abstract String register(Context context, DSpaceObject dso, boolean skipFilter)
public abstract String register(Context context, DSpaceObject dso, Filter filter)
throws IdentifierException;
/**
@@ -51,10 +54,10 @@ public abstract class FilteredIdentifierProvider extends IdentifierProvider {
* @param context - DSpace context
* @param dso - DSpaceObject identified by the new identifier
* @param identifier - String containing the identifier to register
* @param skipFilter - boolean indicating whether to skip any filtering of items before performing registration
* @param filter - Logical item filter to determine whether this identifier should be registered
* @throws IdentifierException
*/
public abstract void register(Context context, DSpaceObject dso, String identifier, boolean skipFilter)
public abstract void register(Context context, DSpaceObject dso, String identifier, Filter filter)
throws IdentifierException;
/**
@@ -62,23 +65,23 @@ public abstract class FilteredIdentifierProvider extends IdentifierProvider {
* @param context - DSpace context
* @param dso - DSpaceObject identified by this identifier
* @param identifier - String containing the identifier to reserve
* @param skipFilter - boolean indicating whether to skip any filtering of items before performing reservation
* @param filter - Logical item filter to determine whether this identifier should be reserved
* @throws IdentifierException
* @throws IllegalArgumentException
* @throws SQLException
*/
public abstract void reserve(Context context, DSpaceObject dso, String identifier, boolean skipFilter)
public abstract void reserve(Context context, DSpaceObject dso, String identifier, Filter filter)
throws IdentifierException, IllegalArgumentException, SQLException;
/**
* Mint a new identifier in DSpace - this is usually the first step of registration
* @param context - DSpace context
* @param dso - DSpaceObject identified by the new identifier
* @param skipFilter - boolean indicating whether to skip any filtering of items before minting.
* @param filter - Logical item filter to determine whether this identifier should be registered
* @return a String containing the new identifier
* @throws IdentifierException
*/
public abstract String mint(Context context, DSpaceObject dso, boolean skipFilter) throws IdentifierException;
public abstract String mint(Context context, DSpaceObject dso, Filter filter) throws IdentifierException;
/**
* Check configured item filters to see if this identifier is allowed to be minted
@@ -88,5 +91,13 @@ public abstract class FilteredIdentifierProvider extends IdentifierProvider {
*/
public abstract void checkMintable(Context context, DSpaceObject dso) throws IdentifierException;
/**
* Check configured item filters to see if this identifier is allowed to be minted
* @param context - DSpace context
* @param filter - Logical item filter
* @param dso - DSpaceObject to be inspected
* @throws IdentifierException
*/
public abstract void checkMintable(Context context, Filter filter, DSpaceObject dso) throws IdentifierException;
}

View File

@@ -10,6 +10,7 @@ package org.dspace.identifier;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
@@ -17,6 +18,7 @@ import org.apache.logging.log4j.Logger;
import org.dspace.authorize.AuthorizeException;
import org.dspace.content.DSpaceObject;
import org.dspace.content.factory.ContentServiceFactory;
import org.dspace.content.logic.Filter;
import org.dspace.core.Context;
import org.dspace.handle.service.HandleService;
import org.dspace.identifier.service.IdentifierService;
@@ -44,7 +46,6 @@ public class IdentifierServiceImpl implements IdentifierService {
protected HandleService handleService;
protected IdentifierServiceImpl() {
}
@Autowired(required = true)
@@ -98,7 +99,7 @@ public class IdentifierServiceImpl implements IdentifierService {
@Override
public void register(Context context, DSpaceObject dso)
throws AuthorizeException, SQLException, IdentifierException {
throws AuthorizeException, SQLException, IdentifierException {
//We need to commit our context because one of the providers might require the handle created above
// Next resolve all other services
for (IdentifierProvider service : providers) {
@@ -112,11 +113,99 @@ public class IdentifierServiceImpl implements IdentifierService {
contentServiceFactory.getDSpaceObjectService(dso).update(context, dso);
}
@Override
public void register(Context context, DSpaceObject dso, Class<? extends Identifier> type, Filter filter)
throws AuthorizeException, SQLException, IdentifierException {
boolean registered = false;
// Iterate all services and register identifiers as appropriate
for (IdentifierProvider service : providers) {
if (service.supports(type)) {
try {
if (service instanceof FilteredIdentifierProvider) {
FilteredIdentifierProvider filteredService = (FilteredIdentifierProvider)service;
filteredService.register(context, dso, filter);
} else {
service.register(context, dso);
}
registered = true;
} catch (IdentifierNotApplicableException e) {
log.warn("Identifier not registered (inapplicable): " + e.getMessage());
}
}
}
if (!registered) {
throw new IdentifierException("Cannot register identifier: Didn't "
+ "find a provider that supports this identifier.");
}
// Update our item / collection / community
contentServiceFactory.getDSpaceObjectService(dso).update(context, dso);
}
@Override
public void register(Context context, DSpaceObject dso, Class<? extends Identifier> type)
throws AuthorizeException, SQLException, IdentifierException {
boolean registered = false;
// Iterate all services and register identifiers as appropriate
for (IdentifierProvider service : providers) {
if (service.supports(type)) {
try {
service.register(context, dso);
registered = true;
} catch (IdentifierNotApplicableException e) {
log.warn("Identifier not registered (inapplicable): " + e.getMessage());
}
}
}
if (!registered) {
throw new IdentifierException("Cannot register identifier: Didn't "
+ "find a provider that supports this identifier.");
}
// Update our item / collection / community
contentServiceFactory.getDSpaceObjectService(dso).update(context, dso);
}
@Override
public void register(Context context, DSpaceObject dso, Map<Class<? extends Identifier>, Filter> typeFilters)
throws AuthorizeException, SQLException, IdentifierException {
// Iterate all services and register identifiers as appropriate
for (IdentifierProvider service : providers) {
try {
// If the service supports filtering, look through the map and the first supported class
// we find, set the filter and break. If no filter was seen for this type, just let the provider
// use its own implementation.
if (service instanceof FilteredIdentifierProvider) {
FilteredIdentifierProvider filteredService = (FilteredIdentifierProvider)service;
Filter filter = null;
for (Class<? extends Identifier> type : typeFilters.keySet()) {
if (filteredService.supports(type)) {
filter = typeFilters.get(type);
break;
}
}
if (filter != null) {
// Pass the found filter to the provider
filteredService.register(context, dso, filter);
} else {
// Let the provider use the default filter / behaviour
filteredService.register(context, dso);
}
} else {
service.register(context, dso);
}
} catch (IdentifierNotApplicableException e) {
log.warn("Identifier not registered (inapplicable): " + e.getMessage());
}
}
// Update our item / collection / community
contentServiceFactory.getDSpaceObjectService(dso).update(context, dso);
}
@Override
public void register(Context context, DSpaceObject object, String identifier)
throws AuthorizeException, SQLException, IdentifierException {
//We need to commit our context because one of the providers might require the handle created above
// Next resolve all other services
// Iterate all services and register identifiers as appropriate
boolean registered = false;
for (IdentifierProvider service : providers) {
if (service.supports(identifier)) {
@@ -132,7 +221,7 @@ public class IdentifierServiceImpl implements IdentifierService {
throw new IdentifierException("Cannot register identifier: Didn't "
+ "find a provider that supports this identifier.");
}
//Update our item / collection / community
// pdate our item / collection / community
contentServiceFactory.getDSpaceObjectService(object).update(context, object);
}

View File

@@ -18,6 +18,7 @@ import org.dspace.authorize.AuthorizeException;
import org.dspace.content.DSpaceObject;
import org.dspace.content.Item;
import org.dspace.content.MetadataValue;
import org.dspace.content.logic.Filter;
import org.dspace.core.Context;
import org.dspace.identifier.doi.DOIConnector;
import org.dspace.identifier.doi.DOIIdentifierException;
@@ -49,7 +50,12 @@ public class VersionedDOIIdentifierProvider extends DOIIdentifierProvider {
protected VersionHistoryService versionHistoryService;
@Override
public String mint(Context context, DSpaceObject dso)
public String mint(Context context, DSpaceObject dso) throws IdentifierException {
return mint(context, dso, this.filter);
}
@Override
public String mint(Context context, DSpaceObject dso, Filter filter)
throws IdentifierException {
if (!(dso instanceof Item)) {
throw new IdentifierException("Currently only Items are supported for DOIs.");
@@ -79,6 +85,9 @@ public class VersionedDOIIdentifierProvider extends DOIIdentifierProvider {
+ " with ID " + dso.getID() + ".", ex);
}
// Make a call to the filter here to throw an exception instead of carrying on with removal + creation
checkMintable(context, filter, dso);
// check whether we have a DOI in the metadata and if we have to remove it
String metadataDOI = getDOIOutOfObject(dso);
if (metadataDOI != null) {
@@ -111,7 +120,7 @@ public class VersionedDOIIdentifierProvider extends DOIIdentifierProvider {
// ensure DOI exists in our database as well and return.
// this also checks that the doi is not assigned to another dso already.
try {
loadOrCreateDOI(context, dso, versionedDOI);
loadOrCreateDOI(context, dso, versionedDOI, filter);
} catch (SQLException ex) {
log.error(
"A problem with the database connection occurd while processing DOI " + versionedDOI + ".", ex);
@@ -127,7 +136,7 @@ public class VersionedDOIIdentifierProvider extends DOIIdentifierProvider {
// if we have a history, we have a item
doi = makeIdentifierBasedOnHistory(context, dso, history);
} else {
doi = loadOrCreateDOI(context, dso, null).getDoi();
doi = loadOrCreateDOI(context, dso, null, filter).getDoi();
}
} catch (SQLException ex) {
log.error("SQLException while creating a new DOI: ", ex);
@@ -140,7 +149,12 @@ public class VersionedDOIIdentifierProvider extends DOIIdentifierProvider {
}
@Override
public void register(Context context, DSpaceObject dso, String identifier)
public void register(Context context, DSpaceObject dso, String identifier) throws IdentifierException {
register(context, dso, identifier, this.filter);
}
@Override
public void register(Context context, DSpaceObject dso, String identifier, Filter filter)
throws IdentifierException {
if (!(dso instanceof Item)) {
throw new IdentifierException("Currently only Items are supported for DOIs.");
@@ -220,8 +234,14 @@ public class VersionedDOIIdentifierProvider extends DOIIdentifierProvider {
return doiPostfix;
}
// Should never return null!
protected String makeIdentifierBasedOnHistory(Context context, DSpaceObject dso, VersionHistory history)
throws AuthorizeException, SQLException, DOIIdentifierException, IdentifierNotApplicableException {
return makeIdentifierBasedOnHistory(context, dso, history, this.filter);
}
// Should never return null!
protected String makeIdentifierBasedOnHistory(Context context, DSpaceObject dso, VersionHistory history,
Filter filter)
throws AuthorizeException, SQLException, DOIIdentifierException, IdentifierNotApplicableException {
// Mint foreach new version an identifier like: 12345/100.versionNumber
// use the bare handle (g.e. 12345/100) for the first version.
@@ -244,6 +264,9 @@ public class VersionedDOIIdentifierProvider extends DOIIdentifierProvider {
}
if (previousVersionDOI == null) {
// Before continuing with any new DOI creation, apply the filter
checkMintable(context, filter, dso);
// We need to generate a new DOI.
DOI doi = doiService.create(context);
@@ -269,7 +292,7 @@ public class VersionedDOIIdentifierProvider extends DOIIdentifierProvider {
String.valueOf(versionHistoryService.getVersion(context, history, item).getVersionNumber()));
}
loadOrCreateDOI(context, dso, identifier);
loadOrCreateDOI(context, dso, identifier, filter);
return identifier;
}

View File

@@ -7,22 +7,32 @@
*/
package org.dspace.identifier.doi;
import java.sql.SQLException;
import org.apache.logging.log4j.Logger;
import org.dspace.content.DSpaceObject;
import org.dspace.content.Item;
import org.dspace.content.factory.ContentServiceFactory;
import org.dspace.content.logic.Filter;
import org.dspace.content.logic.FilterUtils;
import org.dspace.core.Constants;
import org.dspace.core.Context;
import org.dspace.event.Consumer;
import org.dspace.event.Event;
import org.dspace.identifier.DOI;
import org.dspace.identifier.DOIIdentifierProvider;
import org.dspace.identifier.IdentifierException;
import org.dspace.identifier.IdentifierNotFoundException;
import org.dspace.identifier.IdentifierNotApplicableException;
import org.dspace.identifier.factory.IdentifierServiceFactory;
import org.dspace.identifier.service.DOIService;
import org.dspace.services.ConfigurationService;
import org.dspace.services.factory.DSpaceServicesFactory;
import org.dspace.utils.DSpace;
import org.dspace.workflow.factory.WorkflowServiceFactory;
/**
* @author Pascal-Nicolas Becker (p dot becker at tu hyphen berlin dot de)
* @author Kim Shepherd
*/
public class DOIConsumer implements Consumer {
/**
@@ -30,12 +40,15 @@ public class DOIConsumer implements Consumer {
*/
private static Logger log = org.apache.logging.log4j.LogManager.getLogger(DOIConsumer.class);
ConfigurationService configurationService;
@Override
public void initialize() throws Exception {
// nothing to do
// we can ask spring to give as a properly setuped instance of
// DOIIdentifierProvider. Doing so we don't have to configure it and
// can load it in consume method as this is not very expensive.
configurationService = DSpaceServicesFactory.getInstance().getConfigurationService();
}
@@ -62,36 +75,73 @@ public class DOIConsumer implements Consumer {
return;
}
Item item = (Item) dso;
if (ContentServiceFactory.getInstance().getWorkspaceItemService().findByItem(ctx, item) != null
|| WorkflowServiceFactory.getInstance().getWorkflowItemService().findByItem(ctx, item) != null) {
// ignore workflow and workspace items, DOI will be minted when item is installed
return;
DOIIdentifierProvider provider = new DSpace().getSingletonService(DOIIdentifierProvider.class);
boolean inProgress = (ContentServiceFactory.getInstance().getWorkspaceItemService().findByItem(ctx, item)
!= null || WorkflowServiceFactory.getInstance().getWorkflowItemService().findByItem(ctx, item) != null);
boolean identifiersInSubmission = configurationService.getBooleanProperty("identifiers.submission.register",
false);
DOIService doiService = IdentifierServiceFactory.getInstance().getDOIService();
Filter workspaceFilter = null;
if (identifiersInSubmission) {
workspaceFilter = FilterUtils.getFilterFromConfiguration("identifiers.submission.filter.workspace");
}
DOIIdentifierProvider provider = new DSpace().getSingletonService(
DOIIdentifierProvider.class);
String doi = null;
if (inProgress && !identifiersInSubmission) {
// ignore workflow and workspace items, DOI will be minted and updated when item is installed
// UNLESS special pending filter is set
return;
}
DOI doi = null;
try {
doi = provider.lookup(ctx, dso);
} catch (IdentifierNotFoundException ex) {
doi = doiService.findDOIByDSpaceObject(ctx, dso);
} catch (SQLException ex) {
// nothing to do here, next if clause will stop us from processing
// items without dois.
}
if (doi == null) {
log.debug("DOIConsumer cannot handles items without DOIs, skipping: "
+ event.toString());
return;
}
try {
provider.updateMetadata(ctx, dso, doi);
} catch (IllegalArgumentException ex) {
// should not happen, as we got the DOI from the DOIProvider
log.warn("DOIConsumer caught an IdentifierException.", ex);
} catch (IdentifierException ex) {
log.warn("DOIConsumer cannot update metadata for Item with ID "
+ item.getID() + " and DOI " + doi + ".", ex);
// No DOI. The only time something should be minted is if we have enabled submission reg'n and
// it passes the workspace filter. We also need to update status to PENDING straight after.
if (inProgress) {
provider.mint(ctx, dso, workspaceFilter);
DOI newDoi = doiService.findDOIByDSpaceObject(ctx, dso);
if (newDoi != null) {
newDoi.setStatus(DOIIdentifierProvider.PENDING);
doiService.update(ctx, newDoi);
}
} else {
log.debug("DOIConsumer cannot handles items without DOIs, skipping: " + event.toString());
}
} else {
// If in progress, we can also switch PENDING and MINTED status depending on the latest filter
// evaluation
if (inProgress) {
try {
// Check the filter
provider.checkMintable(ctx, workspaceFilter, dso);
// If we made it here, the existing doi should be back to PENDING
if (DOIIdentifierProvider.MINTED.equals(doi.getStatus())) {
doi.setStatus(DOIIdentifierProvider.PENDING);
}
} catch (IdentifierNotApplicableException e) {
// Set status to MINTED if configured to downgrade existing DOIs
if (configurationService
.getBooleanProperty("identifiers.submission.strip_pending_during_submission", true)) {
doi.setStatus(DOIIdentifierProvider.MINTED);
}
}
doiService.update(ctx, doi);
} else {
try {
provider.updateMetadata(ctx, dso, doi.getDoi());
} catch (IllegalArgumentException ex) {
// should not happen, as we got the DOI from the DOIProvider
log.warn("DOIConsumer caught an IdentifierException.", ex);
} catch (IdentifierException ex) {
log.warn("DOIConsumer cannot update metadata for Item with ID "
+ item.getID() + " and DOI " + doi + ".", ex);
}
}
ctx.commit();
}
}

View File

@@ -30,6 +30,9 @@ import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.dspace.content.DSpaceObject;
import org.dspace.content.factory.ContentServiceFactory;
import org.dspace.content.logic.Filter;
import org.dspace.content.logic.FilterUtils;
import org.dspace.content.logic.TrueFilter;
import org.dspace.content.service.ItemService;
import org.dspace.core.Constants;
import org.dspace.core.Context;
@@ -61,7 +64,8 @@ public class DOIOrganiser {
protected ItemService itemService;
protected DOIService doiService;
protected ConfigurationService configurationService;
protected boolean skipFilter;
// This filter will override the default provider filter / behaviour
protected Filter filter;
/**
* Constructor to be called within the main() method
@@ -76,7 +80,8 @@ public class DOIOrganiser {
this.itemService = ContentServiceFactory.getInstance().getItemService();
this.doiService = IdentifierServiceFactory.getInstance().getDOIService();
this.configurationService = DSpaceServicesFactory.getInstance().getConfigurationService();
this.skipFilter = false;
this.filter = DSpaceServicesFactory.getInstance().getServiceManager().getServiceByName(
"always_true_filter", TrueFilter.class);
}
/**
@@ -121,12 +126,13 @@ public class DOIOrganiser {
"Perform online metadata update for all identifiers queued for metadata update.");
options.addOption("d", "delete-all", false,
"Perform online deletion for all identifiers queued for deletion.");
options.addOption("q", "quiet", false,
"Turn the command line output off.");
options.addOption(null, "skip-filter", false,
"Skip the configured item filter when registering or reserving.");
Option filterDoi = Option.builder().optionalArg(true).longOpt("filter").hasArg().argName("filterName")
.desc("Use the specified filter name instead of the provider's filter. Defaults to a special " +
"'always true' filter to force operations").build();
options.addOption(filterDoi);
Option registerDoi = Option.builder()
.longOpt("register-doi")
@@ -203,10 +209,12 @@ public class DOIOrganiser {
}
DOIService doiService = IdentifierServiceFactory.getInstance().getDOIService();
// Should we skip the filter?
if (line.hasOption("skip-filter")) {
System.out.println("Skipping the item filter");
organiser.skipFilter = true;
// Do we get a filter?
if (line.hasOption("filter")) {
String filter = line.getOptionValue("filter");
if (null != filter) {
organiser.filter = FilterUtils.getFilterFromConfiguration(filter);
}
}
if (line.hasOption('s')) {
@@ -394,19 +402,18 @@ public class DOIOrganiser {
/**
* Register DOI with the provider
* @param doiRow - doi to register
* @param skipFilter - whether filters should be skipped before registration
* @param filter - logical item filter to override
* @throws SQLException
* @throws DOIIdentifierException
*/
public void register(DOI doiRow, boolean skipFilter) throws SQLException, DOIIdentifierException {
public void register(DOI doiRow, Filter filter) throws SQLException, DOIIdentifierException {
DSpaceObject dso = doiRow.getDSpaceObject();
if (Constants.ITEM != dso.getType()) {
throw new IllegalArgumentException("Currenty DSpace supports DOIs for Items only.");
}
try {
provider.registerOnline(context, dso,
DOI.SCHEME + doiRow.getDoi());
provider.registerOnline(context, dso, DOI.SCHEME + doiRow.getDoi(), filter);
if (!quiet) {
System.out.println("This identifier: "
@@ -466,29 +473,23 @@ public class DOIOrganiser {
}
/**
* Register DOI with the provider, always applying (ie. never skipping) any configured filters
* Register DOI with the provider
* @param doiRow - doi to register
* @throws SQLException
* @throws DOIIdentifierException
*/
public void register(DOI doiRow) throws SQLException, DOIIdentifierException {
if (this.skipFilter) {
System.out.println("Skipping the filter for " + doiRow.getDoi());
}
register(doiRow, this.skipFilter);
register(doiRow, this.filter);
}
/**
* Reserve DOI with the provider, always applying (ie. never skipping) any configured filters
* Reserve DOI with the provider,
* @param doiRow - doi to reserve
* @throws SQLException
* @throws DOIIdentifierException
*/
public void reserve(DOI doiRow) {
if (this.skipFilter) {
System.out.println("Skipping the filter for " + doiRow.getDoi());
}
reserve(doiRow, this.skipFilter);
reserve(doiRow, this.filter);
}
/**
@@ -497,14 +498,14 @@ public class DOIOrganiser {
* @throws SQLException
* @throws DOIIdentifierException
*/
public void reserve(DOI doiRow, boolean skipFilter) {
public void reserve(DOI doiRow, Filter filter) {
DSpaceObject dso = doiRow.getDSpaceObject();
if (Constants.ITEM != dso.getType()) {
throw new IllegalArgumentException("Currently DSpace supports DOIs for Items only.");
}
try {
provider.reserveOnline(context, dso, DOI.SCHEME + doiRow.getDoi(), skipFilter);
provider.reserveOnline(context, dso, DOI.SCHEME + doiRow.getDoi(), filter);
if (!quiet) {
System.out.println("This identifier : " + DOI.SCHEME + doiRow.getDoi() + " is successfully reserved.");
@@ -699,7 +700,7 @@ public class DOIOrganiser {
//Check if this Item has an Identifier, mint one if it doesn't
if (null == doiRow) {
doi = provider.mint(context, dso, this.skipFilter);
doi = provider.mint(context, dso, this.filter);
doiRow = doiService.findByDoi(context,
doi.substring(DOI.SCHEME.length()));
return doiRow;
@@ -723,7 +724,7 @@ public class DOIOrganiser {
doiRow = doiService.findDOIByDSpaceObject(context, dso);
if (null == doiRow) {
doi = provider.mint(context, dso, this.skipFilter);
doi = provider.mint(context, dso, this.filter);
doiRow = doiService.findByDoi(context,
doi.substring(DOI.SCHEME.length()));
}

View File

@@ -9,9 +9,11 @@ package org.dspace.identifier.service;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;
import org.dspace.authorize.AuthorizeException;
import org.dspace.content.DSpaceObject;
import org.dspace.content.logic.Filter;
import org.dspace.core.Context;
import org.dspace.identifier.Identifier;
import org.dspace.identifier.IdentifierException;
@@ -103,6 +105,52 @@ public interface IdentifierService {
*/
void register(Context context, DSpaceObject dso) throws AuthorizeException, SQLException, IdentifierException;
/**
*
* Register identifiers for a DSO, with a map of logical filters for each Identifier class to apply
* at the time of local registration.
*
* @param context The relevant DSpace Context.
* @param dso DSpace object to be registered
* @param typeFilters If a service supports a given Identifier implementation, apply the associated filter
* @throws AuthorizeException if authorization error
* @throws SQLException if database error
* @throws IdentifierException if identifier error
*/
void register(Context context, DSpaceObject dso, Map<Class<? extends Identifier>, Filter> typeFilters)
throws AuthorizeException, SQLException, IdentifierException;
/**
*
* Register identifier(s) for the given DSO just with providers that support that Identifier class, and
* apply the given filter if that provider extends FilteredIdentifierProvider
*
* @param context The relevant DSpace Context.
* @param dso DSpace object to be registered
* @param type Type of identifier to register
* @param filter If a service supports a given Identifier implementation, apply this specific filter
* @throws AuthorizeException if authorization error
* @throws SQLException if database error
* @throws IdentifierException if identifier error
*/
void register(Context context, DSpaceObject dso, Class<? extends Identifier> type, Filter filter)
throws AuthorizeException, SQLException, IdentifierException;
/**
*
* Register identifier(s) for the given DSO just with providers that support that Identifier class, and
* apply the given filter if that provider extends FilteredIdentifierProvider
*
* @param context The relevant DSpace Context.
* @param dso DSpace object to be registered
* @param type Type of identifier to register
* @throws AuthorizeException if authorization error
* @throws SQLException if database error
* @throws IdentifierException if identifier error
*/
void register(Context context, DSpaceObject dso, Class<? extends Identifier> type)
throws AuthorizeException, SQLException, IdentifierException;
/**
* Used to Register a specific Identifier (for example a Handle, hdl:1234.5/6).
* The provider is responsible for detecting and processing the appropriate

View File

@@ -8,6 +8,7 @@
package org.dspace.scripts;
import java.lang.reflect.InvocationTargetException;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
@@ -36,7 +37,9 @@ public class ScriptServiceImpl implements ScriptService {
@Override
public List<ScriptConfiguration> getScriptConfigurations(Context context) {
return serviceManager.getServicesByType(ScriptConfiguration.class).stream().filter(
scriptConfiguration -> scriptConfiguration.isAllowedToExecute(context)).collect(Collectors.toList());
scriptConfiguration -> scriptConfiguration.isAllowedToExecute(context))
.sorted(Comparator.comparing(ScriptConfiguration::getName))
.collect(Collectors.toList());
}
@Override

View File

@@ -303,6 +303,10 @@ public class BitstreamStorageServiceImpl implements BitstreamStorageService, Ini
commitCounter++;
if (commitCounter % 100 == 0) {
context.dispatchEvents();
// Commit actual changes to DB after dispatch events
System.out.print("Performing incremental commit to the database...");
context.commit();
System.out.println(" Incremental commit done!");
}
context.uncacheEntity(bitstream);

View File

@@ -0,0 +1,97 @@
/**
* 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.subscriptions;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.apache.commons.lang.StringUtils.EMPTY;
import java.io.ByteArrayOutputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.dspace.content.Item;
import org.dspace.content.crosswalk.StreamDisseminationCrosswalk;
import org.dspace.content.service.ItemService;
import org.dspace.core.Context;
import org.dspace.core.Email;
import org.dspace.core.I18nUtil;
import org.dspace.discovery.IndexableObject;
import org.dspace.eperson.EPerson;
import org.dspace.subscriptions.service.SubscriptionGenerator;
import org.springframework.beans.factory.annotation.Autowired;
/**
* Implementation class of SubscriptionGenerator
* which will handle the logic of sending the emails
* in case of 'content' subscriptionType
*/
@SuppressWarnings("rawtypes")
public class ContentGenerator implements SubscriptionGenerator<IndexableObject> {
private final Logger log = LogManager.getLogger(ContentGenerator.class);
@SuppressWarnings("unchecked")
private Map<String, StreamDisseminationCrosswalk> entityType2Disseminator = new HashMap();
@Autowired
private ItemService itemService;
@Override
public void notifyForSubscriptions(Context context, EPerson ePerson,
List<IndexableObject> indexableComm,
List<IndexableObject> indexableColl) {
try {
if (Objects.nonNull(ePerson)) {
Locale supportedLocale = I18nUtil.getEPersonLocale(ePerson);
Email email = Email.getEmail(I18nUtil.getEmailFilename(supportedLocale, "subscriptions_content"));
email.addRecipient(ePerson.getEmail());
email.addArgument(generateBodyMail(context, indexableComm));
email.addArgument(generateBodyMail(context, indexableColl));
email.send();
}
} catch (Exception e) {
log.error(e.getMessage(), e);
log.warn("Cannot email user eperson_id: {} eperson_email: {}", ePerson::getID, ePerson::getEmail);
}
}
private String generateBodyMail(Context context, List<IndexableObject> indexableObjects) {
try {
ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write("\n".getBytes(UTF_8));
if (indexableObjects.size() > 0) {
for (IndexableObject indexableObject : indexableObjects) {
out.write("\n".getBytes(UTF_8));
Item item = (Item) indexableObject.getIndexedObject();
String entityType = itemService.getEntityTypeLabel(item);
Optional.ofNullable(entityType2Disseminator.get(entityType))
.orElseGet(() -> entityType2Disseminator.get("Item"))
.disseminate(context, item, out);
}
return out.toString();
} else {
out.write("No items".getBytes(UTF_8));
}
return out.toString();
} catch (Exception e) {
log.error(e.getMessage(), e);
}
return EMPTY;
}
public void setEntityType2Disseminator(Map<String, StreamDisseminationCrosswalk> entityType2Disseminator) {
this.entityType2Disseminator = entityType2Disseminator;
}
}

View File

@@ -0,0 +1,94 @@
/**
* 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.subscriptions;
import java.sql.SQLException;
import java.util.Objects;
import java.util.UUID;
import org.apache.commons.cli.ParseException;
import org.apache.commons.lang3.StringUtils;
import org.dspace.core.Context;
import org.dspace.eperson.EPerson;
import org.dspace.eperson.FrequencyType;
import org.dspace.eperson.factory.EPersonServiceFactory;
import org.dspace.scripts.DSpaceRunnable;
import org.dspace.utils.DSpace;
/**
* Implementation of {@link DSpaceRunnable} to find subscribed objects and send notification mails about them
*
* @author alba aliu
*/
public class SubscriptionEmailNotification
extends DSpaceRunnable<SubscriptionEmailNotificationConfiguration<SubscriptionEmailNotification>> {
private Context context;
private SubscriptionEmailNotificationService subscriptionEmailNotificationService;
@Override
@SuppressWarnings("unchecked")
public SubscriptionEmailNotificationConfiguration<SubscriptionEmailNotification> getScriptConfiguration() {
return new DSpace().getServiceManager().getServiceByName("subscription-send",
SubscriptionEmailNotificationConfiguration.class);
}
@Override
public void setup() throws ParseException {
this.subscriptionEmailNotificationService = new DSpace().getServiceManager().getServiceByName(
SubscriptionEmailNotificationServiceImpl.class.getName(), SubscriptionEmailNotificationServiceImpl.class);
}
@Override
public void internalRun() throws Exception {
assignCurrentUserInContext();
assignSpecialGroupsInContext();
String frequencyOption = commandLine.getOptionValue("f");
if (StringUtils.isBlank(frequencyOption)) {
throw new IllegalArgumentException("Option --frequency (-f) must be set");
}
if (!FrequencyType.isSupportedFrequencyType(frequencyOption)) {
throw new IllegalArgumentException(
"Option f must be one of following values D(Day), W(Week) or M(Month)");
}
subscriptionEmailNotificationService.perform(getContext(), handler, "content", frequencyOption);
}
private void assignCurrentUserInContext() throws SQLException {
context = new Context();
UUID uuid = getEpersonIdentifier();
if (Objects.nonNull(uuid)) {
EPerson ePerson = EPersonServiceFactory.getInstance().getEPersonService().find(context, uuid);
context.setCurrentUser(ePerson);
}
}
private void assignSpecialGroupsInContext() throws SQLException {
for (UUID uuid : handler.getSpecialGroups()) {
context.setSpecialGroup(uuid);
}
}
public SubscriptionEmailNotificationService getSubscriptionEmailNotificationService() {
return subscriptionEmailNotificationService;
}
public void setSubscriptionEmailNotificationService(SubscriptionEmailNotificationService notificationService) {
this.subscriptionEmailNotificationService = notificationService;
}
public Context getContext() {
return context;
}
public void setContext(Context context) {
this.context = context;
}
}

View File

@@ -0,0 +1,15 @@
/**
* 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.subscriptions;
/**
* Extension of {@link SubscriptionEmailNotification} for CLI.
*/
public class SubscriptionEmailNotificationCli extends SubscriptionEmailNotification {
}

View File

@@ -0,0 +1,16 @@
/**
* 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.subscriptions;
/**
* Extension of {@link SubscriptionEmailNotificationCli} for CLI.
*/
public class SubscriptionEmailNotificationCliScriptConfiguration<T extends SubscriptionEmailNotificationCli>
extends SubscriptionEmailNotificationConfiguration<T> {
}

View File

@@ -0,0 +1,63 @@
/**
* 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.subscriptions;
import java.sql.SQLException;
import java.util.Objects;
import org.apache.commons.cli.Options;
import org.dspace.authorize.AuthorizeServiceImpl;
import org.dspace.core.Context;
import org.dspace.scripts.DSpaceRunnable;
import org.dspace.scripts.configuration.ScriptConfiguration;
import org.springframework.beans.factory.annotation.Autowired;
/**
* Implementation of {@link DSpaceRunnable} to find subscribed objects and send notification mails about them
*/
public class SubscriptionEmailNotificationConfiguration<T
extends SubscriptionEmailNotification> extends ScriptConfiguration<T> {
private Class<T> dspaceRunnableClass;
@Autowired
private AuthorizeServiceImpl authorizeService;
@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 (Objects.isNull(options)) {
Options options = new Options();
options.addOption("f", "frequency", true,
"Subscription frequency. Valid values include: D (Day), W (Week) and M (Month)");
options.getOption("f").setRequired(true);
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,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.subscriptions;
import java.util.Set;
import org.dspace.core.Context;
import org.dspace.scripts.handler.DSpaceRunnableHandler;
/**
* Service interface class for the subscription e-mail notification services
*
* @author Mykhaylo Boychuk (mykhaylo.boychuk@4science.com)
*/
public interface SubscriptionEmailNotificationService {
/**
* Performs sending of e-mails to subscribers by frequency value and SubscriptionType
*
* @param context DSpace context object
* @param handler Applicable DSpaceRunnableHandler
* @param subscriptionType Currently supported only "content"
* @param frequency Valid values include: D (Day), W (Week) and M (Month)
*/
public void perform(Context context, DSpaceRunnableHandler handler, String subscriptionType, String frequency);
/**
* returns a set of supported SubscriptionTypes
*/
public Set<String> getSupportedSubscriptionTypes();
}

View File

@@ -0,0 +1,172 @@
/**
* 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.subscriptions;
import static org.dspace.core.Constants.COLLECTION;
import static org.dspace.core.Constants.COMMUNITY;
import static org.dspace.core.Constants.READ;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.dspace.authorize.service.AuthorizeService;
import org.dspace.content.Collection;
import org.dspace.content.Community;
import org.dspace.content.DSpaceObject;
import org.dspace.content.Item;
import org.dspace.core.Context;
import org.dspace.discovery.IndexableObject;
import org.dspace.eperson.EPerson;
import org.dspace.eperson.Subscription;
import org.dspace.eperson.service.SubscribeService;
import org.dspace.scripts.DSpaceRunnable;
import org.dspace.scripts.handler.DSpaceRunnableHandler;
import org.dspace.subscriptions.service.DSpaceObjectUpdates;
import org.dspace.subscriptions.service.SubscriptionGenerator;
import org.springframework.beans.factory.annotation.Autowired;
/**
* Implementation of {@link DSpaceRunnable} to find subscribed objects and send notification mails about them
*
* @author alba aliu
*/
public class SubscriptionEmailNotificationServiceImpl implements SubscriptionEmailNotificationService {
private static final Logger log = LogManager.getLogger(SubscriptionEmailNotificationServiceImpl.class);
private Map<String, DSpaceObjectUpdates> contentUpdates = new HashMap<>();
@SuppressWarnings("rawtypes")
private Map<String, SubscriptionGenerator> subscriptionType2generators = new HashMap<>();
@Autowired
private AuthorizeService authorizeService;
@Autowired
private SubscribeService subscribeService;
@SuppressWarnings("rawtypes")
public SubscriptionEmailNotificationServiceImpl(Map<String, DSpaceObjectUpdates> contentUpdates,
Map<String, SubscriptionGenerator> subscriptionType2generators) {
this.contentUpdates = contentUpdates;
this.subscriptionType2generators = subscriptionType2generators;
}
@SuppressWarnings({ "rawtypes", "unchecked" })
public void perform(Context context, DSpaceRunnableHandler handler, String subscriptionType, String frequency) {
List<IndexableObject> communityItems = new ArrayList<>();
List<IndexableObject> collectionsItems = new ArrayList<>();
try {
List<Subscription> subscriptions =
findAllSubscriptionsBySubscriptionTypeAndFrequency(context, subscriptionType, frequency);
// Here is verified if SubscriptionType is "content" Or "statistics" as them are configured
if (subscriptionType2generators.keySet().contains(subscriptionType)) {
// the list of the person who has subscribed
int iterator = 0;
for (Subscription subscription : subscriptions) {
DSpaceObject dSpaceObject = subscription.getDSpaceObject();
EPerson ePerson = subscription.getEPerson();
if (!authorizeService.authorizeActionBoolean(context, ePerson, dSpaceObject, READ, true)) {
iterator++;
continue;
}
if (dSpaceObject.getType() == COMMUNITY) {
List<IndexableObject> indexableCommunityItems = contentUpdates
.get(Community.class.getSimpleName().toLowerCase())
.findUpdates(context, dSpaceObject, frequency);
communityItems.addAll(getItems(context, ePerson, indexableCommunityItems));
} else if (dSpaceObject.getType() == COLLECTION) {
List<IndexableObject> indexableCollectionItems = contentUpdates
.get(Collection.class.getSimpleName().toLowerCase())
.findUpdates(context, dSpaceObject, frequency);
collectionsItems.addAll(getItems(context, ePerson, indexableCollectionItems));
} else {
log.warn("found an invalid DSpace Object type ({}) among subscriptions to send",
dSpaceObject.getType());
continue;
}
if (iterator < subscriptions.size() - 1) {
// as the subscriptions are ordered by eperson id, so we send them by ePerson
if (ePerson.equals(subscriptions.get(iterator + 1).getEPerson())) {
iterator++;
continue;
} else {
subscriptionType2generators.get(subscriptionType)
.notifyForSubscriptions(context, ePerson, communityItems, collectionsItems);
communityItems.clear();
collectionsItems.clear();
}
} else {
//in the end of the iteration
subscriptionType2generators.get(subscriptionType)
.notifyForSubscriptions(context, ePerson, communityItems, collectionsItems);
}
iterator++;
}
} else {
throw new IllegalArgumentException("Currently this SubscriptionType:" + subscriptionType +
" is not supported!");
}
} catch (Exception e) {
log.error(e.getMessage(), e);
handler.handleException(e);
context.abort();
}
}
@SuppressWarnings("rawtypes")
private List<IndexableObject> getItems(Context context, EPerson ePerson, List<IndexableObject> indexableItems)
throws SQLException {
List<IndexableObject> items = new ArrayList<IndexableObject>();
for (IndexableObject indexableitem : indexableItems) {
Item item = (Item) indexableitem.getIndexedObject();
if (authorizeService.authorizeActionBoolean(context, ePerson, item, READ, true)) {
items.add(indexableitem);
}
}
return items;
}
/**
* Return all Subscriptions by subscriptionType and frequency ordered by ePerson ID
* if there are none it returns an empty list
*
* @param context DSpace context
* @param subscriptionType Could be "content" or "statistics". NOTE: in DSpace we have only "content"
* @param frequency Could be "D" stand for Day, "W" stand for Week, and "M" stand for Month
* @return
*/
private List<Subscription> findAllSubscriptionsBySubscriptionTypeAndFrequency(Context context,
String subscriptionType, String frequency) {
try {
return subscribeService.findAllSubscriptionsBySubscriptionTypeAndFrequency(context, subscriptionType,
frequency)
.stream()
.sorted(Comparator.comparing(s -> s.getEPerson().getID()))
.collect(Collectors.toList());
} catch (SQLException e) {
log.error(e.getMessage(), e);
}
return new ArrayList<Subscription>();
}
@Override
public Set<String> getSupportedSubscriptionTypes() {
return subscriptionType2generators.keySet();
}
}

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.subscriptions.objectupdates;
import java.util.List;
import org.dspace.content.DSpaceObject;
import org.dspace.core.Context;
import org.dspace.discovery.DiscoverQuery;
import org.dspace.discovery.DiscoverResult;
import org.dspace.discovery.IndexableObject;
import org.dspace.discovery.SearchService;
import org.dspace.discovery.SearchServiceException;
import org.dspace.eperson.FrequencyType;
import org.dspace.subscriptions.service.DSpaceObjectUpdates;
import org.springframework.beans.factory.annotation.Autowired;
/**
* Class which will be used to find
* all collection objects updated related with subscribed DSO
*
* @author Alba Aliu
*/
public class CollectionUpdates implements DSpaceObjectUpdates {
@Autowired
private SearchService searchService;
@Override
@SuppressWarnings("rawtypes")
public List<IndexableObject> findUpdates(Context context, DSpaceObject dSpaceObject, String frequency)
throws SearchServiceException {
DiscoverQuery discoverQuery = new DiscoverQuery();
getDefaultFilterQueries().stream().forEach(fq -> discoverQuery.addFilterQueries(fq));
discoverQuery.addFilterQueries("location.coll:(" + dSpaceObject.getID() + ")");
discoverQuery.addFilterQueries("lastModified:" + FrequencyType.findLastFrequency(frequency));
DiscoverResult discoverResult = searchService.search(context, discoverQuery);
return discoverResult.getIndexableObjects();
}
}

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.subscriptions.objectupdates;
import java.util.List;
import org.dspace.content.DSpaceObject;
import org.dspace.core.Context;
import org.dspace.discovery.DiscoverQuery;
import org.dspace.discovery.DiscoverResult;
import org.dspace.discovery.IndexableObject;
import org.dspace.discovery.SearchService;
import org.dspace.discovery.SearchServiceException;
import org.dspace.eperson.FrequencyType;
import org.dspace.subscriptions.service.DSpaceObjectUpdates;
import org.springframework.beans.factory.annotation.Autowired;
/**
* Class which will be used to find
* all community objects updated related with subscribed DSO
*
* @author Alba Aliu
*/
public class CommunityUpdates implements DSpaceObjectUpdates {
@Autowired
private SearchService searchService;
@Override
@SuppressWarnings("rawtypes")
public List<IndexableObject> findUpdates(Context context, DSpaceObject dSpaceObject, String frequency)
throws SearchServiceException {
DiscoverQuery discoverQuery = new DiscoverQuery();
getDefaultFilterQueries().stream().forEach(fq -> discoverQuery.addFilterQueries(fq));
discoverQuery.addFilterQueries("location.comm:(" + dSpaceObject.getID() + ")");
discoverQuery.addFilterQueries("lastModified:" + FrequencyType.findLastFrequency(frequency));
DiscoverResult discoverResult = searchService.search(context, discoverQuery);
return discoverResult.getIndexableObjects();
}
}

View File

@@ -0,0 +1,41 @@
/**
* 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.subscriptions.service;
import java.util.Arrays;
import java.util.List;
import org.dspace.content.DSpaceObject;
import org.dspace.content.Item;
import org.dspace.core.Context;
import org.dspace.discovery.IndexableObject;
import org.dspace.discovery.SearchServiceException;
/**
* Interface class which will be used to find all objects updated related with subscribed DSO
*
* @author Alba Aliu
*/
public interface DSpaceObjectUpdates {
/**
* Send an email to some addresses, concerning a Subscription, using a given dso.
*
* @param context current DSpace session.
*/
@SuppressWarnings("rawtypes")
public List<IndexableObject> findUpdates(Context context, DSpaceObject dSpaceObject, String frequency)
throws SearchServiceException;
default List<String> getDefaultFilterQueries() {
return Arrays.asList("search.resourcetype:" + Item.class.getSimpleName(),
"-discoverable:" + false,
"-withdrawn:" + true);
}
}

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.subscriptions.service;
import java.util.List;
import org.dspace.core.Context;
import org.dspace.eperson.EPerson;
/**
* Interface Class which will be used to send email notifications to ePerson
* containing information for all list of objects.
*
* @author Alba Aliu
*/
public interface SubscriptionGenerator<T> {
public void notifyForSubscriptions(Context c, EPerson ePerson, List<T> comm, List<T> coll);
}

View File

@@ -18,6 +18,7 @@ import org.dspace.core.Context;
import org.dspace.eperson.EPerson;
import org.dspace.eperson.Group;
import org.dspace.xmlworkflow.WorkflowConfigurationException;
import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem;
/**
* Service interface class for the WorkflowService framework.
@@ -100,6 +101,9 @@ public interface WorkflowService<T extends WorkflowItem> {
String rejection_message)
throws SQLException, AuthorizeException, IOException;
public void restartWorkflow(Context context, XmlWorkflowItem wi, EPerson decliner, String provenance)
throws SQLException, AuthorizeException, IOException, WorkflowException;
public String getMyDSpaceLink();
public void deleteCollection(Context context, Collection collection)

View File

@@ -41,6 +41,9 @@ public class Role implements BeanNameAware {
@Autowired
private WorkflowItemRoleService workflowItemRoleService;
// Whether or not to delete temporary group made attached to the WorkflowItemRole for this role in AutoAssignAction
private boolean deleteTemporaryGroup = false;
private String id;
private String name;
private String description;
@@ -153,4 +156,17 @@ public class Role implements BeanNameAware {
public void setInternal(boolean internal) {
isInternal = internal;
}
public boolean isDeleteTemporaryGroup() {
return deleteTemporaryGroup;
}
/**
* Setter for config that indicated whether or not to delete temporary group made attached to the
* WorkflowItemRole for this role in AutoAssignAction
* @param deleteTemporaryGroup
*/
public void setDeleteTemporaryGroup(boolean deleteTemporaryGroup) {
this.deleteTemporaryGroup = deleteTemporaryGroup;
}
}

View File

@@ -1076,6 +1076,53 @@ public class XmlWorkflowServiceImpl implements XmlWorkflowService {
return wsi;
}
@Override
public void restartWorkflow(Context context, XmlWorkflowItem wi, EPerson decliner, String provenance)
throws SQLException, AuthorizeException, IOException, WorkflowException {
if (!authorizeService.isAdmin(context)) {
throw new AuthorizeException("You must be an admin to restart a workflow");
}
context.turnOffAuthorisationSystem();
// rejection provenance
Item myitem = wi.getItem();
// Here's what happened
String provDescription =
provenance + " Declined by " + getEPersonName(decliner) + " on " + DCDate.getCurrent().toString() +
" (GMT) ";
// Add to item as a DC field
itemService
.addMetadata(context, myitem, MetadataSchemaEnum.DC.getName(),
"description", "provenance", "en", provDescription);
//Clear any workflow schema related metadata
itemService
.clearMetadata(context, myitem, WorkflowRequirementsService.WORKFLOW_SCHEMA, Item.ANY, Item.ANY, Item.ANY);
itemService.update(context, myitem);
// remove policy for controller
removeUserItemPolicies(context, myitem, decliner);
revokeReviewerPolicies(context, myitem);
// convert into personal workspace
WorkspaceItem wsi = returnToWorkspace(context, wi);
// Because of issue of xmlWorkflowItemService not realising wfi wrapper has been deleted
context.commit();
wsi = context.reloadEntity(wsi);
log.info(LogHelper.getHeader(context, "decline_workflow", "workflow_item_id="
+ wi.getID() + "item_id=" + wi.getItem().getID() + "collection_id=" + wi.getCollection().getID() +
"eperson_id=" + decliner.getID()));
// Restart workflow
this.startWithoutNotify(context, wsi);
context.restoreAuthSystemState();
}
/**
* Return the workflow item to the workspace of the submitter. The workflow
* item is removed, and a workspace item created.

View File

@@ -14,10 +14,15 @@ import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.dspace.authorize.AuthorizeException;
import org.dspace.content.DCDate;
import org.dspace.content.MetadataSchemaEnum;
import org.dspace.content.factory.ContentServiceFactory;
import org.dspace.content.service.ItemService;
import org.dspace.core.Context;
import org.dspace.workflow.WorkflowException;
import org.dspace.xmlworkflow.RoleMembers;
import org.dspace.xmlworkflow.WorkflowConfigurationException;
import org.dspace.xmlworkflow.factory.XmlWorkflowServiceFactory;
import org.dspace.xmlworkflow.state.Step;
import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem;
@@ -37,6 +42,8 @@ public abstract class Action {
private WorkflowActionConfig parent;
private static final String ERROR_FIELDS_ATTRIBUTE = "dspace.workflow.error_fields";
private List<String> advancedOptions = new ArrayList<>();
private List<ActionAdvancedInfo> advancedInfo = new ArrayList<>();
/**
* Called when a workflow item becomes eligible for this Action.
@@ -192,4 +199,58 @@ public abstract class Action {
//save updated list
setErrorFields(request, errorFields);
}
/**
* Returns a list of advanced options that the user can select at this action
* @return A list of advanced options of this action, resulting in the next step of the workflow
*/
protected List<String> getAdvancedOptions() {
return advancedOptions;
}
/**
* Returns true if this Action has advanced options, false if it doesn't
* @return true if there are advanced options, false otherwise
*/
protected boolean isAdvanced() {
return !getAdvancedOptions().isEmpty();
}
/**
* Returns a list of advanced info required by the advanced options
* @return A list of advanced info required by the advanced options
*/
protected List<ActionAdvancedInfo> getAdvancedInfo() {
return advancedInfo;
}
/**
* Adds info in the metadata field dc.description.provenance about item being approved containing in which step
* it was approved, which user approved it and the time
*
* @param c DSpace contect
* @param wfi Workflow item we're adding workflow accept provenance on
*/
public void addApprovedProvenance(Context c, XmlWorkflowItem wfi) throws SQLException, AuthorizeException {
ItemService itemService = ContentServiceFactory.getInstance().getItemService();
//Add the provenance for the accept
String now = DCDate.getCurrent().toString();
// Get user's name + email address
String usersName =
XmlWorkflowServiceFactory.getInstance().getXmlWorkflowService().getEPersonName(c.getCurrentUser());
String provDescription = getProvenanceStartId() + " Approved for entry into archive by " + usersName + " on "
+ now + " (GMT) ";
// Add to item as a DC field
c.turnOffAuthorisationSystem();
itemService.addMetadata(c, wfi.getItem(), MetadataSchemaEnum.DC.getName(), "description", "provenance", "en",
provDescription);
itemService.update(c, wfi.getItem());
c.restoreAuthSystemState();
}
}

View File

@@ -0,0 +1,42 @@
/**
* 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.xmlworkflow.state.actions;
/**
* Interface for the shared properties of an 'advancedInfo' section of an advanced workflow {@link Action}
* Implementations of this class will define the specific fields per action that will need to be defined/configured
* to pass along this info to REST endpoint
*/
public abstract class ActionAdvancedInfo {
protected String type;
protected String id;
protected final static String TYPE_PREFIX = "action_info_";
public String getType() {
return type;
}
public void setType(String type) {
this.type = TYPE_PREFIX + type;
}
public String getId() {
return id;
}
/**
* Setter for the Action id to be set.
* This is an MD5 hash of the type and the stringified properties of the advanced info
*
* @param type The type of this Action to be included in the MD5 hash
*/
protected abstract void generateId(String type);
}

View File

@@ -69,4 +69,28 @@ public class WorkflowActionConfig {
return this.processingAction.getOptions();
}
/**
* Returns a list of advanced options this user has on this action, resulting in the next step of the workflow
* @return A list of advanced options of this action, resulting in the next step of the workflow
*/
public List<String> getAdvancedOptions() {
return this.processingAction.getAdvancedOptions();
}
/**
* Returns a boolean depending on whether this action has advanced options
* @return The boolean indicating whether this action has advanced options
*/
public boolean isAdvanced() {
return this.processingAction.isAdvanced();
}
/**
* Returns a Map of info for the advanced options this user has on this action
* @return a Map of info for the advanced options this user has on this action
*/
public List<ActionAdvancedInfo> getAdvancedInfo() {
return this.processingAction.getAdvancedInfo();
}
}

View File

@@ -15,8 +15,6 @@ import javax.servlet.http.HttpServletRequest;
import org.dspace.app.util.Util;
import org.dspace.authorize.AuthorizeException;
import org.dspace.content.DCDate;
import org.dspace.content.MetadataSchemaEnum;
import org.dspace.core.Context;
import org.dspace.xmlworkflow.factory.XmlWorkflowServiceFactory;
import org.dspace.xmlworkflow.state.Step;
@@ -34,8 +32,6 @@ import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem;
*/
public class AcceptEditRejectAction extends ProcessingAction {
private static final String SUBMIT_APPROVE = "submit_approve";
private static final String SUBMIT_REJECT = "submit_reject";
private static final String SUBMITTER_IS_DELETED_PAGE = "submitter_deleted";
//TODO: rename to AcceptAndEditMetadataAction
@@ -53,7 +49,7 @@ public class AcceptEditRejectAction extends ProcessingAction {
case SUBMIT_APPROVE:
return processAccept(c, wfi);
case SUBMIT_REJECT:
return processRejectPage(c, wfi, request);
return super.processRejectPage(c, wfi, request);
case SUBMITTER_IS_DELETED_PAGE:
return processSubmitterIsDeletedPage(c, wfi, request);
default:
@@ -69,33 +65,18 @@ public class AcceptEditRejectAction extends ProcessingAction {
options.add(SUBMIT_APPROVE);
options.add(SUBMIT_REJECT);
options.add(ProcessingAction.SUBMIT_EDIT_METADATA);
options.add(RETURN_TO_POOL);
return options;
}
public ActionResult processAccept(Context c, XmlWorkflowItem wfi)
throws SQLException, AuthorizeException {
//Delete the tasks
addApprovedProvenance(c, wfi);
super.addApprovedProvenance(c, wfi);
return new ActionResult(ActionResult.TYPE.TYPE_OUTCOME, ActionResult.OUTCOME_COMPLETE);
}
public ActionResult processRejectPage(Context c, XmlWorkflowItem wfi, HttpServletRequest request)
throws SQLException, AuthorizeException, IOException {
String reason = request.getParameter("reason");
if (reason == null || 0 == reason.trim().length()) {
addErrorField(request, "reason");
return new ActionResult(ActionResult.TYPE.TYPE_ERROR);
}
// We have pressed reject, so remove the task the user has & put it back
// to a workspace item
XmlWorkflowServiceFactory.getInstance().getXmlWorkflowService().sendWorkflowItemBackSubmission(c, wfi,
c.getCurrentUser(), this.getProvenanceStartId(), reason);
return new ActionResult(ActionResult.TYPE.TYPE_SUBMISSION_PAGE);
}
public ActionResult processSubmitterIsDeletedPage(Context c, XmlWorkflowItem wfi, HttpServletRequest request)
throws SQLException, AuthorizeException, IOException {
if (request.getParameter("submit_delete") != null) {
@@ -111,21 +92,4 @@ public class AcceptEditRejectAction extends ProcessingAction {
return new ActionResult(ActionResult.TYPE.TYPE_PAGE);
}
}
private void addApprovedProvenance(Context c, XmlWorkflowItem wfi) throws SQLException, AuthorizeException {
//Add the provenance for the accept
String now = DCDate.getCurrent().toString();
// Get user's name + email address
String usersName = XmlWorkflowServiceFactory.getInstance().getXmlWorkflowService()
.getEPersonName(c.getCurrentUser());
String provDescription = getProvenanceStartId() + " Approved for entry into archive by "
+ usersName + " on " + now + " (GMT) ";
// Add to item as a DC field
itemService.addMetadata(c, wfi.getItem(), MetadataSchemaEnum.DC.getName(), "description", "provenance", "en",
provDescription);
itemService.update(c, wfi.getItem());
}
}

View File

@@ -14,10 +14,7 @@ import javax.servlet.http.HttpServletRequest;
import org.dspace.app.util.Util;
import org.dspace.authorize.AuthorizeException;
import org.dspace.content.DCDate;
import org.dspace.content.MetadataSchemaEnum;
import org.dspace.core.Context;
import org.dspace.xmlworkflow.factory.XmlWorkflowServiceFactory;
import org.dspace.xmlworkflow.state.Step;
import org.dspace.xmlworkflow.state.actions.ActionResult;
import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem;
@@ -52,7 +49,7 @@ public class FinalEditAction extends ProcessingAction {
switch (Util.getSubmitButton(request, SUBMIT_CANCEL)) {
case SUBMIT_APPROVE:
//Delete the tasks
addApprovedProvenance(c, wfi);
super.addApprovedProvenance(c, wfi);
return new ActionResult(ActionResult.TYPE.TYPE_OUTCOME, ActionResult.OUTCOME_COMPLETE);
default:
//We pressed the leave button so return to our submissions page
@@ -67,25 +64,8 @@ public class FinalEditAction extends ProcessingAction {
List<String> options = new ArrayList<>();
options.add(SUBMIT_APPROVE);
options.add(ProcessingAction.SUBMIT_EDIT_METADATA);
options.add(RETURN_TO_POOL);
return options;
}
private void addApprovedProvenance(Context c, XmlWorkflowItem wfi) throws SQLException, AuthorizeException {
//Add the provenance for the accept
String now = DCDate.getCurrent().toString();
// Get user's name + email address
String usersName = XmlWorkflowServiceFactory.getInstance().getXmlWorkflowService()
.getEPersonName(c.getCurrentUser());
String provDescription = getProvenanceStartId() + " Approved for entry into archive by "
+ usersName + " on " + now + " (GMT) ";
// Add to item as a DC field
itemService.addMetadata(c, wfi.getItem(), MetadataSchemaEnum.DC.getName(), "description", "provenance", "en",
provDescription);
itemService.update(c, wfi.getItem());
}
}

View File

@@ -7,12 +7,16 @@
*/
package org.dspace.xmlworkflow.state.actions.processingaction;
import java.io.IOException;
import java.sql.SQLException;
import javax.servlet.http.HttpServletRequest;
import org.dspace.authorize.AuthorizeException;
import org.dspace.content.service.ItemService;
import org.dspace.core.Context;
import org.dspace.xmlworkflow.service.XmlWorkflowService;
import org.dspace.xmlworkflow.state.actions.Action;
import org.dspace.xmlworkflow.state.actions.ActionResult;
import org.dspace.xmlworkflow.storedcomponents.ClaimedTask;
import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem;
import org.dspace.xmlworkflow.storedcomponents.service.ClaimedTaskService;
@@ -32,9 +36,15 @@ public abstract class ProcessingAction extends Action {
protected ClaimedTaskService claimedTaskService;
@Autowired(required = true)
protected ItemService itemService;
@Autowired
protected XmlWorkflowService xmlWorkflowService;
public static final String SUBMIT_EDIT_METADATA = "submit_edit_metadata";
public static final String SUBMIT_CANCEL = "submit_cancel";
protected static final String SUBMIT_APPROVE = "submit_approve";
protected static final String SUBMIT_REJECT = "submit_reject";
protected static final String RETURN_TO_POOL = "return_to_pool";
protected static final String REJECT_REASON = "reason";
@Override
public boolean isAuthorized(Context context, HttpServletRequest request, XmlWorkflowItem wfi) throws SQLException {
@@ -48,4 +58,31 @@ public abstract class ProcessingAction extends Action {
task.getStepID().equals(getParent().getStep().getId()) &&
task.getActionID().equals(getParent().getId());
}
/**
* Process result when option {@link this#SUBMIT_REJECT} is selected.
* - Sets the reason and workflow step responsible on item in dc.description.provenance
* - Send workflow back to the submission
* If reason is not given => error
*/
public ActionResult processRejectPage(Context c, XmlWorkflowItem wfi, HttpServletRequest request)
throws SQLException, AuthorizeException, IOException {
String reason = request.getParameter(REJECT_REASON);
if (reason == null || 0 == reason.trim().length()) {
addErrorField(request, REJECT_REASON);
return new ActionResult(ActionResult.TYPE.TYPE_ERROR);
}
// We have pressed reject, so remove the task the user has & put it back
// to a workspace item
xmlWorkflowService.sendWorkflowItemBackSubmission(c, wfi, c.getCurrentUser(), this.getProvenanceStartId(),
reason);
return new ActionResult(ActionResult.TYPE.TYPE_SUBMISSION_PAGE);
}
@Override
protected boolean isAdvanced() {
return !getAdvancedOptions().isEmpty();
}
}

View File

@@ -15,8 +15,6 @@ import javax.servlet.http.HttpServletRequest;
import org.dspace.app.util.Util;
import org.dspace.authorize.AuthorizeException;
import org.dspace.content.DCDate;
import org.dspace.content.MetadataSchemaEnum;
import org.dspace.core.Context;
import org.dspace.xmlworkflow.factory.XmlWorkflowServiceFactory;
import org.dspace.xmlworkflow.state.Step;
@@ -36,11 +34,8 @@ public class ReviewAction extends ProcessingAction {
public static final int MAIN_PAGE = 0;
public static final int REJECT_PAGE = 1;
private static final String SUBMIT_APPROVE = "submit_approve";
private static final String SUBMIT_REJECT = "submit_reject";
private static final String SUBMITTER_IS_DELETED_PAGE = "submitter_deleted";
@Override
public void activate(Context c, XmlWorkflowItem wfItem) {
@@ -54,7 +49,7 @@ public class ReviewAction extends ProcessingAction {
case SUBMIT_APPROVE:
return processAccept(c, wfi);
case SUBMIT_REJECT:
return processRejectPage(c, wfi, step, request);
return super.processRejectPage(c, wfi, request);
case SUBMITTER_IS_DELETED_PAGE:
return processSubmitterIsDeletedPage(c, wfi, request);
default:
@@ -69,50 +64,15 @@ public class ReviewAction extends ProcessingAction {
List<String> options = new ArrayList<>();
options.add(SUBMIT_APPROVE);
options.add(SUBMIT_REJECT);
options.add(RETURN_TO_POOL);
return options;
}
public ActionResult processAccept(Context c, XmlWorkflowItem wfi) throws SQLException, AuthorizeException {
//Delete the tasks
addApprovedProvenance(c, wfi);
super.addApprovedProvenance(c, wfi);
return new ActionResult(ActionResult.TYPE.TYPE_OUTCOME, ActionResult.OUTCOME_COMPLETE);
}
private void addApprovedProvenance(Context c, XmlWorkflowItem wfi) throws SQLException, AuthorizeException {
//Add the provenance for the accept
String now = DCDate.getCurrent().toString();
// Get user's name + email address
String usersName = XmlWorkflowServiceFactory.getInstance().getXmlWorkflowService()
.getEPersonName(c.getCurrentUser());
String provDescription = getProvenanceStartId() + " Approved for entry into archive by "
+ usersName + " on " + now + " (GMT) ";
// Add to item as a DC field
itemService.addMetadata(c, wfi.getItem(), MetadataSchemaEnum.DC.getName(), "description", "provenance", "en",
provDescription);
itemService.update(c, wfi.getItem());
}
public ActionResult processRejectPage(Context c, XmlWorkflowItem wfi, Step step, HttpServletRequest request)
throws SQLException, AuthorizeException, IOException {
String reason = request.getParameter("reason");
if (reason == null || 0 == reason.trim().length()) {
request.setAttribute("page", REJECT_PAGE);
addErrorField(request, "reason");
return new ActionResult(ActionResult.TYPE.TYPE_ERROR);
}
//We have pressed reject, so remove the task the user has & put it back to a workspace item
XmlWorkflowServiceFactory.getInstance().getXmlWorkflowService()
.sendWorkflowItemBackSubmission(c, wfi, c.getCurrentUser(),
this.getProvenanceStartId(), reason);
return new ActionResult(ActionResult.TYPE.TYPE_SUBMISSION_PAGE);
}
public ActionResult processSubmitterIsDeletedPage(Context c, XmlWorkflowItem wfi, HttpServletRequest request)
throws SQLException, AuthorizeException, IOException {
if (request.getParameter("submit_delete") != null) {

View File

@@ -7,6 +7,9 @@
*/
package org.dspace.xmlworkflow.state.actions.processingaction;
import static org.dspace.xmlworkflow.state.actions.processingaction.ScoreReviewAction.REVIEW_FIELD;
import static org.dspace.xmlworkflow.state.actions.processingaction.ScoreReviewAction.SCORE_FIELD;
import java.io.IOException;
import java.sql.SQLException;
import java.util.ArrayList;
@@ -19,7 +22,6 @@ import org.dspace.content.MetadataSchemaEnum;
import org.dspace.content.MetadataValue;
import org.dspace.core.Context;
import org.dspace.xmlworkflow.factory.XmlWorkflowServiceFactory;
import org.dspace.xmlworkflow.service.WorkflowRequirementsService;
import org.dspace.xmlworkflow.state.Step;
import org.dspace.xmlworkflow.state.actions.ActionResult;
import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem;
@@ -37,6 +39,7 @@ import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem;
*/
public class ScoreEvaluationAction extends ProcessingAction {
// Minimum aggregate of scores
private int minimumAcceptanceScore;
@Override
@@ -47,43 +50,64 @@ public class ScoreEvaluationAction extends ProcessingAction {
@Override
public ActionResult execute(Context c, XmlWorkflowItem wfi, Step step, HttpServletRequest request)
throws SQLException, AuthorizeException, IOException {
boolean hasPassed = false;
//Retrieve all our scores from the metadata & add em up
// Retrieve all our scores from the metadata & add em up
int scoreMean = getMeanScore(wfi);
//We have passed if we have at least gained our minimum score
boolean hasPassed = getMinimumAcceptanceScore() <= scoreMean;
//Whether or not we have passed, clear our score information
itemService.clearMetadata(c, wfi.getItem(), SCORE_FIELD.schema, SCORE_FIELD.element, SCORE_FIELD.qualifier,
Item.ANY);
if (hasPassed) {
this.addRatingInfoToProv(c, wfi, scoreMean);
return new ActionResult(ActionResult.TYPE.TYPE_OUTCOME, ActionResult.OUTCOME_COMPLETE);
} else {
//We haven't passed, reject our item
XmlWorkflowServiceFactory.getInstance().getXmlWorkflowService()
.sendWorkflowItemBackSubmission(c, wfi, c.getCurrentUser(), this.getProvenanceStartId(),
"The item was reject due to a bad review score.");
return new ActionResult(ActionResult.TYPE.TYPE_SUBMISSION_PAGE);
}
}
private int getMeanScore(XmlWorkflowItem wfi) {
List<MetadataValue> scores = itemService
.getMetadata(wfi.getItem(), WorkflowRequirementsService.WORKFLOW_SCHEMA, "score", null, Item.ANY);
.getMetadata(wfi.getItem(), SCORE_FIELD.schema, SCORE_FIELD.element, SCORE_FIELD.qualifier, Item.ANY);
int scoreMean = 0;
if (0 < scores.size()) {
int totalScoreCount = 0;
for (MetadataValue score : scores) {
totalScoreCount += Integer.parseInt(score.getValue());
}
int scoreMean = totalScoreCount / scores.size();
//We have passed if we have at least gained our minimum score
hasPassed = getMinimumAcceptanceScore() <= scoreMean;
//Wether or not we have passed, clear our score information
itemService
.clearMetadata(c, wfi.getItem(), WorkflowRequirementsService.WORKFLOW_SCHEMA, "score", null, Item.ANY);
scoreMean = totalScoreCount / scores.size();
}
return scoreMean;
}
String provDescription = getProvenanceStartId() + " Approved for entry into archive with a score of: " +
scoreMean;
itemService.addMetadata(c, wfi.getItem(), MetadataSchemaEnum.DC.getName(),
"description", "provenance", "en", provDescription);
itemService.update(c, wfi.getItem());
private void addRatingInfoToProv(Context c, XmlWorkflowItem wfi, int scoreMean)
throws SQLException, AuthorizeException {
StringBuilder provDescription = new StringBuilder();
provDescription.append(String.format("%s Approved for entry into archive with a score of: %s",
getProvenanceStartId(), scoreMean));
List<MetadataValue> reviews = itemService
.getMetadata(wfi.getItem(), REVIEW_FIELD.schema, REVIEW_FIELD.element, REVIEW_FIELD.qualifier, Item.ANY);
if (!reviews.isEmpty()) {
provDescription.append(" | Reviews: ");
}
if (hasPassed) {
return new ActionResult(ActionResult.TYPE.TYPE_OUTCOME, ActionResult.OUTCOME_COMPLETE);
} else {
//We haven't passed, reject our item
XmlWorkflowServiceFactory.getInstance().getXmlWorkflowService()
.sendWorkflowItemBackSubmission(c, wfi, c.getCurrentUser(),
this.getProvenanceStartId(),
"The item was reject due to a bad review score.");
return new ActionResult(ActionResult.TYPE.TYPE_SUBMISSION_PAGE);
for (MetadataValue review : reviews) {
provDescription.append(String.format("; %s", review.getValue()));
}
c.turnOffAuthorisationSystem();
itemService.addMetadata(c, wfi.getItem(), MetadataSchemaEnum.DC.getName(),
"description", "provenance", "en", provDescription.toString());
itemService.update(c, wfi.getItem());
c.restoreAuthSystemState();
}
@Override
public List<String> getOptions() {
return new ArrayList<>();
List<String> options = new ArrayList<>();
options.add(RETURN_TO_POOL);
return options;
}
public int getMinimumAcceptanceScore() {

View File

@@ -9,14 +9,20 @@ package org.dspace.xmlworkflow.state.actions.processingaction;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.dspace.app.util.Util;
import org.dspace.authorize.AuthorizeException;
import org.dspace.content.MetadataFieldName;
import org.dspace.core.Context;
import org.dspace.xmlworkflow.service.WorkflowRequirementsService;
import org.dspace.xmlworkflow.state.Step;
import org.dspace.xmlworkflow.state.actions.ActionAdvancedInfo;
import org.dspace.xmlworkflow.state.actions.ActionResult;
import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem;
@@ -24,40 +30,121 @@ import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem;
* This action will allow multiple users to rate a certain item
* if the mean of this score is higher then the minimum score the
* item will be sent to the next action/step else it will be rejected
*
* @author Bram De Schouwer (bram.deschouwer at dot com)
* @author Kevin Van de Velde (kevin at atmire dot com)
* @author Ben Bosman (ben at atmire dot com)
* @author Mark Diggory (markd at atmire dot com)
*/
public class ScoreReviewAction extends ProcessingAction {
private static final Logger log = LogManager.getLogger(ScoreReviewAction.class);
private static final String SUBMIT_SCORE = "submit_score";
// Option(s)
public static final String SUBMIT_SCORE = "submit_score";
// Response param(s)
private static final String SCORE = "score";
private static final String REVIEW = "review";
// Metadata fields to save params in
public static final MetadataFieldName SCORE_FIELD =
new MetadataFieldName(WorkflowRequirementsService.WORKFLOW_SCHEMA, SCORE, null);
public static final MetadataFieldName REVIEW_FIELD =
new MetadataFieldName(WorkflowRequirementsService.WORKFLOW_SCHEMA, REVIEW, null);
// Whether or not it is required that a text review is added to the rating
private boolean descriptionRequired;
// Maximum value rating is allowed to be
private int maxValue;
@Override
public void activate(Context c, XmlWorkflowItem wf) {
// empty
}
@Override
public ActionResult execute(Context c, XmlWorkflowItem wfi, Step step, HttpServletRequest request)
throws SQLException, AuthorizeException {
if (request.getParameter(SUBMIT_SCORE) != null) {
int score = Util.getIntParameter(request, "score");
//Add our score to the metadata
itemService.addMetadata(c, wfi.getItem(), WorkflowRequirementsService.WORKFLOW_SCHEMA, "score", null, null,
String.valueOf(score));
itemService.update(c, wfi.getItem());
return new ActionResult(ActionResult.TYPE.TYPE_OUTCOME, ActionResult.OUTCOME_COMPLETE);
} else {
//We have pressed the leave button so return to our submission page
return new ActionResult(ActionResult.TYPE.TYPE_SUBMISSION_PAGE);
throws SQLException, AuthorizeException {
if (super.isOptionInParam(request) &&
StringUtils.equalsIgnoreCase(Util.getSubmitButton(request, SUBMIT_CANCEL), SUBMIT_SCORE)) {
return processSetRating(c, wfi, request);
}
return new ActionResult(ActionResult.TYPE.TYPE_CANCEL);
}
private ActionResult processSetRating(Context c, XmlWorkflowItem wfi, HttpServletRequest request)
throws SQLException, AuthorizeException {
int score = Util.getIntParameter(request, SCORE);
String review = request.getParameter(REVIEW);
if (!this.checkRequestValid(score, review)) {
return new ActionResult(ActionResult.TYPE.TYPE_ERROR);
}
//Add our rating and review to the metadata
itemService.addMetadata(c, wfi.getItem(), SCORE_FIELD.schema, SCORE_FIELD.element, SCORE_FIELD.qualifier, null,
String.valueOf(score));
if (StringUtils.isNotBlank(review)) {
itemService.addMetadata(c, wfi.getItem(), REVIEW_FIELD.schema, REVIEW_FIELD.element,
REVIEW_FIELD.qualifier, null, String.format("%s - %s", score, review));
}
itemService.update(c, wfi.getItem());
return new ActionResult(ActionResult.TYPE.TYPE_OUTCOME, ActionResult.OUTCOME_COMPLETE);
}
/**
* Request is not valid if:
* - Given score is higher than configured maxValue
* - There is no review given and description is configured to be required
* Config in workflow-actions.xml
*
* @param score Given score rating from request
* @param review Given review/description from request
* @return True if valid request params with config, otherwise false
*/
private boolean checkRequestValid(int score, String review) {
if (score > this.maxValue) {
log.error("{} only allows max rating {} (config workflow-actions.xml), given rating of " +
"{} not allowed.", this.getClass().toString(), this.maxValue, score);
return false;
}
if (StringUtils.isBlank(review) && this.descriptionRequired) {
log.error("{} has config descriptionRequired=true (workflow-actions.xml), so rating " +
"requests without 'review' query param containing description are not allowed",
this.getClass().toString());
return false;
}
return true;
}
@Override
public List<String> getOptions() {
return List.of(SUBMIT_SCORE, RETURN_TO_POOL);
}
@Override
protected List<String> getAdvancedOptions() {
return Arrays.asList(SUBMIT_SCORE);
}
@Override
protected List<ActionAdvancedInfo> getAdvancedInfo() {
ScoreReviewActionAdvancedInfo scoreReviewActionAdvancedInfo = new ScoreReviewActionAdvancedInfo();
scoreReviewActionAdvancedInfo.setDescriptionRequired(descriptionRequired);
scoreReviewActionAdvancedInfo.setMaxValue(maxValue);
scoreReviewActionAdvancedInfo.setType(SUBMIT_SCORE);
scoreReviewActionAdvancedInfo.generateId(SUBMIT_SCORE);
return Collections.singletonList(scoreReviewActionAdvancedInfo);
}
/**
* Setter that sets the descriptionRequired property from workflow-actions.xml
* @param descriptionRequired boolean whether a description is required
*/
public void setDescriptionRequired(boolean descriptionRequired) {
this.descriptionRequired = descriptionRequired;
}
/**
* Setter that sets the maxValue property from workflow-actions.xml
* @param maxValue integer of the maximum allowed value
*/
public void setMaxValue(int maxValue) {
this.maxValue = maxValue;
}
}

View File

@@ -0,0 +1,45 @@
/**
* 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.xmlworkflow.state.actions.processingaction;
import org.dspace.xmlworkflow.state.actions.ActionAdvancedInfo;
import org.springframework.util.DigestUtils;
/**
* Class that holds the advanced information needed for the
* {@link org.dspace.xmlworkflow.state.actions.processingaction.ScoreReviewAction}
* See config {@code workflow-actions.cfg}
*/
public class ScoreReviewActionAdvancedInfo extends ActionAdvancedInfo {
private boolean descriptionRequired;
private int maxValue;
public boolean isDescriptionRequired() {
return descriptionRequired;
}
public void setDescriptionRequired(boolean descriptionRequired) {
this.descriptionRequired = descriptionRequired;
}
public int getMaxValue() {
return maxValue;
}
public void setMaxValue(int maxValue) {
this.maxValue = maxValue;
}
@Override
public void generateId(String type) {
String idString = type
+ ";descriptionRequired," + descriptionRequired
+ ";maxValue," + maxValue;
super.id = DigestUtils.md5DigestAsHex(idString.getBytes());
}
}

View File

@@ -9,17 +9,27 @@ package org.dspace.xmlworkflow.state.actions.processingaction;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import javax.annotation.Nullable;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.dspace.app.util.Util;
import org.dspace.authorize.AuthorizeException;
import org.dspace.core.Context;
import org.dspace.eperson.EPerson;
import org.dspace.eperson.Group;
import org.dspace.eperson.service.EPersonService;
import org.dspace.eperson.service.GroupService;
import org.dspace.services.ConfigurationService;
import org.dspace.xmlworkflow.Role;
import org.dspace.xmlworkflow.state.Step;
import org.dspace.xmlworkflow.state.actions.ActionAdvancedInfo;
import org.dspace.xmlworkflow.state.actions.ActionResult;
import org.dspace.xmlworkflow.storedcomponents.WorkflowItemRole;
import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem;
@@ -37,13 +47,13 @@ import org.springframework.beans.factory.annotation.Autowired;
*/
public class SelectReviewerAction extends ProcessingAction {
public static final int SEARCH_RESULTS_PAGE = 1;
public static final int RESULTS_PER_PAGE = 5;
private static final Logger log = LogManager.getLogger(SelectReviewerAction.class);
private static final String SUBMIT_CANCEL = "submit_cancel";
private static final String SUBMIT_SEARCH = "submit_search";
private static final String SUBMIT_SELECT_REVIEWER = "submit_select_reviewer_";
private static final String SUBMIT_SELECT_REVIEWER = "submit_select_reviewer";
private static final String PARAM_REVIEWER = "eperson";
private static final String CONFIG_REVIEWER_GROUP = "action.selectrevieweraction.group";
private Role role;
@@ -53,6 +63,15 @@ public class SelectReviewerAction extends ProcessingAction {
@Autowired(required = true)
private WorkflowItemRoleService workflowItemRoleService;
@Autowired
private ConfigurationService configurationService;
@Autowired
private GroupService groupService;
private static Group selectFromReviewsGroup;
private static boolean selectFromReviewsGroupInitialised = false;
@Override
public void activate(Context c, XmlWorkflowItem wf) {
@@ -60,56 +79,128 @@ public class SelectReviewerAction extends ProcessingAction {
@Override
public ActionResult execute(Context c, XmlWorkflowItem wfi, Step step, HttpServletRequest request)
throws SQLException, AuthorizeException {
throws SQLException, AuthorizeException {
String submitButton = Util.getSubmitButton(request, SUBMIT_CANCEL);
//Check if our user has pressed cancel
if (submitButton.equals(SUBMIT_CANCEL)) {
//Send us back to the submissions page
return new ActionResult(ActionResult.TYPE.TYPE_CANCEL);
} else if (submitButton.equals(SUBMIT_SEARCH)) {
//Perform the search
String query = request.getParameter("query");
int page = Util.getIntParameter(request, "result-page");
if (page == -1) {
page = 0;
}
int resultCount = ePersonService.searchResultCount(c, query);
List<EPerson> epeople = ePersonService.search(c, query, page * RESULTS_PER_PAGE, RESULTS_PER_PAGE);
request.setAttribute("eperson-result-count", resultCount);
request.setAttribute("eperson-results", epeople);
request.setAttribute("result-page", page);
request.setAttribute("page", SEARCH_RESULTS_PAGE);
return new ActionResult(ActionResult.TYPE.TYPE_PAGE, SEARCH_RESULTS_PAGE);
} else if (submitButton.startsWith(SUBMIT_SELECT_REVIEWER)) {
//Retrieve the identifier of the eperson which will do the reviewing
UUID reviewerId = UUID.fromString(submitButton.substring(submitButton.lastIndexOf("_") + 1));
EPerson reviewer = ePersonService.find(c, reviewerId);
//Assign the reviewer. The workflowitemrole will be translated into a task in the autoassign
WorkflowItemRole workflowItemRole = workflowItemRoleService.create(c);
workflowItemRole.setEPerson(reviewer);
workflowItemRole.setRoleId(getRole().getId());
workflowItemRole.setWorkflowItem(wfi);
workflowItemRoleService.update(c, workflowItemRole);
return new ActionResult(ActionResult.TYPE.TYPE_OUTCOME, ActionResult.OUTCOME_COMPLETE);
return processSelectReviewers(c, wfi, request);
}
//There are only 2 active buttons on this page, so if anything else happens just return an error
return new ActionResult(ActionResult.TYPE.TYPE_ERROR);
}
/**
* Method to handle the {@link this#SUBMIT_SELECT_REVIEWER} action:
* - will retrieve the reviewer(s) uuid from request (param {@link this#PARAM_REVIEWER})
* - assign them to a {@link WorkflowItemRole}
* - In {@link org.dspace.xmlworkflow.state.actions.userassignment.AutoAssignAction} these reviewer(s) will get
* claimed task for this {@link XmlWorkflowItem}
* Will result in error if:
* - No reviewer(s) uuid in request (param {@link this#PARAM_REVIEWER})
* - If none of the reviewer(s) uuid passed along result in valid EPerson
* - If the reviewer(s) passed along are not in {@link this#selectFromReviewsGroup} when it is set
*
* @param c current DSpace session
* @param wfi the item on which the action is to be performed
* @param request the current client request
* @return the result of performing the action
*/
private ActionResult processSelectReviewers(Context c, XmlWorkflowItem wfi, HttpServletRequest request)
throws SQLException, AuthorizeException {
//Retrieve the identifier of the eperson which will do the reviewing
String[] reviewerIds = request.getParameterValues(PARAM_REVIEWER);
if (ArrayUtils.isEmpty(reviewerIds)) {
return new ActionResult(ActionResult.TYPE.TYPE_ERROR);
}
List<EPerson> reviewers = new ArrayList<>();
for (String reviewerId : reviewerIds) {
EPerson reviewer = ePersonService.find(c, UUID.fromString(reviewerId));
if (reviewer == null) {
log.warn("No EPerson found with uuid {}", reviewerId);
} else {
reviewers.add(reviewer);
}
}
if (!this.checkReviewersValid(c, reviewers)) {
return new ActionResult(ActionResult.TYPE.TYPE_ERROR);
}
createWorkflowItemRole(c, wfi, reviewers);
return new ActionResult(ActionResult.TYPE.TYPE_OUTCOME, ActionResult.OUTCOME_COMPLETE);
}
private boolean checkReviewersValid(Context c, List<EPerson> reviewers) throws SQLException {
if (reviewers.size() == 0) {
return false;
}
Group group = this.getGroup(c);
if (group != null) {
for (EPerson reviewer: reviewers) {
if (!groupService.isMember(c, reviewer, group)) {
log.error("Reviewers selected must be member of group {}", group.getID());
return false;
}
}
}
return true;
}
private WorkflowItemRole createWorkflowItemRole(Context c, XmlWorkflowItem wfi, List<EPerson> reviewers)
throws SQLException, AuthorizeException {
WorkflowItemRole workflowItemRole = workflowItemRoleService.create(c);
workflowItemRole.setRoleId(getRole().getId());
workflowItemRole.setWorkflowItem(wfi);
if (reviewers.size() == 1) {
// 1 reviewer in workflowitemrole => will be translated into a claimed task in the autoassign
workflowItemRole.setEPerson(reviewers.get(0));
} else {
// multiple reviewers, create a temporary group and assign this group, the workflowitemrole will be
// translated into a claimed task for reviewers in the autoassign, where group will be deleted
c.turnOffAuthorisationSystem();
Group selectedReviewsGroup = groupService.create(c);
groupService.setName(selectedReviewsGroup, "selectedReviewsGroup_" + wfi.getID());
for (EPerson reviewer : reviewers) {
groupService.addMember(c, selectedReviewsGroup, reviewer);
}
workflowItemRole.setGroup(selectedReviewsGroup);
c.restoreAuthSystemState();
}
workflowItemRoleService.update(c, workflowItemRole);
return workflowItemRole;
}
@Override
public List<String> getOptions() {
List<String> options = new ArrayList<>();
options.add(SUBMIT_SEARCH);
options.add(SUBMIT_SELECT_REVIEWER);
options.add(RETURN_TO_POOL);
return options;
}
@Override
protected List<String> getAdvancedOptions() {
return Arrays.asList(SUBMIT_SELECT_REVIEWER);
}
@Override
protected List<ActionAdvancedInfo> getAdvancedInfo() {
List<ActionAdvancedInfo> advancedInfo = new ArrayList<>();
SelectReviewerActionAdvancedInfo selectReviewerActionAdvancedInfo = new SelectReviewerActionAdvancedInfo();
if (getGroup(null) != null) {
selectReviewerActionAdvancedInfo.setGroup(getGroup(null).getID().toString());
}
selectReviewerActionAdvancedInfo.setType(SUBMIT_SELECT_REVIEWER);
selectReviewerActionAdvancedInfo.generateId(SUBMIT_SELECT_REVIEWER);
advancedInfo.add(selectReviewerActionAdvancedInfo);
return advancedInfo;
}
public Role getRole() {
return role;
}
@@ -118,4 +209,49 @@ public class SelectReviewerAction extends ProcessingAction {
public void setRole(Role role) {
this.role = role;
}
/**
* Get the Reviewer group from the "action.selectrevieweraction.group" property in actions.cfg by its UUID or name
* Returns null if no (valid) group configured
*
* @return configured reviewers Group from property or null if none
*/
private Group getGroup(@Nullable Context context) {
if (selectFromReviewsGroupInitialised) {
return this.selectFromReviewsGroup;
}
if (context == null) {
context = new Context();
}
String groupIdOrName = configurationService.getProperty(CONFIG_REVIEWER_GROUP);
if (StringUtils.isNotBlank(groupIdOrName)) {
Group group = null;
try {
// try to get group by name
group = groupService.findByName(context, groupIdOrName);
if (group == null) {
// try to get group by uuid if not a name
group = groupService.find(context, UUID.fromString(groupIdOrName));
}
} catch (Exception e) {
// There is an issue with the reviewer group that is set; if it is not set then can be chosen
// from all epeople
log.error("Issue with determining matching group for config {}={} for reviewer group of " +
"select reviewers workflow", CONFIG_REVIEWER_GROUP, groupIdOrName);
}
this.selectFromReviewsGroup = group;
}
selectFromReviewsGroupInitialised = true;
return this.selectFromReviewsGroup;
}
/**
* To be used by IT, e.g. {@code XmlWorkflowServiceIT}, when defining new 'Reviewers' group
*/
static public void resetGroup() {
selectFromReviewsGroup = null;
selectFromReviewsGroupInitialised = false;
}
}

View File

@@ -0,0 +1,36 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.xmlworkflow.state.actions.processingaction;
import org.dspace.xmlworkflow.state.actions.ActionAdvancedInfo;
import org.springframework.util.DigestUtils;
/**
* Class that holds the advanced information needed for the
* {@link org.dspace.xmlworkflow.state.actions.processingaction.SelectReviewerAction}
* See config {@code workflow-actions.cfg}
*/
public class SelectReviewerActionAdvancedInfo extends ActionAdvancedInfo {
private String group;
public String getGroup() {
return group;
}
public void setGroup(String group) {
this.group = group;
}
@Override
public void generateId(String type) {
String idString = type
+ ";group," + group;
super.id = DigestUtils.md5DigestAsHex(idString.getBytes());
}
}

View File

@@ -13,11 +13,15 @@ import java.util.ArrayList;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.dspace.app.util.Util;
import org.dspace.authorize.AuthorizeException;
import org.dspace.content.DCDate;
import org.dspace.content.MetadataSchemaEnum;
import org.dspace.content.WorkspaceItem;
import org.dspace.content.factory.ContentServiceFactory;
import org.dspace.core.Context;
import org.dspace.eperson.EPerson;
import org.dspace.workflow.WorkflowException;
import org.dspace.xmlworkflow.factory.XmlWorkflowServiceFactory;
import org.dspace.xmlworkflow.state.Step;
import org.dspace.xmlworkflow.state.actions.ActionResult;
@@ -34,39 +38,59 @@ import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem;
* @author Mark Diggory (markd at atmire dot com)
*/
public class SingleUserReviewAction extends ProcessingAction {
public static final int MAIN_PAGE = 0;
public static final int REJECT_PAGE = 1;
public static final int SUBMITTER_IS_DELETED_PAGE = 2;
private static final Logger log = LogManager.getLogger(SingleUserReviewAction.class);
public static final int OUTCOME_REJECT = 1;
protected static final String SUBMIT_APPROVE = "submit_approve";
protected static final String SUBMIT_REJECT = "submit_reject";
protected static final String SUBMIT_DECLINE_TASK = "submit_decline_task";
@Override
public void activate(Context c, XmlWorkflowItem wfItem) {
// empty
}
@Override
public ActionResult execute(Context c, XmlWorkflowItem wfi, Step step, HttpServletRequest request)
throws SQLException, AuthorizeException, IOException {
int page = Util.getIntParameter(request, "page");
switch (page) {
case MAIN_PAGE:
return processMainPage(c, wfi, step, request);
case REJECT_PAGE:
return processRejectPage(c, wfi, step, request);
case SUBMITTER_IS_DELETED_PAGE:
return processSubmitterIsDeletedPage(c, wfi, request);
throws SQLException, AuthorizeException, IOException, WorkflowException {
if (!super.isOptionInParam(request)) {
return new ActionResult(ActionResult.TYPE.TYPE_CANCEL);
}
switch (Util.getSubmitButton(request, SUBMIT_CANCEL)) {
case SUBMIT_APPROVE:
return processAccept(c, wfi);
case SUBMIT_REJECT:
return processReject(c, wfi, request);
case SUBMIT_DECLINE_TASK:
return processDecline(c, wfi);
default:
return new ActionResult(ActionResult.TYPE.TYPE_CANCEL);
}
}
/**
* Process {@link super#SUBMIT_REJECT} on this action, will either:
* - If submitter of item no longer exists => Permanently delete corresponding item (no wfi/wsi remaining)
* - Otherwise: reject item back to submission => becomes wsi of submitter again
*/
private ActionResult processReject(Context c, XmlWorkflowItem wfi, HttpServletRequest request)
throws SQLException, IOException, AuthorizeException {
if (wfi.getSubmitter() == null) {
// If the original submitter is no longer there, delete the task
return processDelete(c, wfi);
} else {
return super.processRejectPage(c, wfi, request);
}
}
/**
* Accept the workflow item => last step in workflow so will be archived
* Info on step & reviewer will be added on metadata dc.description.provenance of resulting item
*/
public ActionResult processAccept(Context c, XmlWorkflowItem wfi) throws SQLException, AuthorizeException {
super.addApprovedProvenance(c, wfi);
return new ActionResult(ActionResult.TYPE.TYPE_OUTCOME, ActionResult.OUTCOME_COMPLETE);
}
@Override
public List<String> getOptions() {
List<String> options = new ArrayList<>();
@@ -76,87 +100,29 @@ public class SingleUserReviewAction extends ProcessingAction {
return options;
}
public ActionResult processMainPage(Context c, XmlWorkflowItem wfi, Step step, HttpServletRequest request)
throws SQLException, AuthorizeException {
if (request.getParameter(SUBMIT_APPROVE) != null) {
//Delete the tasks
addApprovedProvenance(c, wfi);
return new ActionResult(ActionResult.TYPE.TYPE_OUTCOME, ActionResult.OUTCOME_COMPLETE);
} else if (request.getParameter(SUBMIT_REJECT) != null) {
// Make sure we indicate which page we want to process
if (wfi.getSubmitter() == null) {
request.setAttribute("page", SUBMITTER_IS_DELETED_PAGE);
} else {
request.setAttribute("page", REJECT_PAGE);
}
// We have pressed reject item, so take the user to a page where they can reject
return new ActionResult(ActionResult.TYPE.TYPE_PAGE);
} else if (request.getParameter(SUBMIT_DECLINE_TASK) != null) {
return new ActionResult(ActionResult.TYPE.TYPE_OUTCOME, OUTCOME_REJECT);
} else {
//We pressed the leave button so return to our submissions page
return new ActionResult(ActionResult.TYPE.TYPE_SUBMISSION_PAGE);
}
}
private void addApprovedProvenance(Context c, XmlWorkflowItem wfi) throws SQLException, AuthorizeException {
//Add the provenance for the accept
String now = DCDate.getCurrent().toString();
// Get user's name + email address
String usersName = XmlWorkflowServiceFactory.getInstance().getXmlWorkflowService()
.getEPersonName(c.getCurrentUser());
String provDescription = getProvenanceStartId() + " Approved for entry into archive by "
+ usersName + " on " + now + " (GMT) ";
// Add to item as a DC field
itemService.addMetadata(c, wfi.getItem(), MetadataSchemaEnum.DC.getName(), "description", "provenance", "en",
provDescription);
itemService.update(c, wfi.getItem());
}
public ActionResult processRejectPage(Context c, XmlWorkflowItem wfi, Step step, HttpServletRequest request)
/**
* Since original submitter no longer exists, workflow item is permanently deleted
*/
private ActionResult processDelete(Context c, XmlWorkflowItem wfi)
throws SQLException, AuthorizeException, IOException {
if (request.getParameter("submit_reject") != null) {
String reason = request.getParameter("reason");
if (reason == null || 0 == reason.trim().length()) {
request.setAttribute("page", REJECT_PAGE);
addErrorField(request, "reason");
return new ActionResult(ActionResult.TYPE.TYPE_ERROR);
}
//We have pressed reject, so remove the task the user has & put it back to a workspace item
XmlWorkflowServiceFactory.getInstance().getXmlWorkflowService()
.sendWorkflowItemBackSubmission(c, wfi, c.getCurrentUser(),
this.getProvenanceStartId(), reason);
return new ActionResult(ActionResult.TYPE.TYPE_SUBMISSION_PAGE);
} else {
//Cancel, go back to the main task page
request.setAttribute("page", MAIN_PAGE);
return new ActionResult(ActionResult.TYPE.TYPE_PAGE);
}
EPerson user = c.getCurrentUser();
c.turnOffAuthorisationSystem();
WorkspaceItem workspaceItem = XmlWorkflowServiceFactory.getInstance().getXmlWorkflowService()
.abort(c, wfi, user);
ContentServiceFactory.getInstance().getWorkspaceItemService().deleteAll(c, workspaceItem);
c.restoreAuthSystemState();
return new ActionResult(ActionResult.TYPE.TYPE_SUBMISSION_PAGE);
}
public ActionResult processSubmitterIsDeletedPage(Context c, XmlWorkflowItem wfi, HttpServletRequest request)
throws SQLException, AuthorizeException, IOException {
if (request.getParameter("submit_delete") != null) {
XmlWorkflowServiceFactory.getInstance().getXmlWorkflowService()
.deleteWorkflowByWorkflowItem(c, wfi, c.getCurrentUser());
// Delete and send user back to myDspace page
return new ActionResult(ActionResult.TYPE.TYPE_SUBMISSION_PAGE);
} else if (request.getParameter("submit_keep_it") != null) {
// Do nothing, just send it back to myDspace page
return new ActionResult(ActionResult.TYPE.TYPE_SUBMISSION_PAGE);
} else {
//Cancel, go back to the main task page
request.setAttribute("page", MAIN_PAGE);
return new ActionResult(ActionResult.TYPE.TYPE_PAGE);
}
/**
* Selected reviewer declines to review task, then the workflow is aborted and restarted
*/
private ActionResult processDecline(Context c, XmlWorkflowItem wfi)
throws SQLException, IOException, AuthorizeException, WorkflowException {
c.turnOffAuthorisationSystem();
xmlWorkflowService.restartWorkflow(c, wfi, c.getCurrentUser(), this.getProvenanceStartId());
c.restoreAuthSystemState();
return new ActionResult(ActionResult.TYPE.TYPE_SUBMISSION_PAGE);
}
}

View File

@@ -80,6 +80,10 @@ public class AutoAssignAction extends UserSelectionAction {
}
//Delete our workflow item role since the users have been assigned
workflowItemRoleService.delete(c, workflowItemRole);
if (role.isDeleteTemporaryGroup() && workflowItemRole.getGroup() != null) {
// Delete temporary groups created after members have workflow task assigned
groupService.delete(c, workflowItemRole.getGroup());
}
}
} else {
log.warn(LogHelper.getHeader(c, "Error while executing auto assign action",
@@ -127,7 +131,7 @@ public class AutoAssignAction extends UserSelectionAction {
protected void createTaskForEPerson(Context c, XmlWorkflowItem wfi, Step step, WorkflowActionConfig actionConfig,
EPerson user) throws SQLException, AuthorizeException, IOException {
if (claimedTaskService.find(c, wfi, step.getId(), actionConfig.getId()) != null) {
workflowRequirementsService.addClaimedUser(c, wfi, step, c.getCurrentUser());
workflowRequirementsService.addClaimedUser(c, wfi, step, user);
XmlWorkflowServiceFactory.getInstance().getXmlWorkflowService()
.createOwnedTask(c, wfi, step, actionConfig, user);
}

View File

@@ -138,6 +138,10 @@ public class ClaimAction extends UserSelectionAction {
RoleMembers roleMembers = role.getMembers(context, wfi);
ArrayList<EPerson> epersons = roleMembers.getAllUniqueMembers(context);
if (epersons.isEmpty() || step.getRequiredUsers() > epersons.size()) {
log.warn(String.format("There must be at least %s ePerson(s) in the group",
step.getRequiredUsers()));
}
return !(epersons.isEmpty() || step.getRequiredUsers() > epersons.size());
} else {
// We don't have a role and do have a UI so throw a workflow exception

View File

@@ -0,0 +1,44 @@
--
-- 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/
--
-----------------------------------------------------------------------------------
-- ADD table subscription_parameter
-----------------------------------------------------------------------------------
CREATE SEQUENCE if NOT EXISTS subscription_parameter_seq;
-------------------------------------------------------
-- Create the subscription_parameter table
-------------------------------------------------------
CREATE TABLE if NOT EXISTS subscription_parameter
(
subscription_parameter_id INTEGER NOT NULL,
name CHARACTER VARYING(255),
value CHARACTER VARYING(255),
subscription_id INTEGER NOT NULL,
CONSTRAINT subscription_parameter_pkey PRIMARY KEY (subscription_parameter_id),
CONSTRAINT subscription_parameter_subscription_fkey FOREIGN KEY (subscription_id) REFERENCES subscription (subscription_id) ON DELETE CASCADE
);
--
ALTER TABLE subscription ADD COLUMN if NOT EXISTS dspace_object_id UUID;
--
ALTER TABLE subscription ADD COLUMN if NOT EXISTS type CHARACTER VARYING(255);
--
ALTER TABLE subscription DROP CONSTRAINT IF EXISTS subscription_dspaceobject_fkey;
ALTER TABLE subscription ADD CONSTRAINT subscription_dspaceobject_fkey FOREIGN KEY (dspace_object_id) REFERENCES dspaceobject (uuid);
-- --
UPDATE subscription set dspace_object_id = collection_id , type = 'content';
--
ALTER TABLE subscription DROP CONSTRAINT IF EXISTS Subscription_collection_id_fk;
--
ALTER TABLE subscription DROP COLUMN IF EXISTS collection_id;

View File

@@ -0,0 +1,22 @@
--
-- The contents of this file are subject to the license and copyright
-- detailed in the LICENSE and NOTICE files at the root of the source
-- tree and available online at
--
-- http://www.dspace.org/license/
--
-----------------------------------------------------------------------------------
-- Create table for System wide alerts
-----------------------------------------------------------------------------------
CREATE SEQUENCE alert_id_seq;
CREATE TABLE systemwidealert
(
alert_id INTEGER NOT NULL PRIMARY KEY,
message VARCHAR(512),
allow_sessions VARCHAR(64),
countdown_to TIMESTAMP,
active BOOLEAN
);

View File

@@ -0,0 +1,45 @@
--
-- 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/
--
-----------------------------------------------------------------------------------
-- ADD table subscription_parameter
-----------------------------------------------------------------------------------
CREATE SEQUENCE if NOT EXISTS subscription_parameter_seq;
-----------------------------------------------------------------------------------
-- ADD table subscription_parameter
-----------------------------------------------------------------------------------
CREATE TABLE if NOT EXISTS subscription_parameter
(
subscription_parameter_id INTEGER NOT NULL,
name VARCHAR(255),
value VARCHAR(255),
subscription_id INTEGER NOT NULL,
CONSTRAINT subscription_parameter_pkey PRIMARY KEY (subscription_parameter_id),
CONSTRAINT subscription_parameter_subscription_fkey FOREIGN KEY (subscription_id)
REFERENCES subscription (subscription_id) ON DELETE CASCADE
);
-- --
ALTER TABLE subscription ADD COLUMN if NOT EXISTS dspace_object_id UUID;
---- --
ALTER TABLE subscription ADD COLUMN if NOT EXISTS type CHARACTER VARYING(255);
--
UPDATE subscription SET dspace_object_id = collection_id , type = 'content';
--
ALTER TABLE subscription DROP CONSTRAINT IF EXISTS subscription_dspaceobject_fkey;
ALTER TABLE subscription ADD CONSTRAINT subscription_dspaceobject_fkey FOREIGN KEY (dspace_object_id) REFERENCES dspaceobject (uuid);
--
ALTER TABLE subscription DROP CONSTRAINT IF EXISTS subscription_collection_id_fkey;
---- --
ALTER TABLE subscription DROP COLUMN IF EXISTS collection_id;
-- --
INSERT INTO subscription_parameter (subscription_parameter_id, name, value, subscription_id)
SELECT getnextid('subscription_parameter'), 'frequency', 'D', subscription_id from "subscription" ;

View File

@@ -0,0 +1,22 @@
--
-- The contents of this file are subject to the license and copyright
-- detailed in the LICENSE and NOTICE files at the root of the source
-- tree and available online at
--
-- http://www.dspace.org/license/
--
-----------------------------------------------------------------------------------
-- Create table for System wide alerts
-----------------------------------------------------------------------------------
CREATE SEQUENCE alert_id_seq;
CREATE TABLE systemwidealert
(
alert_id INTEGER NOT NULL PRIMARY KEY,
message VARCHAR(512),
allow_sessions VARCHAR(64),
countdown_to TIMESTAMP,
active BOOLEAN
);

View File

@@ -0,0 +1,43 @@
--
-- 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/
--
-----------------------------------------------------------------------------------
-- ADD table subscription_parameter
-----------------------------------------------------------------------------------
CREATE SEQUENCE if NOT EXISTS subscription_parameter_seq;
-----------------------------------------------------------------------------------
-- ADD table subscription_parameter
-----------------------------------------------------------------------------------
CREATE TABLE if NOT EXISTS subscription_parameter
(
subscription_parameter_id INTEGER NOT NULL,
name CHARACTER VARYING(255),
value CHARACTER VARYING(255),
subscription_id INTEGER NOT NULL,
CONSTRAINT subscription_parameter_pkey PRIMARY KEY (subscription_parameter_id),
CONSTRAINT subscription_parameter_subscription_fkey FOREIGN KEY (subscription_id) REFERENCES subscription (subscription_id) ON DELETE CASCADE
);
--
ALTER TABLE subscription ADD COLUMN if NOT EXISTS dspace_object_id UUID;
-- --
ALTER TABLE subscription ADD COLUMN if NOT EXISTS type CHARACTER VARYING(255);
---- --
ALTER TABLE subscription DROP CONSTRAINT IF EXISTS subscription_dspaceobject_fkey;
ALTER TABLE subscription ADD CONSTRAINT subscription_dspaceobject_fkey FOREIGN KEY (dspace_object_id) REFERENCES dspaceobject (uuid);
--
UPDATE subscription SET dspace_object_id = collection_id , type = 'content';
--
ALTER TABLE subscription DROP CONSTRAINT IF EXISTS subscription_collection_id_fkey;
-- --
ALTER TABLE subscription DROP COLUMN IF EXISTS collection_id;
-- --
INSERT INTO subscription_parameter (subscription_parameter_id, name, value, subscription_id)
SELECT getnextid('subscription_parameter'), 'frequency', 'D', subscription_id from "subscription" ;

View File

@@ -0,0 +1,22 @@
--
-- The contents of this file are subject to the license and copyright
-- detailed in the LICENSE and NOTICE files at the root of the source
-- tree and available online at
--
-- http://www.dspace.org/license/
--
-----------------------------------------------------------------------------------
-- Create table for System wide alerts
-----------------------------------------------------------------------------------
CREATE SEQUENCE alert_id_seq;
CREATE TABLE systemwidealert
(
alert_id INTEGER NOT NULL PRIMARY KEY,
message VARCHAR(512),
allow_sessions VARCHAR(64),
countdown_to TIMESTAMP,
active BOOLEAN
);

View File

@@ -143,6 +143,12 @@
<processing-class>org.dspace.app.rest.submit.step.SherpaPolicyStep</processing-class>
<type>sherpaPolicy</type>
</step-definition>
<step-definition id="identifiers">
<heading>submit.progressbar.identifiers</heading>
<processing-class>org.dspace.app.rest.submit.step.ShowIdentifiersStep</processing-class>
<type>identifiers</type>
</step-definition>
</step-definitions>
<!-- The submission-definitions map lays out the detailed definition of -->
@@ -169,6 +175,8 @@
<step id="collection"/>
<step id="identifiers"/>
<!--Step will be to Describe the item. -->
<step id="traditionalpageone"/>
<step id="traditionalpagetwo"/>

View File

@@ -43,7 +43,7 @@ dspace.server.url = http://localhost
db.driver = org.h2.Driver
db.dialect=org.hibernate.dialect.H2Dialect
# Use a 10 second database lock timeout to avoid occasional JDBC lock timeout errors
db.url = jdbc:h2:mem:test;LOCK_TIMEOUT=10000;
db.url = jdbc:h2:mem:test;LOCK_TIMEOUT=10000;NON_KEYWORDS=VALUE
db.username = sa
db.password =
# H2's default schema is PUBLIC

View File

@@ -0,0 +1,49 @@
#----------------------------------------------------------------------#
#---------------------IDENTIFIER CONFIGURATIONS------------------------#
#----------------------------------------------------------------------#
# These configs are used for additional identifier configuration such #
# as the Show Identifiers step which can "pre-mint" DOIs and Handles #
#----------------------------------------------------------------------#
# Should configured identifiers (eg handle and DOI) be minted for (future) registration at workspace item creation?
# A handle created at this stage will act just like a regular handle created at archive time.
# A DOI created at this stage will be in a 'PENDING' status while in workspace and workflow.
# At the time of item install, the DOI filter (if any) will be applied and if the item matches the filter, the DOI
# status will be updated to TO_BE_REGISTERED. An administrator can also manually progress the DOI status, overriding
# any filters, in the item status page.
# This option doesn't require the Show Identifiers submission step to be visible.
# Default: false
identifiers.submission.register = false
# This configuration property can be set to a filter name to determine if a PENDING DOI for an item
# should be queued for registration. If the filter doesn't match, the DOI will stay in PENDING or MINTED status
# so that the identifier itself persists in case it is considered for registration in the future.
# See doi-filter and other example filters in item-filters.xml.
# Default (always_true_filter)
identifiers.submission.filter.install = doi_filter
# This optional configuration property can be set to a filter name, in case there are some initial rules to apply
# when first deciding whether a DOI should be be created for a new workspace item with a PENDING status.
# This filter is only applied if identifiers.submission.register is true.
# This filter is updated as submission data is saved.
# Default: (always_true_filter)
identifiers.submission.filter.workspace = doi_filter
# If true, the workspace filter will be applied as submission data is saved. If the filter no longer
# matches the item, the DOI will be shifted into a MINTED status and not displayed in the submission section.
# If false, then once a DOI has been created with PENDING status it will remain that way until final item install
# Default: true
#identifiers.submission.strip_pending_during_submission = true
# This configuration property can be set to a filter name to determine if an item processed by RegisterDOI curation
# task should be eligible for a DOI
identifiers.submission.filter.curation = always_true_filter
# Show Register DOI button in item status page?
# Default: false
identifiers.item-status.register-doi = true
# Which identifier types to show in submission step?
# Default: handle, doi (currently the only supported identifier 'types')
identifiers.submission.display = handle
identifiers.submission.display = doi

View File

@@ -0,0 +1,370 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
The contents of this file are subject to the license and copyright
detailed in the LICENSE and NOTICE files at the root of the source
tree and available online at
http://www.dspace.org/license/
-->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"
>
<!-- default-autowire-candidates="*Service,*DAO,javax.sql.DataSource"> -->
<context:annotation-config /> <!-- allows us to use spring annotations in beans -->
<bean id="always_true_filter" scope="singleton" class="org.dspace.content.logic.TrueFilter"/>
<!-- DEFINE CONDITIONS
Define condition beans below for use as sub-statements in operator and filter beans
-->
<!--
The MetadataValueMatchCondition takes a regular expression, not an exact value.
For an exact value match (rather than 'contains'), make sure to anchor the string
like "^Exact Match$".
Special characters used in Java regular expressions will need escaping.
The below condition returns true if dc.title contains "demo" (case insensitive)
-->
<bean id="title-contains-demo_condition"
class="org.dspace.content.logic.condition.MetadataValueMatchCondition">
<property name="parameters">
<map>
<entry key="field" value="dc.title" />
<entry key="pattern" value="(?i)demo" />
</map>
</property>
</bean>
<bean id="item-is-public_condition"
class="org.dspace.content.logic.condition.ReadableByGroupCondition">
<property name="parameters">
<map>
<entry key="group" value="Anonymous" />
<entry key="action" value="READ" />
</map>
</property>
</bean>
<!-- dc.title starts with Pattern -->
<bean id="title-starts-with-pattern_condition"
class="org.dspace.content.logic.condition.MetadataValueMatchCondition">
<property name="parameters">
<map>
<entry key="field" value="dc.title" />
<entry key="pattern" value="^Pattern" />
</map>
</property>
</bean>
<!-- dc.type is exactly Journal Article -->
<bean id="type-equals-journal-article_condition"
class="org.dspace.content.logic.condition.MetadataValueMatchCondition">
<property name="parameters">
<map>
<entry key="field" value="dc.type" />
<entry key="pattern" value="^Journal Article$" />
</map>
</property>
</bean>
<!-- dc.type is exactly Dataset -->
<bean id="type-equals-dataset_condition"
class="org.dspace.content.logic.condition.MetadataValueMatchCondition">
<property name="parameters">
<map>
<entry key="field" value="dc.type" />
<entry key="pattern" value="^Dataset$" />
</map>
</property>
</bean>
<!--
A filter that checks if any value of dc.identifier.uri contains "10.12345/".
-->
<bean id="dc-identifier-uri-contains-doi_condition"
class="org.dspace.content.logic.condition.MetadataValueMatchCondition">
<property name="parameters">
<map>
<entry key="field" value="dc.identifier.uri" />
<entry key="pattern" value="10.12345/" />
</map>
</property>
</bean>
<!-- dc.type ends with any of the listed values, as per XOAI "driverDocumentTypeCondition" -->
<bean id="driver-document-type_condition"
class="org.dspace.content.logic.condition.MetadataValuesMatchCondition">
<property name="parameters">
<map>
<entry key="field" value="dc.type" />
<entry key="patterns">
<list>
<value>article$</value>
<value>bachelorThesis$</value>
<value>masterThesis$</value>
<value>doctoralThesis$</value>
<value>book$</value>
<value>bookPart$</value>
<value>review$</value>
<value>conferenceObject$</value>
<value>lecture$</value>
<value>workingPaper$</value>
<value>preprint$</value>
<value>report$</value>
<value>annotation$</value>
<value>contributionToPeriodical$</value>
<value>patent$</value>
<value>dataset$</value>
<value>other$</value>
</list>
</entry>
</map>
</property>
</bean>
<!-- is in collection 123456789/20 (note, list parameter map means multiple collections can be passed) -->
<bean id="in-outfit-collection_condition"
class="org.dspace.content.logic.condition.InCollectionCondition">
<property name="parameters">
<map>
<entry key="collections">
<list>
<value>123456789/20</value>
</list>
</entry>
</map>
</property>
</bean>
<!-- has exactly one bitstream in ORIGINAL bundle -->
<bean id="has-one-bitstream_condition"
class="org.dspace.content.logic.condition.BitstreamCountCondition">
<property name="parameters">
<map>
<entry key="bundle" value="ORIGINAL"/>
<entry key="min" value="1"/>
<entry key="max" value="1"/>
</map>
</property>
</bean>
<!-- has at least one bitstream in ORIGINAL bundle -->
<bean id="has-at-least-one-bitstream_condition"
class="org.dspace.content.logic.condition.BitstreamCountCondition">
<property name="parameters">
<map>
<entry key="bundle" value="ORIGINAL"/>
<entry key="min" value="1"/>
</map>
</property>
</bean>
<bean id="is-archived_condition" class="org.dspace.content.logic.condition.IsArchivedCondition">
<property name="parameters">
<map></map>
</property>
</bean>
<bean id="is-withdrawn_condition" class="org.dspace.content.logic.condition.IsWithdrawnCondition">
<property name="parameters">
<map></map>
</property>
</bean>
<!-- DEFINE OPERATORS
Operators can be defined too, if a particular AND or OR statement needs to be re-used a lot, though
it may be easier in most cases to turn that into a filter and reference the filter in other sub-statements
-->
<bean class="org.dspace.content.logic.operator.Or" id="a-common-or_statement">
<property name="statements">
<list>
<ref bean="type-equals-journal-article_condition"/>
<ref bean="type-equals-dataset_condition"/>
</list>
</property>
</bean>
<!-- DEFINE FILTERS -->
<!-- Note that this filter is almost the same as the above "or" bean but the advantage is we
can reference the type_filter directly for item logic *and* as a sub-statement of other filters
whereas the operator class can only be a sub-statement
-->
<!-- Example DOI Filter. An item has to pass the filter (filter returns true) to get a DOI.
If the filter returns false on an item, minting of a new DOI for that item is prevented. -->
<bean id="doi-filter" class="org.dspace.content.logic.DefaultFilter">
<property name="statement">
<bean class="org.dspace.content.logic.operator.And">
<property name="statements">
<list>
<!-- Make sure the item is archived -->
<ref bean="is-archived_condition"/>
<!-- Make sure the item is not withdrawn -->
<bean class="org.dspace.content.logic.operator.Not">
<property name="statements" ref="is-withdrawn_condition"/>
</bean>
<!-- Don't create new DOIs for items that already have one -->
<bean class="org.dspace.content.logic.operator.Not">
<property name="statements" ref="dc-identifier-uri-contains-doi_condition"/>
</bean>
<!-- Create DOIs for items only that do have at least one bitstream. -->
<ref bean="has-at-least-one-bitstream_condition"/>
</list>
</property>
</bean>
</property>
</bean>
<bean id="type_filter" class="org.dspace.content.logic.DefaultFilter">
<property name="statement">
<!-- the below is the same as referencing the above, eg:
<ref bean="a-common-r_statement"/> -->
<bean class="org.dspace.content.logic.operator.Or">
<property name="statements">
<list>
<ref bean="type-equals-journal-article_condition"/>
<ref bean="type-equals-dataset_condition"/>
</list>
</property>
</bean>
</property>
</bean>
<!-- A very simple demonstration filter, using the metadata match condition -->
<bean id="simple-demo_filter" class="org.dspace.content.logic.DefaultFilter">
<property name="statement" ref="title-contains-demo_condition"/>
</bean>
<!-- A very simple filter for items with at least one bitstream -->
<bean id="has-bitstream_filter" class="org.dspace.content.logic.DefaultFilter">
<property name="statement" ref="has-at-least-one-bitstream_condition"/>
</bean>
<!--
a more complex example:
title contains 'demo' AND (title starts with 'Pattern' OR item is in one of the listed collections)
-->
<bean id="demo_filter" class="org.dspace.content.logic.DefaultFilter">
<property name="statement">
<bean class="org.dspace.content.logic.operator.And">
<!-- title contains 'demo' AND (the result of the OR substatement is true) -->
<property name="statements">
<list>
<ref bean="title-contains-demo_condition"/>
<bean class="org.dspace.content.logic.operator.Or">
<!-- title starts with Lily OR the item in one of the listed collections -->
<property name="statements">
<list>
<ref bean="title-starts-with-pattern_condition"/>
<bean class="org.dspace.content.logic.condition.InCollectionCondition">
<property name="parameters">
<map>
<entry key="collections">
<list>
<value>123456789/3</value>
<value>123456789/4</value>
</list>
</entry>
</map>
</property>
</bean>
</list>
</property>
</bean>
</list>
</property>
</bean>
</property>
</bean>
<!-- An example of an OpenAIRE compliance filter based on the same rules in xoai.xml
some sub-statements are defined within this bean, and some are referenced from earlier definitions
-->
<bean id="openaire_filter" class="org.dspace.content.logic.DefaultFilter">
<property name="statement">
<bean class="org.dspace.content.logic.operator.And">
<property name="statements">
<list>
<!-- Has a non-empty title -->
<bean id="has-title_condition"
class="org.dspace.content.logic.condition.MetadataValueMatchCondition">
<property name="parameters">
<map>
<entry key="field" value="dc.title" />
<entry key="pattern" value=".*" />
</map>
</property>
</bean>
<!-- AND has a non-empty author -->
<bean id="has-author_condition"
class="org.dspace.content.logic.condition.MetadataValueMatchCondition">
<property name="parameters">
<map>
<entry key="field" value="dc.contributor.author" />
<entry key="pattern" value=".*" />
</map>
</property>
</bean>
<!-- AND has a valid DRIVER document type (defined earlier) -->
<ref bean="driver-document-type_condition" />
<!-- AND (the item is publicly accessible OR withdrawn) -->
<bean class="org.dspace.content.logic.operator.Or">
<property name="statements">
<list>
<!-- item is public, defined earlier -->
<ref bean="item-is-public_condition" />
<!-- OR item is withdrawn, for tombstoning -->
<bean class="org.dspace.content.logic.condition.IsWithdrawnCondition">
<property name="parameters"><map></map></property>
</bean>
</list>
</property>
</bean>
<!-- AND the dc.relation is a valid OpenAIRE identifier
(starts with "info:eu-repo/grantAgreement/") -->
<bean id="has-openaire-relation_condition"
class="org.dspace.content.logic.condition.MetadataValueMatchCondition">
<property name="parameters">
<map>
<entry key="field" value="dc.relation" />
<entry key="pattern" value="^info:eu-repo/grantAgreement/" />
</map>
</property>
</bean>
</list>
</property>
</bean>
</property>
</bean>
<bean id="example-doi_filter" class="org.dspace.content.logic.DefaultFilter">
<property name="statement">
<bean class="org.dspace.content.logic.operator.And">
<property name="statements">
<list>
<!-- Has a non-empty title -->
<bean id="has-title_condition"
class="org.dspace.content.logic.condition.MetadataValueMatchCondition">
<property name="parameters">
<map>
<entry key="field" value="dc.title" />
<entry key="pattern" value=".*" />
</map>
</property>
</bean>
</list>
</property>
</bean>
</property>
</bean>
</beans>

View File

@@ -15,9 +15,12 @@
<bean id="selectrevieweractionAPI" class="org.dspace.xmlworkflow.state.actions.processingaction.SelectReviewerAction" scope="prototype">
<property name="role" ref="scoreassignedreviewer"/>
</bean>
<bean id="scorereviewactionAPI" class="org.dspace.xmlworkflow.state.actions.processingaction.ScoreReviewAction" scope="prototype"/>
<bean id="scorereviewactionAPI" class="org.dspace.xmlworkflow.state.actions.processingaction.ScoreReviewAction" scope="prototype">
<property name="descriptionRequired" value="true"/>
<property name="maxValue" value="5"/>
</bean>
<bean id="evaluationactionAPI" class="org.dspace.xmlworkflow.state.actions.processingaction.ScoreEvaluationAction" scope="prototype">
<property name="minimumAcceptanceScore" value="50" />
<property name="minimumAcceptanceScore" value="3" />
</bean>
@@ -63,6 +66,12 @@
<property name="requiresUI" value="true"/>
</bean>
<bean id="ratingreviewaction" class="org.dspace.xmlworkflow.state.actions.WorkflowActionConfig" scope="prototype">
<constructor-arg type="java.lang.String" value="ratingreviewaction"/>
<property name="processingAction" ref="ratingreviewactionAPI" />
<property name="requiresUI" value="true"/>
</bean>
<!--Autmatic step that evaluates scores (workflow.score) and checks if they match the configured minimum for archiving -->
<bean id="evaluationaction" class="org.dspace.xmlworkflow.state.actions.WorkflowActionConfig" scope="prototype">
<constructor-arg type="java.lang.String" value="evaluationaction"/>

View File

@@ -153,6 +153,7 @@
<bean id="scoreassignedreviewer" class="org.dspace.xmlworkflow.Role">
<property name="scope" value="#{ T(org.dspace.xmlworkflow.Role.Scope).ITEM}"/>
<property name="name" value="Reviewer"/>
<property name="deleteTemporaryGroup" value="true"/>
</bean>

Some files were not shown because too many files have changed in this diff Show More