diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml
index f6b06f69edb7..0aafa5df73fb 100644
--- a/dspace-api/pom.xml
+++ b/dspace-api/pom.xml
@@ -374,6 +374,11 @@
+
+ com.fasterxml.jackson.datatype
+ jackson-datatype-jsr310
+ 2.13.5
+
javax.cache
cache-api
@@ -837,6 +842,10 @@
org.yaml
snakeyaml
+
+ com.fasterxml.jackson.datatype
+ jackson-datatype-jsr310
+
diff --git a/dspace-api/src/main/java/org/dspace/administer/MetadataImporter.java b/dspace-api/src/main/java/org/dspace/administer/MetadataImporter.java
index 2677cb20501f..80bda610c7dd 100644
--- a/dspace-api/src/main/java/org/dspace/administer/MetadataImporter.java
+++ b/dspace-api/src/main/java/org/dspace/administer/MetadataImporter.java
@@ -21,6 +21,8 @@
import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
import org.dspace.authorize.AuthorizeException;
import org.dspace.content.MetadataField;
import org.dspace.content.MetadataSchema;
@@ -30,8 +32,6 @@
import org.dspace.content.service.MetadataFieldService;
import org.dspace.content.service.MetadataSchemaService;
import org.dspace.core.Context;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
@@ -40,9 +40,9 @@
/**
* @author Richard Jones
*
- * This class takes an xml document as passed in the arguments and
+ * This class takes an XML document as passed in the arguments and
* uses it to create metadata elements in the Metadata Registry if
- * they do not already exist
+ * they do not already exist.
*
* The format of the XML file is as follows:
*
@@ -69,7 +69,7 @@ public class MetadataImporter {
/**
* logging category
*/
- private static final Logger log = LoggerFactory.getLogger(MetadataImporter.class);
+ private static final Logger log = LogManager.getLogger();
/**
* Default constructor
@@ -89,6 +89,7 @@ private MetadataImporter() { }
* @throws SAXException if parser error
* @throws NonUniqueMetadataException if duplicate metadata
* @throws RegistryImportException if import fails
+ * @throws XPathExpressionException passed through
**/
public static void main(String[] args)
throws ParseException, SQLException, IOException, TransformerException,
@@ -125,6 +126,7 @@ public static void main(String[] args)
* @throws SAXException if parser error
* @throws NonUniqueMetadataException if duplicate metadata
* @throws RegistryImportException if import fails
+ * @throws XPathExpressionException passed through
*/
public static void loadRegistry(String file, boolean forceUpdate)
throws SQLException, IOException, TransformerException, ParserConfigurationException, AuthorizeException,
@@ -203,7 +205,7 @@ private static void loadSchema(Context context, Node node, boolean updateExistin
if (s == null) {
// Schema does not exist - create
- log.info("Registering Schema " + name + " (" + namespace + ")");
+ log.info("Registering Schema {}({})", name, namespace);
metadataSchemaService.create(context, name, namespace);
} else {
// Schema exists - if it's the same namespace, allow the type imports to continue
@@ -215,7 +217,7 @@ private static void loadSchema(Context context, Node node, boolean updateExistin
// It's a different namespace - have we been told to update?
if (updateExisting) {
// Update the existing schema namespace and continue to type import
- log.info("Updating Schema " + name + ": New namespace " + namespace);
+ log.info("Updating Schema {}: New namespace {}", name, namespace);
s.setNamespace(namespace);
metadataSchemaService.update(context, s);
} else {
@@ -274,7 +276,7 @@ private static void loadType(Context context, Node node)
if (qualifier == null) {
fieldName = schema + "." + element;
}
- log.info("Registering metadata field " + fieldName);
+ log.info("Registering metadata field {}", fieldName);
MetadataField field = metadataFieldService.create(context, schemaObj, element, qualifier, scopeNote);
metadataFieldService.update(context, field);
}
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/ItemFilter.java b/dspace-api/src/main/java/org/dspace/app/ldn/ItemFilter.java
new file mode 100644
index 000000000000..44dd5389d3de
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/ItemFilter.java
@@ -0,0 +1,30 @@
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in the LICENSE and NOTICE files at the root of the source
+ * tree and available online at
+ *
+ * http://www.dspace.org/license/
+ */
+package org.dspace.app.ldn;
+
+/**
+ * model class for the item filters configured into item-filters.xml
+ *
+ * @author Mohamed Eskander (mohamed.eskander at 4science.com)
+ */
+public class ItemFilter {
+
+ private String id;
+
+ public ItemFilter(String id) {
+ this.id = id;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+}
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/LDNMessageConsumer.java b/dspace-api/src/main/java/org/dspace/app/ldn/LDNMessageConsumer.java
new file mode 100644
index 000000000000..9e03cf73927e
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/LDNMessageConsumer.java
@@ -0,0 +1,231 @@
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in the LICENSE and NOTICE files at the root of the source
+ * tree and available online at
+ *
+ * http://www.dspace.org/license/
+ */
+package org.dspace.app.ldn;
+
+import static java.lang.String.format;
+
+import java.io.IOException;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.UUID;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.dspace.app.ldn.factory.NotifyServiceFactory;
+import org.dspace.app.ldn.model.Notification;
+import org.dspace.app.ldn.service.LDNMessageService;
+import org.dspace.app.ldn.service.NotifyPatternToTriggerService;
+import org.dspace.app.ldn.service.NotifyServiceInboundPatternService;
+import org.dspace.content.Bitstream;
+import org.dspace.content.BitstreamFormat;
+import org.dspace.content.Bundle;
+import org.dspace.content.Item;
+import org.dspace.content.MetadataValue;
+import org.dspace.content.factory.ContentServiceFactory;
+import org.dspace.content.logic.LogicalStatement;
+import org.dspace.content.service.BitstreamService;
+import org.dspace.content.service.ItemService;
+import org.dspace.core.Constants;
+import org.dspace.core.Context;
+import org.dspace.core.I18nUtil;
+import org.dspace.core.LDN;
+import org.dspace.event.Consumer;
+import org.dspace.event.Event;
+import org.dspace.services.ConfigurationService;
+import org.dspace.services.factory.DSpaceServicesFactory;
+import org.dspace.utils.DSpace;
+import org.dspace.web.ContextUtil;
+
+/**
+ * class for creating a new LDN Messages of installed item
+ *
+ * @author Mohamed Eskander (mohamed.eskander at 4science.com)
+ */
+public class LDNMessageConsumer implements Consumer {
+
+ private NotifyPatternToTriggerService notifyPatternToTriggerService;
+ private NotifyServiceInboundPatternService inboundPatternService;
+ private LDNMessageService ldnMessageService;
+ private ConfigurationService configurationService;
+ private ItemService itemService;
+ private BitstreamService bitstreamService;
+
+ @Override
+ public void initialize() throws Exception {
+ notifyPatternToTriggerService = NotifyServiceFactory.getInstance().getNotifyPatternToTriggerService();
+ ldnMessageService = NotifyServiceFactory.getInstance().getLDNMessageService();
+ configurationService = DSpaceServicesFactory.getInstance().getConfigurationService();
+ itemService = ContentServiceFactory.getInstance().getItemService();
+ bitstreamService = ContentServiceFactory.getInstance().getBitstreamService();
+ inboundPatternService = NotifyServiceFactory.getInstance().getNotifyServiceInboundPatternService();
+ }
+
+ @Override
+ public void consume(Context context, Event event) throws Exception {
+
+ if (event.getSubjectType() != Constants.ITEM ||
+ event.getEventType() != Event.INSTALL) {
+ return;
+ }
+
+ Item item = (Item) event.getSubject(context);
+ createManualLDNMessages(context, item);
+ createAutomaticLDNMessages(context, item);
+ }
+
+ private void createManualLDNMessages(Context context, Item item) throws SQLException, JsonProcessingException {
+ List patternsToTrigger =
+ notifyPatternToTriggerService.findByItem(context, item);
+
+ for (NotifyPatternToTrigger patternToTrigger : patternsToTrigger) {
+ createLDNMessage(context,patternToTrigger.getItem(),
+ patternToTrigger.getNotifyService(), patternToTrigger.getPattern());
+ }
+ }
+
+ private void createAutomaticLDNMessages(Context context, Item item) throws SQLException, JsonProcessingException {
+
+ List inboundPatterns = inboundPatternService.findAutomaticPatterns(context);
+
+ for (NotifyServiceInboundPattern inboundPattern : inboundPatterns) {
+ if (StringUtils.isEmpty(inboundPattern.getConstraint()) ||
+ evaluateFilter(context, item, inboundPattern.getConstraint())) {
+ createLDNMessage(context, item, inboundPattern.getNotifyService(), inboundPattern.getPattern());
+ }
+ }
+ }
+
+ private boolean evaluateFilter(Context context, Item item, String constraint) {
+ LogicalStatement filter =
+ new DSpace().getServiceManager().getServiceByName(constraint, LogicalStatement.class);
+
+ return filter != null && filter.getResult(context, item);
+ }
+
+ private void createLDNMessage(Context context, Item item, NotifyServiceEntity service, String pattern)
+ throws SQLException, JsonMappingException, JsonProcessingException {
+
+ LDN ldn = getLDNMessage(pattern);
+ LDNMessageEntity ldnMessage =
+ ldnMessageService.create(context, format("urn:uuid:%s", UUID.randomUUID()));
+
+ ldnMessage.setObject(item);
+ ldnMessage.setTarget(service);
+ ldnMessage.setQueueStatus(LDNMessageEntity.QUEUE_STATUS_QUEUED);
+ ldnMessage.setQueueTimeout(new Date());
+
+ appendGeneratedMessage(ldn, ldnMessage, pattern);
+
+ ObjectMapper mapper = new ObjectMapper();
+ Notification notification = mapper.readValue(ldnMessage.getMessage(), Notification.class);
+ ldnMessage.setType(StringUtils.joinWith(",", notification.getType()));
+
+ ArrayList notificationTypeArrayList = new ArrayList(notification.getType());
+ // sorting the list
+ Collections.sort(notificationTypeArrayList);
+ ldnMessage.setActivityStreamType(notificationTypeArrayList.get(0));
+ ldnMessage.setCoarNotifyType(notificationTypeArrayList.get(1));
+
+ ldnMessageService.update(context, ldnMessage);
+ }
+
+ private LDN getLDNMessage(String pattern) {
+ try {
+ return LDN.getLDNMessage(I18nUtil.getLDNFilename(Locale.getDefault(), pattern));
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private void appendGeneratedMessage(LDN ldn, LDNMessageEntity ldnMessage, String pattern) {
+ Item item = (Item) ldnMessage.getObject();
+ ldn.addArgument(getUiUrl());
+ ldn.addArgument(configurationService.getProperty("ldn.notify.inbox"));
+ ldn.addArgument(configurationService.getProperty("dspace.name"));
+ ldn.addArgument(Objects.requireNonNullElse(ldnMessage.getTarget().getUrl(), ""));
+ ldn.addArgument(Objects.requireNonNullElse(ldnMessage.getTarget().getLdnUrl(), ""));
+ ldn.addArgument(getUiUrl() + "/handle/" + ldnMessage.getObject().getHandle());
+ ldn.addArgument(getIdentifierUri(item));
+ ldn.addArgument(generateBitstreamDownloadUrl(item));
+ ldn.addArgument(getBitstreamMimeType(findPrimaryBitstream(item)));
+ ldn.addArgument(ldnMessage.getID());
+
+ ldnMessage.setMessage(ldn.generateLDNMessage());
+ }
+
+ private String getUiUrl() {
+ return configurationService.getProperty("dspace.ui.url");
+ }
+
+ private String getIdentifierUri(Item item) {
+ return itemService.getMetadataByMetadataString(item, "dc.identifier.uri")
+ .stream()
+ .findFirst()
+ .map(MetadataValue::getValue)
+ .orElse("");
+ }
+
+ private String generateBitstreamDownloadUrl(Item item) {
+ String uiUrl = getUiUrl();
+ return findPrimaryBitstream(item)
+ .map(bs -> uiUrl + "/bitstreams/" + bs.getID() + "/download")
+ .orElse("");
+ }
+
+ private Optional findPrimaryBitstream(Item item) {
+ List bundles = item.getBundles(Constants.CONTENT_BUNDLE_NAME);
+ return bundles.stream()
+ .findFirst()
+ .map(Bundle::getPrimaryBitstream)
+ .or(() -> bundles.stream()
+ .findFirst()
+ .flatMap(bundle -> CollectionUtils.isNotEmpty(bundle.getBitstreams())
+ ? Optional.of(bundle.getBitstreams().get(0))
+ : Optional.empty()));
+ }
+
+ private String getBitstreamMimeType(Optional bitstream) {
+ return bitstream.map(bs -> {
+ try {
+ Context context = ContextUtil.obtainCurrentRequestContext();
+ BitstreamFormat bitstreamFormat = bs.getFormat(context);
+ if (bitstreamFormat.getShortDescription().equals("Unknown")) {
+ return getUserFormatMimeType(bs);
+ }
+ return bitstreamFormat.getMIMEType();
+ } catch (SQLException e) {
+ throw new RuntimeException(e);
+ }
+ }).orElse("");
+ }
+
+ private String getUserFormatMimeType(Bitstream bitstream) {
+ return bitstreamService.getMetadataFirstValue(bitstream,
+ "dc", "format", "mimetype", Item.ANY);
+ }
+
+ @Override
+ public void end(Context ctx) throws Exception {
+
+ }
+
+ @Override
+ public void finish(Context ctx) throws Exception {
+
+ }
+
+}
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/LDNMessageEntity.java b/dspace-api/src/main/java/org/dspace/app/ldn/LDNMessageEntity.java
new file mode 100644
index 000000000000..5d96dd3f956c
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/LDNMessageEntity.java
@@ -0,0 +1,278 @@
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in the LICENSE and NOTICE files at the root of the source
+ * tree and available online at
+ *
+ * http://www.dspace.org/license/
+ */
+package org.dspace.app.ldn;
+
+import java.util.Date;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+
+import org.dspace.content.DSpaceObject;
+import org.dspace.core.ReloadableEntity;
+
+/**
+ * Class representing ldnMessages stored in the DSpace system and, when locally resolvable,
+ * some information are stored as dedicated attributes.
+ *
+ * @author Mohamed Eskander (mohamed.eskander at 4science.com)
+ */
+@Entity
+@Table(name = "ldn_message")
+public class LDNMessageEntity implements ReloadableEntity {
+
+ /**
+ * LDN messages interact with a fictitious queue. Scheduled tasks manage the queue.
+ */
+
+ /**
+ * Message must not be processed.
+ */
+ public static final Integer QUEUE_STATUS_UNTRUSTED_IP = 0;
+
+ /**
+ * Message queued, it has to be elaborated.
+ */
+ public static final Integer QUEUE_STATUS_QUEUED = 1;
+
+ /**
+ * Message has been taken from the queue and it's elaboration is in progress.
+ */
+ public static final Integer QUEUE_STATUS_PROCESSING = 2;
+
+ /**
+ * Message has been correctly elaborated.
+ */
+ public static final Integer QUEUE_STATUS_PROCESSED = 3;
+
+ /**
+ * Message has not been correctly elaborated - despite more than "ldn.processor.max.attempts" retryies
+ */
+ public static final Integer QUEUE_STATUS_FAILED = 4;
+
+ /**
+ * Message must not be processed
+ */
+ public static final Integer QUEUE_STATUS_UNTRUSTED = 5;
+
+ /**
+ * Message is not processed since action is not mapped
+ */
+ public static final Integer QUEUE_STATUS_UNMAPPED_ACTION = 6;
+
+ @Id
+ private String id;
+
+ @ManyToOne
+ @JoinColumn(name = "object", referencedColumnName = "uuid")
+ private DSpaceObject object;
+
+ @Column(name = "message", nullable = false, columnDefinition = "text")
+ private String message;
+
+ @Column(name = "type")
+ private String type;
+
+ @Column(name = "queue_status")
+ private Integer queueStatus;
+
+ @Column(name = "queue_attempts")
+ private Integer queueAttempts = 0;
+
+ @Temporal(TemporalType.TIMESTAMP)
+ @Column(name = "queue_last_start_time")
+ private Date queueLastStartTime = null;
+
+ @Temporal(TemporalType.TIMESTAMP)
+ @Column(name = "queue_timeout")
+ private Date queueTimeout = null;
+
+ @ManyToOne
+ @JoinColumn(name = "origin", referencedColumnName = "id")
+ private NotifyServiceEntity origin;
+
+ @ManyToOne
+ @JoinColumn(name = "target", referencedColumnName = "id")
+ private NotifyServiceEntity target;
+
+ @ManyToOne
+ @JoinColumn(name = "inReplyTo", referencedColumnName = "id")
+ private LDNMessageEntity inReplyTo;
+
+ @ManyToOne
+ @JoinColumn(name = "context", referencedColumnName = "uuid")
+ private DSpaceObject context;
+
+ @Column(name = "activity_stream_type")
+ private String activityStreamType;
+
+ @Column(name = "coar_notify_type")
+ private String coarNotifyType;
+
+ @Column(name = "source_ip")
+ private String sourceIp;
+
+ protected LDNMessageEntity() {
+
+ }
+
+ public LDNMessageEntity(String id) {
+ this.id = id;
+ }
+
+ @Override
+ public String getID() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ /**
+ *
+ * @return the DSpace item related to this message
+ */
+ public DSpaceObject getObject() {
+ return object;
+ }
+
+ public void setObject(DSpaceObject object) {
+ this.object = object;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ public void setMessage(String message) {
+ this.message = message;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ public String getActivityStreamType() {
+ return activityStreamType;
+ }
+
+ public void setActivityStreamType(String activityStreamType) {
+ this.activityStreamType = activityStreamType;
+ }
+
+ public String getCoarNotifyType() {
+ return coarNotifyType;
+ }
+
+ public void setCoarNotifyType(String coarNotifyType) {
+ this.coarNotifyType = coarNotifyType;
+ }
+
+ /**
+ *
+ * @return The originator of the activity, typically the service responsible for sending the notification
+ */
+ public NotifyServiceEntity getOrigin() {
+ return origin;
+ }
+
+ public void setOrigin(NotifyServiceEntity origin) {
+ this.origin = origin;
+ }
+
+ /**
+ *
+ * @return The intended destination of the activity, typically the service which consumes the notification
+ */
+ public NotifyServiceEntity getTarget() {
+ return target;
+ }
+
+ public void setTarget(NotifyServiceEntity target) {
+ this.target = target;
+ }
+
+ /**
+ *
+ * @return This property is used when the notification is a direct response to a previous notification;
+ * contains an {@link org.dspace.app.ldn.LDNMessageEntity#inReplyTo id}
+ */
+ public LDNMessageEntity getInReplyTo() {
+ return inReplyTo;
+ }
+
+ public void setInReplyTo(LDNMessageEntity inReplyTo) {
+ this.inReplyTo = inReplyTo;
+ }
+
+ /**
+ *
+ * @return This identifies another resource which is relevant to understanding the notification
+ */
+ public DSpaceObject getContext() {
+ return context;
+ }
+
+ public void setContext(DSpaceObject context) {
+ this.context = context;
+ }
+
+ public Integer getQueueStatus() {
+ return queueStatus;
+ }
+
+ public void setQueueStatus(Integer queueStatus) {
+ this.queueStatus = queueStatus;
+ }
+
+ public Integer getQueueAttempts() {
+ return queueAttempts;
+ }
+
+ public void setQueueAttempts(Integer queueAttempts) {
+ this.queueAttempts = queueAttempts;
+ }
+
+ public Date getQueueLastStartTime() {
+ return queueLastStartTime;
+ }
+
+ public void setQueueLastStartTime(Date queueLastStartTime) {
+ this.queueLastStartTime = queueLastStartTime;
+ }
+
+ public Date getQueueTimeout() {
+ return queueTimeout;
+ }
+
+ public void setQueueTimeout(Date queueTimeout) {
+ this.queueTimeout = queueTimeout;
+ }
+
+ public String getSourceIp() {
+ return sourceIp;
+ }
+
+ public void setSourceIp(String sourceIp) {
+ this.sourceIp = sourceIp;
+ }
+
+ @Override
+ public String toString() {
+ return "LDNMessage id:" + this.getID() + " typed:" + this.getType();
+ }
+}
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/LDNMessageQueueStatus.java b/dspace-api/src/main/java/org/dspace/app/ldn/LDNMessageQueueStatus.java
new file mode 100644
index 000000000000..ad3dd36e69c5
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/LDNMessageQueueStatus.java
@@ -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.app.ldn;
+
+public enum LDNMessageQueueStatus {
+
+ /**
+ * Resulting processing status of an LDN Message (aka queue management)
+ */
+ QUEUED, PROCESSING, PROCESSED, FAILED;
+}
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/LDNMetadataFields.java b/dspace-api/src/main/java/org/dspace/app/ldn/LDNMetadataFields.java
new file mode 100644
index 000000000000..67a87c144c83
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/LDNMetadataFields.java
@@ -0,0 +1,38 @@
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in the LICENSE and NOTICE files at the root of the source
+ * tree and available online at
+ *
+ * http://www.dspace.org/license/
+ */
+package org.dspace.app.ldn;
+
+/**
+ * Constants for LDN metadata fields
+ *
+ * @author Francesco Bacchelli (francesco.bacchelli at 4science.com)
+ */
+public final class LDNMetadataFields {
+
+ // schema and element are the same for each metadata of LDN coar-notify
+ public static final String SCHEMA = "coar";
+ public static final String ELEMENT = "notify";
+
+ // qualifiers
+ public static final String INITIALIZE = "initialize";
+ public static final String REQUEST_REVIEW = "requestreview";
+ public static final String REQUEST_ENDORSEMENT = "requestendorsement";
+ public static final String EXAMINATION = "examination";
+ public static final String REFUSED = "refused";
+ public static final String REVIEW = "review";
+ public static final String ENDORSMENT = "endorsement";
+ public static final String RELEASE = "release";
+
+ /**
+ *
+ */
+ private LDNMetadataFields() {
+
+ }
+
+}
\ No newline at end of file
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/LDNQueueExtractor.java b/dspace-api/src/main/java/org/dspace/app/ldn/LDNQueueExtractor.java
new file mode 100644
index 000000000000..57a7cdfb07bf
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/LDNQueueExtractor.java
@@ -0,0 +1,54 @@
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in the LICENSE and NOTICE files at the root of the source
+ * tree and available online at
+ *
+ * http://www.dspace.org/license/
+ */
+package org.dspace.app.ldn;
+
+import java.sql.SQLException;
+
+import org.apache.logging.log4j.Logger;
+import org.dspace.app.ldn.factory.LDNMessageServiceFactory;
+import org.dspace.app.ldn.service.LDNMessageService;
+import org.dspace.core.Context;
+
+/**
+ * LDN Message manager: scheduled task invoking extractAndProcessMessageFromQueue() of {@link LDNMessageService}
+ *
+ * @author Francesco Bacchelli (francesco.bacchelli at 4science dot it)
+ */
+public class LDNQueueExtractor {
+
+ private static final LDNMessageService ldnMessageService = LDNMessageServiceFactory.getInstance()
+ .getLDNMessageService();
+ private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(LDNQueueExtractor.class);
+
+ /**
+ * Default constructor
+ */
+ private LDNQueueExtractor() {
+ }
+
+ /**
+ * invokes
+ * @see org.dspace.app.ldn.service.impl.LDNMessageServiceImpl#extractAndProcessMessageFromQueue(Context)
+ * to process the oldest ldn messages from the queue. An LdnMessage is processed when is routed to a
+ * @see org.dspace.app.ldn.processor.LDNProcessor
+ * Also a +1 is added to the ldnMessage entity
+ * @see org.dspace.app.ldn.LDNMessageEntity#getQueueAttempts()
+ * @return the number of processed ldnMessages.
+ * @throws SQLException
+ */
+ public static int extractMessageFromQueue() throws SQLException {
+ Context context = new Context(Context.Mode.READ_WRITE);
+ int processed_messages = ldnMessageService.extractAndProcessMessageFromQueue(context);
+ if (processed_messages > 0) {
+ log.info("Processed Messages x" + processed_messages);
+ }
+ context.complete();
+ return processed_messages;
+ }
+
+};
\ No newline at end of file
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/LDNQueueTimeoutChecker.java b/dspace-api/src/main/java/org/dspace/app/ldn/LDNQueueTimeoutChecker.java
new file mode 100644
index 000000000000..36a927d672bc
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/LDNQueueTimeoutChecker.java
@@ -0,0 +1,53 @@
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in the LICENSE and NOTICE files at the root of the source
+ * tree and available online at
+ *
+ * http://www.dspace.org/license/
+ */
+package org.dspace.app.ldn;
+
+import java.sql.SQLException;
+
+import org.apache.logging.log4j.Logger;
+import org.dspace.app.ldn.factory.LDNMessageServiceFactory;
+import org.dspace.app.ldn.service.LDNMessageService;
+import org.dspace.core.Context;
+
+/**
+ * LDN Message manager: scheduled task invoking checkQueueMessageTimeout() of {@link LDNMessageService}
+ *
+ * @author Francesco Bacchelli (francesco.bacchelli at 4science dot it)
+ */
+public class LDNQueueTimeoutChecker {
+
+ private static final LDNMessageService ldnMessageService = LDNMessageServiceFactory.getInstance()
+ .getLDNMessageService();
+ private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(LDNQueueTimeoutChecker.class);
+
+ /**
+ * Default constructor
+ */
+ private LDNQueueTimeoutChecker() {
+ }
+
+ /**
+ * invokes
+ * @see org.dspace.app.ldn.service.impl.LDNMessageServiceImpl#checkQueueMessageTimeout(Context)
+ * to refresh the queue status of timed-out and in progressing status ldn messages:
+ * according to their attempts put them back in queue or set their status as failed if maxAttempts
+ * reached.
+ * @return the number of managed ldnMessages.
+ * @throws SQLException
+ */
+ public static int checkQueueMessageTimeout() throws SQLException {
+ Context context = new Context(Context.Mode.READ_WRITE);
+ int fixed_messages = 0;
+ fixed_messages = ldnMessageService.checkQueueMessageTimeout(context);
+ if (fixed_messages > 0) {
+ log.info("Managed Messages x" + fixed_messages);
+ }
+ context.complete();
+ return fixed_messages;
+ }
+}
\ No newline at end of file
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/LDNRouter.java b/dspace-api/src/main/java/org/dspace/app/ldn/LDNRouter.java
new file mode 100644
index 000000000000..14957aa503a3
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/LDNRouter.java
@@ -0,0 +1,91 @@
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in the LICENSE and NOTICE files at the root of the source
+ * tree and available online at
+ *
+ * http://www.dspace.org/license/
+ */
+package org.dspace.app.ldn;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.logging.log4j.Logger;
+import org.dspace.app.ldn.processor.LDNProcessor;
+
+/**
+ * Linked Data Notification router.
+ */
+public class LDNRouter {
+
+ private Map, LDNProcessor> incomingProcessors = new HashMap<>();
+ private Map, LDNProcessor> outcomingProcessors = new HashMap<>();
+ private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(LDNRouter.class);
+
+ /**
+ * Route notification to processor
+ *
+ * @return LDNProcessor processor to process notification, can be null
+ */
+ public LDNProcessor route(LDNMessageEntity ldnMessage) {
+ if (ldnMessage == null) {
+ log.warn("A null LDNMessage was received and could not be routed.");
+ return null;
+ }
+ if (StringUtils.isEmpty(ldnMessage.getType())) {
+ log.warn("LDNMessage " + ldnMessage + " was received. It has no type, so it couldn't be routed.");
+ return null;
+ }
+ Set ldnMessageTypeSet = new HashSet();
+ ldnMessageTypeSet.add(ldnMessage.getActivityStreamType());
+ ldnMessageTypeSet.add(ldnMessage.getCoarNotifyType());
+
+ LDNProcessor processor = null;
+ if (ldnMessage.getTarget() == null) {
+ processor = incomingProcessors.get(ldnMessageTypeSet);
+ } else if (ldnMessage.getOrigin() == null) {
+ processor = outcomingProcessors.get(ldnMessageTypeSet);
+ }
+
+ return processor;
+ }
+
+ /**
+ * Get all incoming routes.
+ *
+ * @return Map, LDNProcessor>
+ */
+ public Map, LDNProcessor> getIncomingProcessors() {
+ return incomingProcessors;
+ }
+
+ /**
+ * Set all incoming routes.
+ *
+ * @param incomingProcessors
+ */
+ public void setIncomingProcessors(Map, LDNProcessor> incomingProcessors) {
+ this.incomingProcessors = incomingProcessors;
+ }
+
+ /**
+ * Get all outcoming routes.
+ *
+ * @return Map, LDNProcessor>
+ */
+ public Map, LDNProcessor> getOutcomingProcessors() {
+ return outcomingProcessors;
+ }
+
+ /**
+ * Set all outcoming routes.
+ *
+ * @param outcomingProcessors
+ */
+ public void setOutcomingProcessors(Map, LDNProcessor> outcomingProcessors) {
+ this.outcomingProcessors = outcomingProcessors;
+ }
+}
\ No newline at end of file
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/NotifyPatternToTrigger.java b/dspace-api/src/main/java/org/dspace/app/ldn/NotifyPatternToTrigger.java
new file mode 100644
index 000000000000..b393d8bedbc5
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/NotifyPatternToTrigger.java
@@ -0,0 +1,83 @@
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in the LICENSE and NOTICE files at the root of the source
+ * tree and available online at
+ *
+ * http://www.dspace.org/license/
+ */
+package org.dspace.app.ldn;
+
+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.content.Item;
+import org.dspace.core.ReloadableEntity;
+
+/**
+ * Database object representing notify patterns to be triggered
+ *
+ * @author Mohamed Eskander (mohamed.eskander at 4science.com)
+ */
+@Entity
+@Table(name = "notifypatterns_to_trigger")
+public class NotifyPatternToTrigger implements ReloadableEntity {
+
+ @Id
+ @Column(name = "id")
+ @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "notifypatterns_to_trigger_id_seq")
+ @SequenceGenerator(name = "notifypatterns_to_trigger_id_seq",
+ sequenceName = "notifypatterns_to_trigger_id_seq",
+ allocationSize = 1)
+ private Integer id;
+
+ @ManyToOne
+ @JoinColumn(name = "item_id", referencedColumnName = "uuid")
+ private Item item;
+
+ @ManyToOne
+ @JoinColumn(name = "service_id", referencedColumnName = "id")
+ private NotifyServiceEntity notifyService;
+
+ @Column(name = "pattern")
+ private String pattern;
+
+ public void setId(Integer id) {
+ this.id = id;
+ }
+
+ public Item getItem() {
+ return item;
+ }
+
+ public void setItem(Item item) {
+ this.item = item;
+ }
+
+ public NotifyServiceEntity getNotifyService() {
+ return notifyService;
+ }
+
+ public void setNotifyService(NotifyServiceEntity notifyService) {
+ this.notifyService = notifyService;
+ }
+
+ public String getPattern() {
+ return pattern;
+ }
+
+ public void setPattern(String pattern) {
+ this.pattern = pattern;
+ }
+
+ @Override
+ public Integer getID() {
+ return id;
+ }
+}
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/NotifyServiceEntity.java b/dspace-api/src/main/java/org/dspace/app/ldn/NotifyServiceEntity.java
new file mode 100644
index 000000000000..206ed16fa00e
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/NotifyServiceEntity.java
@@ -0,0 +1,156 @@
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in the LICENSE and NOTICE files at the root of the source
+ * tree and available online at
+ *
+ * http://www.dspace.org/license/
+ */
+package org.dspace.app.ldn;
+
+import java.math.BigDecimal;
+import java.util.List;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.OneToMany;
+import javax.persistence.SequenceGenerator;
+import javax.persistence.Table;
+
+import org.dspace.core.ReloadableEntity;
+
+/**
+ * Database object representing notify services
+ *
+ * @author Mohamed Eskander (mohamed.eskander at 4science.com)
+ */
+@Entity
+@Table(name = "notifyservice")
+public class NotifyServiceEntity implements ReloadableEntity {
+
+ @Id
+ @Column(name = "id")
+ @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "notifyservice_id_seq")
+ @SequenceGenerator(name = "notifyservice_id_seq", sequenceName = "notifyservice_id_seq",
+ allocationSize = 1)
+ private Integer id;
+
+ @Column(name = "name", nullable = false)
+ private String name;
+
+ @Column(name = "description", columnDefinition = "text")
+ private String description;
+
+ @Column(name = "url")
+ private String url;
+
+ @Column(name = "ldn_url")
+ private String ldnUrl;
+
+ @OneToMany(mappedBy = "notifyService")
+ private List inboundPatterns;
+
+ @Column(name = "enabled")
+ private boolean enabled = false;
+
+ @Column(name = "score")
+ private BigDecimal score;
+
+ @Column(name = "lower_ip")
+ private String lowerIp;
+
+ @Column(name = "upper_ip")
+ private String upperIp;
+
+ public void setId(Integer id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ /**
+ * @return URL of an informative website
+ */
+ public String getUrl() {
+ return url;
+ }
+
+ public void setUrl(String url) {
+ this.url = url;
+ }
+
+ /**
+ * @return URL of the LDN InBox
+ */
+ public String getLdnUrl() {
+ return ldnUrl;
+ }
+
+ public void setLdnUrl(String ldnUrl) {
+ this.ldnUrl = ldnUrl;
+ }
+
+ /**
+ * @return The list of the inbound patterns configuration supported by the service
+ */
+ public List getInboundPatterns() {
+ return inboundPatterns;
+ }
+
+ public void setInboundPatterns(List inboundPatterns) {
+ this.inboundPatterns = inboundPatterns;
+ }
+
+ @Override
+ public Integer getID() {
+ return id;
+ }
+
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ public void setEnabled(boolean enabled) {
+ this.enabled = enabled;
+ }
+
+ public BigDecimal getScore() {
+ return score;
+ }
+
+ public void setScore(BigDecimal score) {
+ this.score = score;
+ }
+
+ public String getLowerIp() {
+ return lowerIp;
+ }
+
+ public void setLowerIp(String lowerIp) {
+ this.lowerIp = lowerIp;
+ }
+
+ public String getUpperIp() {
+ return upperIp;
+ }
+
+ public void setUpperIp(String upperIp) {
+ this.upperIp = upperIp;
+ }
+
+}
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/NotifyServiceInboundPattern.java b/dspace-api/src/main/java/org/dspace/app/ldn/NotifyServiceInboundPattern.java
new file mode 100644
index 000000000000..0c367d505131
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/NotifyServiceInboundPattern.java
@@ -0,0 +1,104 @@
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in the LICENSE and NOTICE files at the root of the source
+ * tree and available online at
+ *
+ * http://www.dspace.org/license/
+ */
+package org.dspace.app.ldn;
+
+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 object representing notify service inbound patterns. Every {@link org.dspace.app.ldn.NotifyServiceEntity}
+ * may have inbounds and outbounds. Inbounds are to be sent to the external service.
+ *
+ * @author Mohamed Eskander (mohamed.eskander at 4science.com)
+ */
+@Entity
+@Table(name = "notifyservice_inbound_pattern")
+public class NotifyServiceInboundPattern implements ReloadableEntity {
+
+ @Id
+ @Column(name = "id")
+ @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "notifyservice_inbound_pattern_id_seq")
+ @SequenceGenerator(name = "notifyservice_inbound_pattern_id_seq",
+ sequenceName = "notifyservice_inbound_pattern_id_seq",
+ allocationSize = 1)
+ private Integer id;
+
+ @ManyToOne
+ @JoinColumn(name = "service_id", referencedColumnName = "id")
+ private NotifyServiceEntity notifyService;
+
+ @Column(name = "pattern")
+ private String pattern;
+
+ @Column(name = "constraint_name")
+ private String constraint;
+
+ @Column(name = "automatic")
+ private boolean automatic;
+
+ @Override
+ public Integer getID() {
+ return id;
+ }
+
+ public void setId(Integer id) {
+ this.id = id;
+ }
+
+ public NotifyServiceEntity getNotifyService() {
+ return notifyService;
+ }
+
+ public void setNotifyService(NotifyServiceEntity notifyService) {
+ this.notifyService = notifyService;
+ }
+
+ /**
+ * @see coar documentation
+ * @return pattern of the inbound notification
+ */
+ public String getPattern() {
+ return pattern;
+ }
+
+ public void setPattern(String pattern) {
+ this.pattern = pattern;
+ }
+
+ /**
+ * @return the condition checked for automatic evaluation
+ */
+ public String getConstraint() {
+ return constraint;
+ }
+
+ public void setConstraint(String constraint) {
+ this.constraint = constraint;
+ }
+
+ /**
+ * when true - the notification is automatically when constraints are respected.
+ * @return the automatic flag
+ */
+ public boolean isAutomatic() {
+ return automatic;
+ }
+
+ public void setAutomatic(boolean automatic) {
+ this.automatic = automatic;
+ }
+}
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/action/LDNAction.java b/dspace-api/src/main/java/org/dspace/app/ldn/action/LDNAction.java
new file mode 100644
index 000000000000..b0c895de9958
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/action/LDNAction.java
@@ -0,0 +1,31 @@
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in the LICENSE and NOTICE files at the root of the source
+ * tree and available online at
+ *
+ * http://www.dspace.org/license/
+ */
+package org.dspace.app.ldn.action;
+
+import org.dspace.app.ldn.model.Notification;
+import org.dspace.content.Item;
+import org.dspace.core.Context;
+
+/**
+ * An action that is run after a notification has been processed.
+ */
+public interface LDNAction {
+
+ /**
+ * Execute action for provided notification and item corresponding to the
+ * notification context.
+ *
+ *@param context the context
+ * @param notification the processed notification to perform action against
+ * @param item the item corresponding to the notification context
+ * @return ActionStatus the resulting status of the action
+ * @throws Exception general exception that can be thrown while executing action
+ */
+ public LDNActionStatus execute(Context context, Notification notification, Item item) throws Exception;
+
+}
\ No newline at end of file
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/action/LDNActionStatus.java b/dspace-api/src/main/java/org/dspace/app/ldn/action/LDNActionStatus.java
new file mode 100644
index 000000000000..86f56ed9baab
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/action/LDNActionStatus.java
@@ -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.app.ldn.action;
+
+/**
+ * Resulting status of an execution of an action.
+ */
+public enum LDNActionStatus {
+ CONTINUE, ABORT;
+}
\ No newline at end of file
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/action/LDNCorrectionAction.java b/dspace-api/src/main/java/org/dspace/app/ldn/action/LDNCorrectionAction.java
new file mode 100644
index 000000000000..5ce3804bcee8
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/action/LDNCorrectionAction.java
@@ -0,0 +1,108 @@
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in the LICENSE and NOTICE files at the root of the source
+ * tree and available online at
+ *
+ * http://www.dspace.org/license/
+ */
+package org.dspace.app.ldn.action;
+
+import java.math.BigDecimal;
+import java.sql.SQLException;
+import java.util.Date;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.dspace.app.ldn.NotifyServiceEntity;
+import org.dspace.app.ldn.model.Notification;
+import org.dspace.app.ldn.service.LDNMessageService;
+import org.dspace.content.Item;
+import org.dspace.content.QAEvent;
+import org.dspace.content.service.ItemService;
+import org.dspace.core.Context;
+import org.dspace.handle.service.HandleService;
+import org.dspace.qaevent.service.QAEventService;
+import org.dspace.qaevent.service.dto.NotifyMessageDTO;
+import org.dspace.services.ConfigurationService;
+import org.springframework.beans.factory.annotation.Autowired;
+
+
+/**
+ * Implementation for LDN Correction Action. It creates a QA Event according to the LDN Message received *
+ * @author Francesco Bacchelli (francesco.bacchelli at 4science.it)
+ *
+ */
+public class LDNCorrectionAction implements LDNAction {
+
+ private static final Logger log = LogManager.getLogger(LDNEmailAction.class);
+
+ private String qaEventTopic;
+
+ @Autowired
+ private ConfigurationService configurationService;
+ @Autowired
+ protected ItemService itemService;
+ @Autowired
+ private QAEventService qaEventService;
+ @Autowired
+ private LDNMessageService ldnMessageService;
+ @Autowired
+ private HandleService handleService;
+
+ @Override
+ public LDNActionStatus execute(Context context, Notification notification, Item item) throws Exception {
+ LDNActionStatus result = LDNActionStatus.ABORT;
+ String itemName = itemService.getName(item);
+ QAEvent qaEvent = null;
+ if (notification.getObject() != null) {
+ String citeAs = notification.getObject().getIetfCiteAs();
+ if (citeAs == null || citeAs.isEmpty()) {
+ citeAs = notification.getObject().getId();
+ }
+ NotifyMessageDTO message = new NotifyMessageDTO();
+ message.setHref(citeAs);
+ message.setRelationship(notification.getObject().getAsRelationship());
+ if (notification.getOrigin() != null) {
+ message.setServiceId(notification.getOrigin().getId());
+ message.setServiceName(notification.getOrigin().getInbox());
+ }
+ BigDecimal score = getScore(context, notification);
+ double doubleScoreValue = score != null ? score.doubleValue() : 0d;
+ ObjectMapper mapper = new ObjectMapper();
+ qaEvent = new QAEvent(QAEvent.COAR_NOTIFY_SOURCE,
+ handleService.findHandle(context, item), item.getID().toString(), itemName,
+ this.getQaEventTopic(), doubleScoreValue,
+ mapper.writeValueAsString(message),
+ new Date());
+ qaEventService.store(context, qaEvent);
+ result = LDNActionStatus.CONTINUE;
+ }
+
+ return result;
+ }
+
+ private BigDecimal getScore(Context context, Notification notification) throws SQLException {
+
+ if (notification.getOrigin() == null) {
+ return BigDecimal.ZERO;
+ }
+
+ NotifyServiceEntity service = ldnMessageService.findNotifyService(context, notification.getOrigin());
+
+ if (service == null) {
+ return BigDecimal.ZERO;
+ }
+
+ return service.getScore();
+ }
+
+ public String getQaEventTopic() {
+ return qaEventTopic;
+ }
+
+ public void setQaEventTopic(String qaEventTopic) {
+ this.qaEventTopic = qaEventTopic;
+ }
+
+}
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/action/LDNEmailAction.java b/dspace-api/src/main/java/org/dspace/app/ldn/action/LDNEmailAction.java
new file mode 100644
index 000000000000..b87001f81500
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/action/LDNEmailAction.java
@@ -0,0 +1,155 @@
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in the LICENSE and NOTICE files at the root of the source
+ * tree and available online at
+ *
+ * http://www.dspace.org/license/
+ */
+package org.dspace.app.ldn.action;
+
+import static java.lang.String.format;
+
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.dspace.app.ldn.model.Notification;
+import org.dspace.content.Item;
+import org.dspace.core.Context;
+import org.dspace.core.Email;
+import org.dspace.core.I18nUtil;
+import org.dspace.services.ConfigurationService;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * Action to send email to receipients provided in actionSendFilter. The email
+ * body will be result of templating actionSendFilter.
+ */
+public class LDNEmailAction implements LDNAction {
+
+ private static final Logger log = LogManager.getLogger(LDNEmailAction.class);
+
+ private final static String DATE_PATTERN = "dd-MM-yyyy HH:mm:ss";
+
+ @Autowired
+ private ConfigurationService configurationService;
+
+ /*
+ * Supported for actionSendFilter are:
+ * -
+ * - GROUP:
+ * - SUBMITTER
+ */
+ private String actionSendFilter;
+
+ // The file name for the requested email
+ private String actionSendEmailTextFile;
+
+ /**
+ * Execute sending an email.
+ *
+ * Template context parameters:
+ *
+ * {0} Service Name
+ * {1} Item Name
+ * {2} Service URL
+ * {3} Item URL
+ * {4} Submitter's Name
+ * {5} Date of the received LDN notification
+ * {6} LDN notification
+ * {7} Item
+ *
+ * @param notification
+ * @param item
+ * @return ActionStatus
+ * @throws Exception
+ */
+ @Override
+ public LDNActionStatus execute(Context context, Notification notification, Item item) throws Exception {
+ try {
+ Locale supportedLocale = I18nUtil.getEPersonLocale(context.getCurrentUser());
+ Email email = Email.getEmail(I18nUtil.getEmailFilename(supportedLocale, actionSendEmailTextFile));
+
+ // Setting recipients email
+ for (String recipient : retrieveRecipientsEmail(item)) {
+ email.addRecipient(recipient);
+ }
+
+ String date = new SimpleDateFormat(DATE_PATTERN).format(Calendar.getInstance().getTime());
+
+ email.addArgument(notification.getActor().getName());
+ email.addArgument(item.getName());
+ email.addArgument(notification.getActor().getId());
+ email.addArgument(notification.getContext() != null ?
+ notification.getContext().getId() : notification.getObject().getId());
+ email.addArgument(item.getSubmitter().getFullName());
+ email.addArgument(date);
+ email.addArgument(notification);
+ email.addArgument(item);
+
+ email.send();
+ } catch (Exception e) {
+ log.error("An Error Occurred while sending a notification email", e);
+ }
+
+ return LDNActionStatus.CONTINUE;
+ }
+
+ /**
+ * @return String
+ */
+ public String getActionSendFilter() {
+ return actionSendFilter;
+ }
+
+ /**
+ * @param actionSendFilter
+ */
+ public void setActionSendFilter(String actionSendFilter) {
+ this.actionSendFilter = actionSendFilter;
+ }
+
+ /**
+ * @return String
+ */
+ public String getActionSendEmailTextFile() {
+ return actionSendEmailTextFile;
+ }
+
+ /**
+ * @param actionSendEmailTextFile
+ */
+ public void setActionSendEmailTextFile(String actionSendEmailTextFile) {
+ this.actionSendEmailTextFile = actionSendEmailTextFile;
+ }
+
+ /**
+ * Parses actionSendFilter for reserved tokens and returns list of email
+ * recipients.
+ *
+ * @param item the item which to get submitter email
+ * @return List list of email recipients
+ */
+ private List retrieveRecipientsEmail(Item item) {
+ List recipients = new LinkedList();
+
+ if (actionSendFilter.startsWith("SUBMITTER")) {
+ recipients.add(item.getSubmitter().getEmail());
+ } else if (actionSendFilter.startsWith("GROUP:")) {
+ String groupName = actionSendFilter.replace("GROUP:", "");
+ String property = format("email.%s.list", groupName);
+ String[] groupEmails = configurationService.getArrayProperty(property);
+ recipients = Arrays.asList(groupEmails);
+ } else {
+ recipients.add(actionSendFilter);
+ }
+
+ return recipients;
+ }
+
+}
\ No newline at end of file
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/action/LDNRelationCorrectionAction.java b/dspace-api/src/main/java/org/dspace/app/ldn/action/LDNRelationCorrectionAction.java
new file mode 100644
index 000000000000..0c2f5dfc1729
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/action/LDNRelationCorrectionAction.java
@@ -0,0 +1,104 @@
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in the LICENSE and NOTICE files at the root of the source
+ * tree and available online at
+ *
+ * http://www.dspace.org/license/
+ */
+package org.dspace.app.ldn.action;
+
+import java.math.BigDecimal;
+import java.sql.SQLException;
+import java.util.Date;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.dspace.app.ldn.NotifyServiceEntity;
+import org.dspace.app.ldn.model.Notification;
+import org.dspace.app.ldn.service.LDNMessageService;
+import org.dspace.content.Item;
+import org.dspace.content.QAEvent;
+import org.dspace.content.service.ItemService;
+import org.dspace.core.Context;
+import org.dspace.handle.service.HandleService;
+import org.dspace.qaevent.service.QAEventService;
+import org.dspace.qaevent.service.dto.NotifyMessageDTO;
+import org.dspace.services.ConfigurationService;
+import org.springframework.beans.factory.annotation.Autowired;
+
+
+/**
+ * Implementation for LDN Correction Action. It creates a QA Event according to the LDN Message received *
+ * @author Francesco Bacchelli (francesco.bacchelli at 4science.it)
+ *
+ */
+public class LDNRelationCorrectionAction implements LDNAction {
+
+ private static final Logger log = LogManager.getLogger(LDNEmailAction.class);
+
+ private String qaEventTopic;
+
+ @Autowired
+ private ConfigurationService configurationService;
+ @Autowired
+ protected ItemService itemService;
+ @Autowired
+ private QAEventService qaEventService;
+ @Autowired
+ private LDNMessageService ldnMessageService;
+ @Autowired
+ private HandleService handleService;
+
+ @Override
+ public LDNActionStatus execute(Context context, Notification notification, Item item) throws Exception {
+ LDNActionStatus result = LDNActionStatus.ABORT;
+ String itemName = itemService.getName(item);
+ QAEvent qaEvent = null;
+ if (notification.getObject() != null) {
+ NotifyMessageDTO message = new NotifyMessageDTO();
+ message.setHref(notification.getObject().getAsSubject());
+ message.setRelationship(notification.getObject().getAsRelationship());
+ if (notification.getOrigin() != null) {
+ message.setServiceId(notification.getOrigin().getId());
+ message.setServiceName(notification.getOrigin().getInbox());
+ }
+ BigDecimal score = getScore(context, notification);
+ double doubleScoreValue = score != null ? score.doubleValue() : 0d;
+ ObjectMapper mapper = new ObjectMapper();
+ qaEvent = new QAEvent(QAEvent.COAR_NOTIFY_SOURCE,
+ handleService.findHandle(context, item), item.getID().toString(), itemName,
+ this.getQaEventTopic(), doubleScoreValue,
+ mapper.writeValueAsString(message),
+ new Date());
+ qaEventService.store(context, qaEvent);
+ result = LDNActionStatus.CONTINUE;
+ }
+
+ return result;
+ }
+
+ private BigDecimal getScore(Context context, Notification notification) throws SQLException {
+
+ if (notification.getOrigin() == null) {
+ return BigDecimal.ZERO;
+ }
+
+ NotifyServiceEntity service = ldnMessageService.findNotifyService(context, notification.getOrigin());
+
+ if (service == null) {
+ return BigDecimal.ZERO;
+ }
+
+ return service.getScore();
+ }
+
+ public String getQaEventTopic() {
+ return qaEventTopic;
+ }
+
+ public void setQaEventTopic(String qaEventTopic) {
+ this.qaEventTopic = qaEventTopic;
+ }
+
+}
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/action/SendLDNMessageAction.java b/dspace-api/src/main/java/org/dspace/app/ldn/action/SendLDNMessageAction.java
new file mode 100644
index 000000000000..16fb43317e2c
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/action/SendLDNMessageAction.java
@@ -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.app.ldn.action;
+
+import java.net.URI;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.http.Header;
+import org.apache.http.HttpException;
+import org.apache.http.HttpHeaders;
+import org.apache.http.HttpStatus;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.dspace.app.ldn.model.Notification;
+import org.dspace.content.Item;
+import org.dspace.core.Context;
+
+/**
+ * Action to send LDN Message
+ *
+ * @author Mohamed Eskander (mohamed.eskander at 4science.com)
+ */
+public class SendLDNMessageAction implements LDNAction {
+
+ private static final Logger log = LogManager.getLogger(SendLDNMessageAction.class);
+
+ private CloseableHttpClient client = null;
+
+ public SendLDNMessageAction() {
+ HttpClientBuilder builder = HttpClientBuilder.create();
+ client = builder
+ .disableAutomaticRetries()
+ .setMaxConnTotal(5)
+ .build();
+ }
+
+ public SendLDNMessageAction(CloseableHttpClient client) {
+ this();
+ if (client != null) {
+ this.client = client;
+ }
+ }
+
+ @Override
+ public LDNActionStatus execute(Context context, Notification notification, Item item) throws Exception {
+ //TODO authorization with Bearer token should be supported.
+
+ String url = notification.getTarget().getInbox();
+
+ HttpPost httpPost = new HttpPost(url);
+ httpPost.addHeader("Content-Type", "application, ld+json");
+ ObjectMapper mapper = new ObjectMapper();
+ httpPost.setEntity(new StringEntity(mapper.writeValueAsString(notification), "UTF-8"));
+
+ LDNActionStatus result = LDNActionStatus.ABORT;
+ // NOTE: Github believes there is a "Potential server-side request forgery due to a user-provided value"
+ // This is a false positive because the LDN Service URL is configured by the user from DSpace.
+ // See the frontend configuration at [dspace.ui.url]/admin/ldn/services
+ try (
+ CloseableHttpResponse response = client.execute(httpPost);
+ ) {
+ if (isSuccessful(response.getStatusLine().getStatusCode())) {
+ result = LDNActionStatus.CONTINUE;
+ } else if (isRedirect(response.getStatusLine().getStatusCode())) {
+ result = handleRedirect(response, httpPost);
+ }
+ } catch (Exception e) {
+ log.error(e);
+ }
+ return result;
+ }
+
+ private boolean isSuccessful(int statusCode) {
+ return statusCode == HttpStatus.SC_ACCEPTED ||
+ statusCode == HttpStatus.SC_CREATED;
+ }
+
+ private boolean isRedirect(int statusCode) {
+ //org.apache.http.HttpStatus has no enum value for 308!
+ return statusCode == (HttpStatus.SC_TEMPORARY_REDIRECT + 1) ||
+ statusCode == HttpStatus.SC_TEMPORARY_REDIRECT;
+ }
+
+ private LDNActionStatus handleRedirect(CloseableHttpResponse oldresponse,
+ HttpPost request) throws HttpException {
+ Header[] urls = oldresponse.getHeaders(HttpHeaders.LOCATION);
+ String url = urls.length > 0 && urls[0] != null ? urls[0].getValue() : null;
+ if (url == null) {
+ throw new HttpException("Error following redirect, unable to reach"
+ + " the correct url.");
+ }
+ LDNActionStatus result = LDNActionStatus.ABORT;
+ try {
+ request.setURI(new URI(url));
+ try (
+ CloseableHttpResponse response = client.execute(request);
+ ) {
+ if (isSuccessful(response.getStatusLine().getStatusCode())) {
+ return LDNActionStatus.CONTINUE;
+ }
+ }
+ } catch (Exception e) {
+ log.error("Error following redirect:", e);
+ }
+
+ return LDNActionStatus.ABORT;
+ }
+}
\ No newline at end of file
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/dao/LDNMessageDao.java b/dspace-api/src/main/java/org/dspace/app/ldn/dao/LDNMessageDao.java
new file mode 100644
index 000000000000..c3f4907cce11
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/dao/LDNMessageDao.java
@@ -0,0 +1,68 @@
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in the LICENSE and NOTICE files at the root of the source
+ * tree and available online at
+ *
+ * http://www.dspace.org/license/
+ */
+package org.dspace.app.ldn.dao;
+
+import java.sql.SQLException;
+import java.util.List;
+
+import org.dspace.app.ldn.LDNMessageEntity;
+import org.dspace.content.Item;
+import org.dspace.core.Context;
+import org.dspace.core.GenericDAO;
+
+/**
+ * Database Access Object interface class for the LDNMessage object.
+ *
+ * The implementation of this class is responsible for all database calls for
+ * the LDNMessage object and is autowired by spring
+ *
+ * @author Mohamed Eskander (mohamed.eskander at 4science.com)
+ */
+public interface LDNMessageDao extends GenericDAO {
+
+ /**
+ * load the oldest ldn messages considering their {@link org.dspace.app.ldn.LDNMessageEntity#queueLastStartTime}
+ * @param context
+ * @param max_attempts consider ldn_message entity with queue_attempts <= max_attempts
+ * @return ldn message entities to be routed
+ * @throws SQLException
+ */
+ public List findOldestMessageToProcess(Context context, int max_attempts) throws SQLException;
+
+ /**
+ * find ldn message entties in processing status and already timed out.
+ * @param context
+ * @param max_attempts consider ldn_message entity with queue_attempts <= max_attempts
+ * @return ldn message entities
+ * @throws SQLException
+ */
+ public List findProcessingTimedoutMessages(Context context, int max_attempts) throws SQLException;
+
+ /**
+ * find all ldn messages related to an item
+ * @param context
+ * @param item item related to the returned ldn messages
+ * @param activities involves only this specific group of activities
+ * @return all ldn messages related to the given item
+ * @throws SQLException
+ */
+ public List findAllMessagesByItem(
+ Context context, Item item, String... activities) throws SQLException;
+
+ /**
+ * find all ldn messages related to an item and to a specific ldn message
+ * @param context
+ * @param msg the referring ldn message
+ * @param item the referring repository item
+ * @param relatedTypes filter for @see org.dspace.app.ldn.LDNMessageEntity#activityStreamType
+ * @return all related ldn messages
+ * @throws SQLException
+ */
+ public List findAllRelatedMessagesByItem(
+ Context context, LDNMessageEntity msg, Item item, String... relatedTypes) throws SQLException;
+}
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/dao/NotifyPatternToTriggerDao.java b/dspace-api/src/main/java/org/dspace/app/ldn/dao/NotifyPatternToTriggerDao.java
new file mode 100644
index 000000000000..9ecd1b728a4e
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/dao/NotifyPatternToTriggerDao.java
@@ -0,0 +1,49 @@
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in the LICENSE and NOTICE files at the root of the source
+ * tree and available online at
+ *
+ * http://www.dspace.org/license/
+ */
+package org.dspace.app.ldn.dao;
+
+import java.sql.SQLException;
+import java.util.List;
+
+import org.dspace.app.ldn.NotifyPatternToTrigger;
+import org.dspace.content.Item;
+import org.dspace.core.Context;
+import org.dspace.core.GenericDAO;
+
+/**
+ * This is the Data Access Object for the {@link NotifyPatternToTrigger} object
+ *
+ * @author Mohamed Eskander (mohamed.eskander at 4science.com)
+ */
+public interface NotifyPatternToTriggerDao extends GenericDAO {
+
+ /**
+ * find the NotifyPatternToTrigger matched with the provided item
+ *
+ * @param context the context
+ * @param item the item
+ * @return the NotifyPatternToTrigger matched the provided item
+ * @throws SQLException if database error
+ */
+ public List findByItem(Context context, Item item) throws SQLException;
+
+ /**
+ * find the NotifyPatternToTrigger matched with the provided
+ * item and pattern
+ *
+ * @param context the context
+ * @param item the item
+ * @param pattern the pattern
+ * @return the NotifyPatternToTrigger matched the provided
+ * item and pattern
+ * @throws SQLException if database error
+ */
+ public List findByItemAndPattern(Context context, Item item, String pattern)
+ throws SQLException;
+
+}
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/dao/NotifyServiceDao.java b/dspace-api/src/main/java/org/dspace/app/ldn/dao/NotifyServiceDao.java
new file mode 100644
index 000000000000..9751b3038290
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/dao/NotifyServiceDao.java
@@ -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.app.ldn.dao;
+
+import java.sql.SQLException;
+import java.util.List;
+
+import org.dspace.app.ldn.NotifyServiceEntity;
+import org.dspace.core.Context;
+import org.dspace.core.GenericDAO;
+
+/**
+ * This is the Data Access Object for the {@link NotifyServiceEntity} object
+ *
+ * @author Mohamed Eskander (mohamed.eskander at 4science.com)
+ */
+public interface NotifyServiceDao extends GenericDAO {
+ /**
+ * find the NotifyServiceEntity matched with the provided ldnUrl
+ *
+ * @param context the context
+ * @param ldnUrl the ldnUrl
+ * @return the NotifyServiceEntity matched the provided ldnUrl
+ * @throws SQLException if database error
+ */
+ public NotifyServiceEntity findByLdnUrl(Context context, String ldnUrl) throws SQLException;
+
+ /**
+ * find all NotifyServiceEntity matched the provided inbound pattern
+ * from the related notifyServiceInboundPatterns
+ * also with 'automatic' equals to false
+ *
+ * @param context the context
+ * @param pattern the ldnUrl
+ * @return all NotifyServiceEntity matched the provided pattern
+ * @throws SQLException if database error
+ */
+ public List findManualServicesByInboundPattern(Context context, String pattern)
+ throws SQLException;
+}
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/dao/NotifyServiceInboundPatternDao.java b/dspace-api/src/main/java/org/dspace/app/ldn/dao/NotifyServiceInboundPatternDao.java
new file mode 100644
index 000000000000..194d30e79598
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/dao/NotifyServiceInboundPatternDao.java
@@ -0,0 +1,47 @@
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in the LICENSE and NOTICE files at the root of the source
+ * tree and available online at
+ *
+ * http://www.dspace.org/license/
+ */
+package org.dspace.app.ldn.dao;
+
+import java.sql.SQLException;
+import java.util.List;
+
+import org.dspace.app.ldn.NotifyServiceEntity;
+import org.dspace.app.ldn.NotifyServiceInboundPattern;
+import org.dspace.core.Context;
+import org.dspace.core.GenericDAO;
+
+/**
+ * This is the Data Access Object for the {@link NotifyServiceInboundPattern} object
+ *
+ * @author Mohamed Eskander (mohamed.eskander at 4science.com)
+ */
+public interface NotifyServiceInboundPatternDao extends GenericDAO {
+
+ /**
+ * find all notifyServiceInboundPatterns matched with
+ * the provided notifyServiceEntity and pattern
+ *
+ * @param context the context
+ * @param notifyServiceEntity the notifyServiceEntity
+ * @param pattern the pattern
+ * @return all notifyServiceInboundPatterns matched with
+ * the provided notifyServiceEntity and pattern
+ * @throws SQLException if database error
+ */
+ public NotifyServiceInboundPattern findByServiceAndPattern(Context context,
+ NotifyServiceEntity notifyServiceEntity,
+ String pattern) throws SQLException;
+ /**
+ * find all automatic notifyServiceInboundPatterns
+ *
+ * @param context the context
+ * @return all automatic notifyServiceInboundPatterns
+ * @throws SQLException if database error
+ */
+ List findAutomaticPatterns(Context context) throws SQLException;
+}
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/dao/impl/LDNMessageDaoImpl.java b/dspace-api/src/main/java/org/dspace/app/ldn/dao/impl/LDNMessageDaoImpl.java
new file mode 100644
index 000000000000..0a0fe672fb0a
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/dao/impl/LDNMessageDaoImpl.java
@@ -0,0 +1,146 @@
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in the LICENSE and NOTICE files at the root of the source
+ * tree and available online at
+ *
+ * http://www.dspace.org/license/
+ */
+package org.dspace.app.ldn.dao.impl;
+
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.LinkedList;
+import java.util.List;
+import javax.persistence.criteria.CriteriaBuilder;
+import javax.persistence.criteria.CriteriaQuery;
+import javax.persistence.criteria.Order;
+import javax.persistence.criteria.Predicate;
+import javax.persistence.criteria.Root;
+
+import org.apache.logging.log4j.Logger;
+import org.dspace.app.ldn.LDNMessageEntity;
+import org.dspace.app.ldn.LDNMessageEntity_;
+import org.dspace.app.ldn.dao.LDNMessageDao;
+import org.dspace.content.Item;
+import org.dspace.core.AbstractHibernateDAO;
+import org.dspace.core.Context;
+
+/**
+ * Hibernate implementation of the Database Access Object interface class for
+ * the LDNMessage object. This class is responsible for all database calls for
+ * the LDNMessage object and is autowired by spring
+ *
+ * @author Mohamed Eskander (mohamed.eskander at 4science.com)
+ */
+public class LDNMessageDaoImpl extends AbstractHibernateDAO implements LDNMessageDao {
+
+ private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(LDNMessageDaoImpl.class);
+
+ @Override
+ public List findOldestMessageToProcess(Context context, int max_attempts) throws SQLException {
+ CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context);
+ CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, LDNMessageEntity.class);
+ Root root = criteriaQuery.from(LDNMessageEntity.class);
+ criteriaQuery.select(root);
+ List andPredicates = new ArrayList<>(3);
+ andPredicates
+ .add(criteriaBuilder.equal(root.get(LDNMessageEntity_.queueStatus), LDNMessageEntity.QUEUE_STATUS_QUEUED));
+ andPredicates.add(criteriaBuilder.lessThan(root.get(LDNMessageEntity_.queueAttempts), max_attempts));
+ andPredicates.add(criteriaBuilder.lessThan(root.get(LDNMessageEntity_.queueTimeout), new Date()));
+ criteriaQuery.where(criteriaBuilder.and(andPredicates.toArray(new Predicate[] {})));
+ List orderList = new LinkedList<>();
+ orderList.add(criteriaBuilder.desc(root.get(LDNMessageEntity_.queueAttempts)));
+ orderList.add(criteriaBuilder.asc(root.get(LDNMessageEntity_.queueLastStartTime)));
+ criteriaQuery.orderBy(orderList);
+ List result = list(context, criteriaQuery, false, LDNMessageEntity.class, -1, -1);
+ if (result == null || result.isEmpty()) {
+ log.debug("No LDN messages found to be processed");
+ }
+ return result;
+ }
+
+ @Override
+ public List findProcessingTimedoutMessages(Context context, int max_attempts)
+ throws SQLException {
+ CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context);
+ CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, LDNMessageEntity.class);
+ Root root = criteriaQuery.from(LDNMessageEntity.class);
+ criteriaQuery.select(root);
+ List andPredicates = new ArrayList<>(3);
+ andPredicates.add(
+ criteriaBuilder.equal(root.get(LDNMessageEntity_.queueStatus), LDNMessageEntity.QUEUE_STATUS_PROCESSING));
+ andPredicates.add(criteriaBuilder.lessThanOrEqualTo(root.get(LDNMessageEntity_.queueAttempts), max_attempts));
+ andPredicates.add(criteriaBuilder.lessThan(root.get(LDNMessageEntity_.queueTimeout), new Date()));
+ criteriaQuery.where(criteriaBuilder.and(andPredicates.toArray(new Predicate[] {})));
+ List orderList = new LinkedList<>();
+ orderList.add(criteriaBuilder.desc(root.get(LDNMessageEntity_.queueAttempts)));
+ orderList.add(criteriaBuilder.asc(root.get(LDNMessageEntity_.queueLastStartTime)));
+ criteriaQuery.orderBy(orderList);
+ List result = list(context, criteriaQuery, false, LDNMessageEntity.class, -1, -1);
+ if (result == null || result.isEmpty()) {
+ log.debug("No LDN messages found to be processed");
+ }
+ return result;
+ }
+
+ @Override
+ public List findAllRelatedMessagesByItem(
+ Context context, LDNMessageEntity msg, Item item, String... relatedTypes) throws SQLException {
+ CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context);
+ CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, LDNMessageEntity.class);
+ Root root = criteriaQuery.from(LDNMessageEntity.class);
+ criteriaQuery.select(root);
+ List andPredicates = new ArrayList<>();
+ Predicate relatedtypePredicate = null;
+ andPredicates.add(
+ criteriaBuilder.equal(root.get(LDNMessageEntity_.queueStatus), LDNMessageEntity.QUEUE_STATUS_PROCESSED));
+ andPredicates.add(
+ criteriaBuilder.isNull(root.get(LDNMessageEntity_.target)));
+ andPredicates.add(
+ criteriaBuilder.equal(root.get(LDNMessageEntity_.inReplyTo), msg));
+ if (relatedTypes != null && relatedTypes.length > 0) {
+ relatedtypePredicate = root.get(LDNMessageEntity_.activityStreamType).in(relatedTypes);
+ andPredicates.add(relatedtypePredicate);
+ }
+ criteriaQuery.where(criteriaBuilder.and(andPredicates.toArray(new Predicate[] {})));
+ List orderList = new LinkedList<>();
+ orderList.add(criteriaBuilder.asc(root.get(LDNMessageEntity_.queueLastStartTime)));
+ orderList.add(criteriaBuilder.desc(root.get(LDNMessageEntity_.queueAttempts)));
+ criteriaQuery.orderBy(orderList);
+ List result = list(context, criteriaQuery, false, LDNMessageEntity.class, -1, -1);
+ if (result == null || result.isEmpty()) {
+ log.debug("No LDN messages ACK found to be processed");
+ }
+ return result;
+ }
+
+ @Override
+ public List findAllMessagesByItem(
+ Context context, Item item, String... activities) throws SQLException {
+ CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context);
+ CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, LDNMessageEntity.class);
+ Root root = criteriaQuery.from(LDNMessageEntity.class);
+ criteriaQuery.select(root);
+ List andPredicates = new ArrayList<>();
+ Predicate activityPredicate = null;
+ andPredicates.add(
+ criteriaBuilder.equal(root.get(LDNMessageEntity_.queueStatus), LDNMessageEntity.QUEUE_STATUS_PROCESSED));
+ andPredicates.add(
+ criteriaBuilder.equal(root.get(LDNMessageEntity_.object), item));
+ if (activities != null && activities.length > 0) {
+ activityPredicate = root.get(LDNMessageEntity_.activityStreamType).in(activities);
+ andPredicates.add(activityPredicate);
+ }
+ criteriaQuery.where(criteriaBuilder.and(andPredicates.toArray(new Predicate[] {})));
+ List orderList = new LinkedList<>();
+ orderList.add(criteriaBuilder.asc(root.get(LDNMessageEntity_.queueLastStartTime)));
+ orderList.add(criteriaBuilder.desc(root.get(LDNMessageEntity_.queueAttempts)));
+ criteriaQuery.orderBy(orderList);
+ List result = list(context, criteriaQuery, false, LDNMessageEntity.class, -1, -1);
+ if (result == null || result.isEmpty()) {
+ log.debug("No LDN messages found");
+ }
+ return result;
+ }
+}
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/dao/impl/NotifyPatternToTriggerDaoImpl.java b/dspace-api/src/main/java/org/dspace/app/ldn/dao/impl/NotifyPatternToTriggerDaoImpl.java
new file mode 100644
index 000000000000..47c584518b14
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/dao/impl/NotifyPatternToTriggerDaoImpl.java
@@ -0,0 +1,58 @@
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in the LICENSE and NOTICE files at the root of the source
+ * tree and available online at
+ *
+ * http://www.dspace.org/license/
+ */
+package org.dspace.app.ldn.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.app.ldn.NotifyPatternToTrigger;
+import org.dspace.app.ldn.NotifyPatternToTrigger_;
+import org.dspace.app.ldn.dao.NotifyPatternToTriggerDao;
+import org.dspace.content.Item;
+import org.dspace.core.AbstractHibernateDAO;
+import org.dspace.core.Context;
+
+/**
+ * Implementation of {@link NotifyPatternToTriggerDao}.
+ *
+ * @author Mohamed Eskander (mohamed.eskander at 4science.com)
+ */
+public class NotifyPatternToTriggerDaoImpl extends AbstractHibernateDAO
+ implements NotifyPatternToTriggerDao {
+
+ @Override
+ public List findByItem(Context context, Item item)
+ throws SQLException {
+ CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context);
+ CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, NotifyPatternToTrigger.class);
+ Root notifyServiceEntityRoot = criteriaQuery.from(NotifyPatternToTrigger.class);
+ criteriaQuery.select(notifyServiceEntityRoot);
+ criteriaQuery.where(criteriaBuilder.equal(
+ notifyServiceEntityRoot.get(NotifyPatternToTrigger_.item), item));
+ return list(context, criteriaQuery, false, NotifyPatternToTrigger.class, -1, -1);
+ }
+ @Override
+ public List findByItemAndPattern(Context context, Item item, String pattern)
+ throws SQLException {
+ CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context);
+ CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, NotifyPatternToTrigger.class);
+ Root notifyServiceEntityRoot = criteriaQuery.from(NotifyPatternToTrigger.class);
+ criteriaQuery.select(notifyServiceEntityRoot);
+ criteriaQuery.where(criteriaBuilder.and(
+ criteriaBuilder.equal(
+ notifyServiceEntityRoot.get(NotifyPatternToTrigger_.item), item),
+ criteriaBuilder.equal(
+ notifyServiceEntityRoot.get(NotifyPatternToTrigger_.pattern), pattern)
+ ));
+ return list(context, criteriaQuery, false, NotifyPatternToTrigger.class, -1, -1);
+ }
+
+}
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/dao/impl/NotifyServiceDaoImpl.java b/dspace-api/src/main/java/org/dspace/app/ldn/dao/impl/NotifyServiceDaoImpl.java
new file mode 100644
index 000000000000..cac804ef0c1f
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/dao/impl/NotifyServiceDaoImpl.java
@@ -0,0 +1,62 @@
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in the LICENSE and NOTICE files at the root of the source
+ * tree and available online at
+ *
+ * http://www.dspace.org/license/
+ */
+package org.dspace.app.ldn.dao.impl;
+
+import java.sql.SQLException;
+import java.util.List;
+import javax.persistence.criteria.CriteriaBuilder;
+import javax.persistence.criteria.CriteriaQuery;
+import javax.persistence.criteria.Join;
+import javax.persistence.criteria.Root;
+
+import org.dspace.app.ldn.NotifyServiceEntity;
+import org.dspace.app.ldn.NotifyServiceEntity_;
+import org.dspace.app.ldn.NotifyServiceInboundPattern;
+import org.dspace.app.ldn.NotifyServiceInboundPattern_;
+import org.dspace.app.ldn.dao.NotifyServiceDao;
+import org.dspace.core.AbstractHibernateDAO;
+import org.dspace.core.Context;
+
+/**
+ * Implementation of {@link NotifyServiceDao}.
+ *
+ * @author Mohamed Eskander (mohamed.eskander at 4science.com)
+ */
+public class NotifyServiceDaoImpl extends AbstractHibernateDAO implements NotifyServiceDao {
+
+ @Override
+ public NotifyServiceEntity findByLdnUrl(Context context, String ldnUrl) throws SQLException {
+ CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context);
+ CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, NotifyServiceEntity.class);
+ Root notifyServiceEntityRoot = criteriaQuery.from(NotifyServiceEntity.class);
+ criteriaQuery.select(notifyServiceEntityRoot);
+ criteriaQuery.where(criteriaBuilder.equal(
+ notifyServiceEntityRoot.get(NotifyServiceEntity_.ldnUrl), ldnUrl));
+ return uniqueResult(context, criteriaQuery, false, NotifyServiceEntity.class);
+ }
+
+ @Override
+ public List findManualServicesByInboundPattern(Context context, String pattern)
+ throws SQLException {
+ CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context);
+ CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, NotifyServiceEntity.class);
+ Root notifyServiceEntityRoot = criteriaQuery.from(NotifyServiceEntity.class);
+
+ Join notifyServiceInboundPatternJoin =
+ notifyServiceEntityRoot.join(NotifyServiceEntity_.inboundPatterns);
+
+ criteriaQuery.select(notifyServiceEntityRoot);
+ criteriaQuery.where(criteriaBuilder.and(
+ criteriaBuilder.equal(
+ notifyServiceInboundPatternJoin.get(NotifyServiceInboundPattern_.pattern), pattern),
+ criteriaBuilder.equal(
+ notifyServiceInboundPatternJoin.get(NotifyServiceInboundPattern_.automatic), false)));
+
+ return list(context, criteriaQuery, false, NotifyServiceEntity.class, -1, -1);
+ }
+}
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/dao/impl/NotifyServiceInboundPatternDaoImpl.java b/dspace-api/src/main/java/org/dspace/app/ldn/dao/impl/NotifyServiceInboundPatternDaoImpl.java
new file mode 100644
index 000000000000..5168fd0bedf8
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/dao/impl/NotifyServiceInboundPatternDaoImpl.java
@@ -0,0 +1,59 @@
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in the LICENSE and NOTICE files at the root of the source
+ * tree and available online at
+ *
+ * http://www.dspace.org/license/
+ */
+package org.dspace.app.ldn.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.app.ldn.NotifyServiceEntity;
+import org.dspace.app.ldn.NotifyServiceInboundPattern;
+import org.dspace.app.ldn.NotifyServiceInboundPattern_;
+import org.dspace.app.ldn.dao.NotifyServiceInboundPatternDao;
+import org.dspace.core.AbstractHibernateDAO;
+import org.dspace.core.Context;
+
+/**
+ * Implementation of {@link NotifyServiceInboundPatternDao}.
+ *
+ * @author Mohamed Eskander (mohamed.eskander at 4science.com)
+ */
+public class NotifyServiceInboundPatternDaoImpl
+ extends AbstractHibernateDAO implements NotifyServiceInboundPatternDao {
+
+ @Override
+ public NotifyServiceInboundPattern findByServiceAndPattern(Context context, NotifyServiceEntity notifyServiceEntity,
+ String pattern) throws SQLException {
+ CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context);
+ CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, NotifyServiceInboundPattern.class);
+ Root inboundPatternRoot = criteriaQuery.from(NotifyServiceInboundPattern.class);
+ criteriaQuery.select(inboundPatternRoot);
+ criteriaQuery.where(criteriaBuilder.and(
+ criteriaBuilder.equal(
+ inboundPatternRoot.get(NotifyServiceInboundPattern_.notifyService), notifyServiceEntity),
+ criteriaBuilder.equal(
+ inboundPatternRoot.get(NotifyServiceInboundPattern_.pattern), pattern)
+ ));
+ return uniqueResult(context, criteriaQuery, false, NotifyServiceInboundPattern.class);
+ }
+
+ @Override
+ public List findAutomaticPatterns(Context context) throws SQLException {
+ CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context);
+ CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, NotifyServiceInboundPattern.class);
+ Root inboundPatternRoot = criteriaQuery.from(NotifyServiceInboundPattern.class);
+ criteriaQuery.select(inboundPatternRoot);
+ criteriaQuery.where(
+ criteriaBuilder.equal(
+ inboundPatternRoot.get(NotifyServiceInboundPattern_.automatic), true)
+ );
+ return list(context, criteriaQuery, false, NotifyServiceInboundPattern.class, -1, -1);
+ }
+}
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/factory/LDNMessageServiceFactory.java b/dspace-api/src/main/java/org/dspace/app/ldn/factory/LDNMessageServiceFactory.java
new file mode 100644
index 000000000000..bbf521123bca
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/factory/LDNMessageServiceFactory.java
@@ -0,0 +1,29 @@
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in the LICENSE and NOTICE files at the root of the source
+ * tree and available online at
+ *
+ * http://www.dspace.org/license/
+ */
+package org.dspace.app.ldn.factory;
+
+import org.dspace.app.ldn.service.LDNMessageService;
+import org.dspace.services.factory.DSpaceServicesFactory;
+
+/**
+ * Abstract factory to get services for the NotifyService package,
+ * use NotifyServiceFactory.getInstance() to retrieve an implementation
+ *
+ * @author Francesco Bacchelli (francesco.bacchelli at 4science.com)
+ */
+public abstract class LDNMessageServiceFactory {
+
+ public abstract LDNMessageService getLDNMessageService();
+
+ public static LDNMessageServiceFactory getInstance() {
+ return DSpaceServicesFactory.getInstance()
+ .getServiceManager()
+ .getServiceByName("ldnMessageServiceFactory",
+ LDNMessageServiceFactory.class);
+ }
+}
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/factory/LDNMessageServiceFactoryImpl.java b/dspace-api/src/main/java/org/dspace/app/ldn/factory/LDNMessageServiceFactoryImpl.java
new file mode 100644
index 000000000000..a001ece04069
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/factory/LDNMessageServiceFactoryImpl.java
@@ -0,0 +1,29 @@
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in the LICENSE and NOTICE files at the root of the source
+ * tree and available online at
+ *
+ * http://www.dspace.org/license/
+ */
+package org.dspace.app.ldn.factory;
+
+import org.dspace.app.ldn.service.LDNMessageService;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * Factory implementation to get services for the notifyservices package, use
+ * NotifyServiceFactory.getInstance() to retrieve an implementation
+ *
+ * @author Francesco Bacchelli (francesco.bacchelli at 4science.com)
+ */
+public class LDNMessageServiceFactoryImpl extends LDNMessageServiceFactory {
+
+ @Autowired(required = true)
+ private LDNMessageService ldnMessageService;
+
+ @Override
+ public LDNMessageService getLDNMessageService() {
+ return ldnMessageService;
+ }
+
+}
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/factory/LDNRouterFactory.java b/dspace-api/src/main/java/org/dspace/app/ldn/factory/LDNRouterFactory.java
new file mode 100644
index 000000000000..4b0f107d2498
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/factory/LDNRouterFactory.java
@@ -0,0 +1,29 @@
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in the LICENSE and NOTICE files at the root of the source
+ * tree and available online at
+ *
+ * http://www.dspace.org/license/
+ */
+package org.dspace.app.ldn.factory;
+
+import org.dspace.app.ldn.LDNRouter;
+import org.dspace.services.factory.DSpaceServicesFactory;
+
+/**
+ * Abstract factory to get services for the ldn package, use
+ * LDNRouterFactory.getInstance() to retrieve an implementation
+ *
+ * @author Francesco Bacchelli (francesco.bacchelli at 4science.com)
+ */
+public abstract class LDNRouterFactory {
+
+ public abstract LDNRouter getLDNRouter();
+
+ public static LDNRouterFactory getInstance() {
+ return DSpaceServicesFactory.getInstance()
+ .getServiceManager()
+ .getServiceByName("ldnRouter",
+ LDNRouterFactory.class);
+ }
+}
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/factory/LDNRouterFactoryImpl.java b/dspace-api/src/main/java/org/dspace/app/ldn/factory/LDNRouterFactoryImpl.java
new file mode 100644
index 000000000000..f411b9d935d0
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/factory/LDNRouterFactoryImpl.java
@@ -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.app.ldn.factory;
+
+import org.dspace.app.ldn.LDNRouter;
+import org.springframework.beans.factory.annotation.Autowired;
+/**
+ * Factory implementation to get services for the ldn package,
+ * use ldnRouter spring bean instance to retrieve an implementation
+ *
+ * @author Francesco Bacchelli (mohamed.eskander at 4science.com)
+ */
+public class LDNRouterFactoryImpl extends LDNRouterFactory {
+
+ @Autowired(required = true)
+ private LDNRouter ldnRouter;
+
+ @Override
+ public LDNRouter getLDNRouter() {
+ return ldnRouter;
+ }
+
+}
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/factory/NotifyServiceFactory.java b/dspace-api/src/main/java/org/dspace/app/ldn/factory/NotifyServiceFactory.java
new file mode 100644
index 000000000000..ea488ca25031
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/factory/NotifyServiceFactory.java
@@ -0,0 +1,36 @@
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in the LICENSE and NOTICE files at the root of the source
+ * tree and available online at
+ *
+ * http://www.dspace.org/license/
+ */
+package org.dspace.app.ldn.factory;
+
+import org.dspace.app.ldn.service.LDNMessageService;
+import org.dspace.app.ldn.service.NotifyPatternToTriggerService;
+import org.dspace.app.ldn.service.NotifyService;
+import org.dspace.app.ldn.service.NotifyServiceInboundPatternService;
+import org.dspace.services.factory.DSpaceServicesFactory;
+
+/**
+ * Abstract factory to get services for the NotifyService package,
+ * use NotifyServiceFactory.getInstance() to retrieve an implementation
+ *
+ * @author Mohamed Eskander (mohamed.eskander at 4science.com)
+ */
+public abstract class NotifyServiceFactory {
+
+ public abstract NotifyService getNotifyService();
+
+ public abstract NotifyServiceInboundPatternService getNotifyServiceInboundPatternService();
+
+ public abstract NotifyPatternToTriggerService getNotifyPatternToTriggerService();
+
+ public abstract LDNMessageService getLDNMessageService();
+
+ public static NotifyServiceFactory getInstance() {
+ return DSpaceServicesFactory.getInstance().getServiceManager().getServiceByName(
+ "notifyServiceFactory", NotifyServiceFactory.class);
+ }
+}
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/factory/NotifyServiceFactoryImpl.java b/dspace-api/src/main/java/org/dspace/app/ldn/factory/NotifyServiceFactoryImpl.java
new file mode 100644
index 000000000000..84e15ee261a2
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/factory/NotifyServiceFactoryImpl.java
@@ -0,0 +1,56 @@
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in the LICENSE and NOTICE files at the root of the source
+ * tree and available online at
+ *
+ * http://www.dspace.org/license/
+ */
+package org.dspace.app.ldn.factory;
+
+import org.dspace.app.ldn.service.LDNMessageService;
+import org.dspace.app.ldn.service.NotifyPatternToTriggerService;
+import org.dspace.app.ldn.service.NotifyService;
+import org.dspace.app.ldn.service.NotifyServiceInboundPatternService;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * Factory implementation to get services for the notifyservices package,
+ * use NotifyServiceFactory.getInstance() to retrieve an implementation
+ *
+ * @author Mohamed Eskander (mohamed.eskander at 4science.com)
+ */
+public class NotifyServiceFactoryImpl extends NotifyServiceFactory {
+
+ @Autowired(required = true)
+ private NotifyService notifyService;
+
+ @Autowired(required = true)
+ private NotifyServiceInboundPatternService notifyServiceInboundPatternService;
+
+ @Autowired(required = true)
+ private NotifyPatternToTriggerService notifyPatternToTriggerService;
+
+ @Autowired(required = true)
+ private LDNMessageService ldnMessageService;
+
+ @Override
+ public NotifyService getNotifyService() {
+ return notifyService;
+ }
+
+ @Override
+ public NotifyServiceInboundPatternService getNotifyServiceInboundPatternService() {
+ return notifyServiceInboundPatternService;
+ }
+
+ @Override
+ public NotifyPatternToTriggerService getNotifyPatternToTriggerService() {
+ return notifyPatternToTriggerService;
+ }
+
+ @Override
+ public LDNMessageService getLDNMessageService() {
+ return ldnMessageService;
+ }
+
+}
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/model/Actor.java b/dspace-api/src/main/java/org/dspace/app/ldn/model/Actor.java
new file mode 100644
index 000000000000..a81cc3f8008a
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/model/Actor.java
@@ -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.app.ldn.model;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * used to map @see org.dspace.app.ldn.model.Notification
+ */
+public class Actor extends Base {
+
+ @JsonProperty("name")
+ private String name;
+
+ /**
+ *
+ */
+ public Actor() {
+ super();
+ }
+
+ /**
+ * @return String
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * @param name
+ */
+ public void setName(String name) {
+ this.name = name;
+ }
+
+}
\ No newline at end of file
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/model/Base.java b/dspace-api/src/main/java/org/dspace/app/ldn/model/Base.java
new file mode 100644
index 000000000000..6ddaae110e1f
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/model/Base.java
@@ -0,0 +1,115 @@
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in the LICENSE and NOTICE files at the root of the source
+ * tree and available online at
+ *
+ * http://www.dspace.org/license/
+ */
+package org.dspace.app.ldn.model;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * used to map @see org.dspace.app.ldn.model.Notification
+ */
+@JsonInclude(Include.NON_EMPTY)
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class Base {
+
+ @JsonProperty("id")
+ private String id;
+
+ @JsonProperty("type")
+ private Set type;
+
+ /**
+ *
+ */
+ public Base() {
+ type = new HashSet<>();
+ }
+
+ /**
+ * @return String
+ */
+ public String getId() {
+ return id;
+ }
+
+ /**
+ * @param id
+ */
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ /**
+ * @return Set
+ */
+ public Set getType() {
+ return type;
+ }
+
+ /**
+ * @param type
+ */
+ public void setType(java.lang.Object type) {
+ if (type instanceof String) {
+ this.type.add((String) type);
+ } else if (type instanceof Collection) {
+ this.type.addAll((Collection) type);
+ }
+ }
+
+ /**
+ * @param type
+ */
+ public void addType(String type) {
+ this.type.add(type);
+ }
+
+ /**
+ * @return int
+ */
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((id == null) ? 0 : id.hashCode());
+ return result;
+ }
+
+ /**
+ * @param obj
+ * @return boolean
+ */
+ @Override
+ public boolean equals(java.lang.Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ Base other = (Base) obj;
+ if (id == null) {
+ if (other.id != null) {
+ return false;
+ }
+ } else if (!id.equals(other.id)) {
+ return false;
+ }
+ return true;
+ }
+
+}
\ No newline at end of file
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/model/Citation.java b/dspace-api/src/main/java/org/dspace/app/ldn/model/Citation.java
new file mode 100644
index 000000000000..c6629f5e7b91
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/model/Citation.java
@@ -0,0 +1,59 @@
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in the LICENSE and NOTICE files at the root of the source
+ * tree and available online at
+ *
+ * http://www.dspace.org/license/
+ */
+package org.dspace.app.ldn.model;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+
+/**
+ * used to map @see org.dspace.app.ldn.model.Notification
+ */
+public class Citation extends Base {
+
+ @JsonProperty("ietf:cite-as")
+ private String ietfCiteAs;
+
+ @JsonProperty("url")
+ private Url url;
+
+ /**
+ *
+ */
+ public Citation() {
+ super();
+ }
+
+ /**
+ * @return String
+ */
+ public String getIetfCiteAs() {
+ return ietfCiteAs;
+ }
+
+ /**
+ * @param ietfCiteAs
+ */
+ public void setIetfCiteAs(String ietfCiteAs) {
+ this.ietfCiteAs = ietfCiteAs;
+ }
+
+ /**
+ * @return Url
+ */
+ public Url getUrl() {
+ return url;
+ }
+
+ /**
+ * @param url
+ */
+ public void setUrl(Url url) {
+ this.url = url;
+ }
+
+}
\ No newline at end of file
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/model/Context.java b/dspace-api/src/main/java/org/dspace/app/ldn/model/Context.java
new file mode 100644
index 000000000000..78fe37341697
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/model/Context.java
@@ -0,0 +1,61 @@
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in the LICENSE and NOTICE files at the root of the source
+ * tree and available online at
+ *
+ * http://www.dspace.org/license/
+ */
+package org.dspace.app.ldn.model;
+
+import java.util.List;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+
+/**
+ * used to map @see org.dspace.app.ldn.model.Notification
+ */
+public class Context extends Citation {
+
+ @JsonProperty("IsSupplementedBy")
+ private List isSupplementedBy;
+
+ @JsonProperty("IsSupplementTo")
+ private List isSupplementTo;
+
+ /**
+ *
+ */
+ public Context() {
+ super();
+ }
+
+ /**
+ * @return List
+ */
+ public List getIsSupplementedBy() {
+ return isSupplementedBy;
+ }
+
+ /**
+ * @param isSupplementedBy
+ */
+ public void setIsSupplementedBy(List isSupplementedBy) {
+ this.isSupplementedBy = isSupplementedBy;
+ }
+
+ /**
+ * @return List
+ */
+ public List getIsSupplementTo() {
+ return isSupplementTo;
+ }
+
+ /**
+ * @param isSupplementTo
+ */
+ public void setIsSupplementTo(List isSupplementTo) {
+ this.isSupplementTo = isSupplementTo;
+ }
+
+}
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/model/Notification.java b/dspace-api/src/main/java/org/dspace/app/ldn/model/Notification.java
new file mode 100644
index 000000000000..52bc9840f42d
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/model/Notification.java
@@ -0,0 +1,159 @@
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in the LICENSE and NOTICE files at the root of the source
+ * tree and available online at
+ *
+ * http://www.dspace.org/license/
+ */
+package org.dspace.app.ldn.model;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonPropertyOrder;
+
+/**
+ * the json object from witch @see org.dspace.app.ldn.LDNMessageEntity are created.
+ * see official coar doc
+ */
+@JsonPropertyOrder(value = {
+ "@context",
+ "id",
+ "type",
+ "actor",
+ "context",
+ "object",
+ "origin",
+ "target",
+ "inReplyTo"
+})
+public class Notification extends Base {
+
+ @JsonProperty("@context")
+ private String[] c = new String[] {
+ "https://purl.org/coar/notify",
+ "https://www.w3.org/ns/activitystreams"
+ };
+
+ @JsonProperty("actor")
+ private Actor actor;
+
+ @JsonProperty("context")
+ private Context context;
+
+ @JsonProperty("object")
+ private Object object;
+
+ @JsonProperty("origin")
+ private Service origin;
+
+ @JsonProperty("target")
+ private Service target;
+
+ @JsonProperty("inReplyTo")
+ private String inReplyTo;
+
+ /**
+ *
+ */
+ public Notification() {
+ super();
+ }
+
+ /**
+ * @return String[]
+ */
+ public String[] getC() {
+ return c;
+ }
+
+ /**
+ * @param c
+ */
+ public void setC(String[] c) {
+ this.c = c;
+ }
+
+ /**
+ * @return Actor
+ */
+ public Actor getActor() {
+ return actor;
+ }
+
+ /**
+ * @param actor
+ */
+ public void setActor(Actor actor) {
+ this.actor = actor;
+ }
+
+ /**
+ * @return Context
+ */
+ public Context getContext() {
+ return context;
+ }
+
+ /**
+ * @param context
+ */
+ public void setContext(Context context) {
+ this.context = context;
+ }
+
+ /**
+ * @return Object
+ */
+ public Object getObject() {
+ return object;
+ }
+
+ /**
+ * @param object
+ */
+ public void setObject(Object object) {
+ this.object = object;
+ }
+
+ /**
+ * @return Service
+ */
+ public Service getOrigin() {
+ return origin;
+ }
+
+ /**
+ * @param origin
+ */
+ public void setOrigin(Service origin) {
+ this.origin = origin;
+ }
+
+ /**
+ * @return Service
+ */
+ public Service getTarget() {
+ return target;
+ }
+
+ /**
+ * @param target
+ */
+ public void setTarget(Service target) {
+ this.target = target;
+ }
+
+ /**
+ * @return String
+ */
+ public String getInReplyTo() {
+ return inReplyTo;
+ }
+
+ /**
+ * @param inReplyTo
+ */
+ public void setInReplyTo(String inReplyTo) {
+ this.inReplyTo = inReplyTo;
+ }
+
+}
\ No newline at end of file
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/model/NotifyRequestStatus.java b/dspace-api/src/main/java/org/dspace/app/ldn/model/NotifyRequestStatus.java
new file mode 100644
index 000000000000..0302b528aa8d
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/model/NotifyRequestStatus.java
@@ -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.app.ldn.model;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+import com.fasterxml.jackson.annotation.JsonPropertyOrder;
+
+@JsonPropertyOrder(value = {
+ "itemuuid",
+ "notifyStatus"
+})
+
+/**
+ * item requests of LDN messages of type
+ *
+ * "Offer", "coar-notify:EndorsementAction"
+ * "Offer", "coar-notify:IngestAction"
+ * "Offer", "coar-notify:ReviewAction"
+ *
+ * and their acknownledgements - if any
+ *
+ * @author Francesco Bacchelli (francesco.bacchelli at 4science dot it)
+ */
+public class NotifyRequestStatus extends Base {
+
+ private UUID itemUuid;
+
+ private List notifyStatus;
+
+ public NotifyRequestStatus() {
+ super();
+ this.notifyStatus = new ArrayList();
+ }
+
+ public UUID getItemUuid() {
+ return itemUuid;
+ }
+
+ public void setItemUuid(UUID itemUuid) {
+ this.itemUuid = itemUuid;
+ }
+
+ public void addRequestStatus(RequestStatus rs) {
+ this.notifyStatus.add(rs);
+ }
+
+ public List getNotifyStatus() {
+ return notifyStatus;
+ }
+
+ public void setNotifyStatus(List notifyStatus) {
+ this.notifyStatus = notifyStatus;
+ }
+
+}
\ No newline at end of file
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/model/NotifyRequestStatusEnum.java b/dspace-api/src/main/java/org/dspace/app/ldn/model/NotifyRequestStatusEnum.java
new file mode 100644
index 000000000000..437c624f84d8
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/model/NotifyRequestStatusEnum.java
@@ -0,0 +1,18 @@
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in the LICENSE and NOTICE files at the root of the source
+ * tree and available online at
+ *
+ * http://www.dspace.org/license/
+ */
+package org.dspace.app.ldn.model;
+/**
+ * REQUESTED means acknowledgements not received yet
+ * ACCEPTED means acknowledgements of "Accept" type received
+ * REJECTED means ack of "TentativeReject" type received
+ *
+ * @author Francesco Bacchelli (francesco.bacchelli at 4science.com)
+ */
+public enum NotifyRequestStatusEnum {
+ REJECTED, ACCEPTED, REQUESTED
+}
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/model/Object.java b/dspace-api/src/main/java/org/dspace/app/ldn/model/Object.java
new file mode 100644
index 000000000000..8913af47dae1
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/model/Object.java
@@ -0,0 +1,74 @@
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in the LICENSE and NOTICE files at the root of the source
+ * tree and available online at
+ *
+ * http://www.dspace.org/license/
+ */
+package org.dspace.app.ldn.model;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * used to map @see org.dspace.app.ldn.model.Notification
+ */
+public class Object extends Citation {
+
+ @JsonProperty("as:object")
+ private String asObject;
+
+ @JsonProperty("as:relationship")
+ private String asRelationship;
+
+ @JsonProperty("as:subject")
+ private String asSubject;
+
+ @JsonProperty("sorg:name")
+ private String title;
+
+ /**
+ *
+ */
+ public Object() {
+ super();
+ }
+
+ /**
+ * @return String
+ */
+ public String getTitle() {
+ return title;
+ }
+
+ /**
+ * @param title
+ */
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ public String getAsObject() {
+ return asObject;
+ }
+
+ public void setAsObject(String asObject) {
+ this.asObject = asObject;
+ }
+
+ public String getAsRelationship() {
+ return asRelationship;
+ }
+
+ public void setAsRelationship(String asRelationship) {
+ this.asRelationship = asRelationship;
+ }
+
+ public String getAsSubject() {
+ return asSubject;
+ }
+
+ public void setAsSubject(String asSubject) {
+ this.asSubject = asSubject;
+ }
+
+}
\ No newline at end of file
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/model/RequestStatus.java b/dspace-api/src/main/java/org/dspace/app/ldn/model/RequestStatus.java
new file mode 100644
index 000000000000..d19369830787
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/model/RequestStatus.java
@@ -0,0 +1,47 @@
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in the LICENSE and NOTICE files at the root of the source
+ * tree and available online at
+ *
+ * http://www.dspace.org/license/
+ */
+package org.dspace.app.ldn.model;
+
+/**
+ * Informations about the Offer and Acknowledgements targeting a specified Item
+ *
+ * @author Francesco Bacchelli (francesco.bacchelli at 4science.com)
+ */
+public class RequestStatus {
+
+ private String serviceName;
+ private String serviceUrl;
+ private String offerType;
+ private NotifyRequestStatusEnum status;
+
+ public String getServiceName() {
+ return serviceName;
+ }
+ public void setServiceName(String serviceName) {
+ this.serviceName = serviceName;
+ }
+ public String getServiceUrl() {
+ return serviceUrl;
+ }
+ public void setServiceUrl(String serviceUrl) {
+ this.serviceUrl = serviceUrl;
+ }
+ public NotifyRequestStatusEnum getStatus() {
+ return status;
+ }
+ public void setStatus(NotifyRequestStatusEnum status) {
+ this.status = status;
+ }
+ public String getOfferType() {
+ return offerType;
+ }
+ public void setOfferType(String offerType) {
+ this.offerType = offerType;
+ }
+
+}
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/model/Service.java b/dspace-api/src/main/java/org/dspace/app/ldn/model/Service.java
new file mode 100644
index 000000000000..cdd3ba5bb536
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/model/Service.java
@@ -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.app.ldn.model;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * used to map @see org.dspace.app.ldn.model.Notification
+ */
+public class Service extends Base {
+
+ @JsonProperty("inbox")
+ private String inbox;
+
+ /**
+ *
+ */
+ public Service() {
+ super();
+ }
+
+ /**
+ * @return String
+ */
+ public String getInbox() {
+ return inbox;
+ }
+
+ /**
+ * @param inbox
+ */
+ public void setInbox(String inbox) {
+ this.inbox = inbox;
+ }
+
+}
\ No newline at end of file
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/model/Url.java b/dspace-api/src/main/java/org/dspace/app/ldn/model/Url.java
new file mode 100644
index 000000000000..47093e02e44b
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/model/Url.java
@@ -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.app.ldn.model;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * used to map @see org.dspace.app.ldn.model.Citation
+ */
+public class Url extends Base {
+
+ @JsonProperty("mediaType")
+ private String mediaType;
+
+ /**
+ *
+ */
+ public Url() {
+ super();
+ }
+
+ /**
+ * @return String
+ */
+ public String getMediaType() {
+ return mediaType;
+ }
+
+ /**
+ * @param mediaType
+ */
+ public void setMediaType(String mediaType) {
+ this.mediaType = mediaType;
+ }
+
+}
\ No newline at end of file
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/processor/LDNContextRepeater.java b/dspace-api/src/main/java/org/dspace/app/ldn/processor/LDNContextRepeater.java
new file mode 100644
index 000000000000..a5ef0957ff8f
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/processor/LDNContextRepeater.java
@@ -0,0 +1,141 @@
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in the LICENSE and NOTICE files at the root of the source
+ * tree and available online at
+ *
+ * http://www.dspace.org/license/
+ */
+package org.dspace.app.ldn.processor;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Objects;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.dspace.app.ldn.model.Context;
+import org.dspace.app.ldn.model.Notification;
+
+/**
+ * Context repeater to iterate over array context properties of a received
+ * notification. The returned notification iterator is a notification with
+ * context array elements hoisted onto the root of the notification context.
+ */
+public class LDNContextRepeater {
+
+ private final static Logger log = LogManager.getLogger(LDNContextRepeater.class);
+
+ private final static String CONTEXT = "context";
+
+ private String repeatOver;
+
+ /**
+ * @return String
+ */
+ public String getRepeatOver() {
+ return repeatOver;
+ }
+
+ /**
+ * @param repeatOver
+ */
+ public void setRepeatOver(String repeatOver) {
+ this.repeatOver = repeatOver;
+ }
+
+ /**
+ * @param notification
+ * @return Iterator
+ */
+ public Iterator iterator(Notification notification) {
+ return new NotificationIterator(notification, repeatOver);
+ }
+
+ /**
+ * Private inner class defining the notification iterator.
+ */
+ private class NotificationIterator implements Iterator {
+
+ private final List notifications;
+
+ /**
+ * Convert notification to JsonNode in order to clone for each context array
+ * element. Each element is then hoisted to the root of the cloned notification
+ * context.
+ *
+ * @param notification received notification
+ * @param repeatOver which context property to repeat over
+ */
+ private NotificationIterator(Notification notification, String repeatOver) {
+ this.notifications = new ArrayList<>();
+
+ if (Objects.nonNull(repeatOver)) {
+ ObjectMapper objectMapper = new ObjectMapper();
+
+ JsonNode notificationNode = objectMapper.valueToTree(notification);
+
+ log.debug("Notification {}", notificationNode);
+
+ JsonNode topContextNode = notificationNode.get(CONTEXT);
+ if (topContextNode.isNull()) {
+ log.warn("Notification is missing context");
+ return;
+ }
+
+ JsonNode contextArrayNode = topContextNode.get(repeatOver);
+ if (contextArrayNode == null || contextArrayNode.isNull()) {
+ log.error("Notification context {} is not defined", repeatOver);
+ return;
+ }
+
+ if (contextArrayNode.isArray()) {
+
+ for (JsonNode contextNode : ((ArrayNode) contextArrayNode)) {
+
+ try {
+ Context context = objectMapper.treeToValue(contextNode, Context.class);
+
+ Notification copy = objectMapper.treeToValue(notificationNode, Notification.class);
+
+ copy.setContext(context);
+
+ this.notifications.add(copy);
+ } catch (JsonProcessingException e) {
+ log.error("Failed to copy notification");
+ }
+
+ }
+
+ } else {
+ log.error("Notification context {} is not an array", repeatOver);
+ }
+
+ } else {
+ this.notifications.add(notification);
+ }
+ }
+
+ /**
+ * @return boolean
+ */
+ @Override
+ public boolean hasNext() {
+ return !this.notifications.isEmpty();
+ }
+
+ /**
+ * @return Notification
+ */
+ @Override
+ public Notification next() {
+ return this.notifications.remove(0);
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/processor/LDNMetadataProcessor.java b/dspace-api/src/main/java/org/dspace/app/ldn/processor/LDNMetadataProcessor.java
new file mode 100644
index 000000000000..223c8b7a3422
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/processor/LDNMetadataProcessor.java
@@ -0,0 +1,187 @@
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in the LICENSE and NOTICE files at the root of the source
+ * tree and available online at
+ *
+ * http://www.dspace.org/license/
+ */
+package org.dspace.app.ldn.processor;
+
+import static java.lang.String.format;
+
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.UUID;
+
+import org.apache.http.HttpStatus;
+import org.apache.http.client.HttpResponseException;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.dspace.app.ldn.action.LDNAction;
+import org.dspace.app.ldn.action.LDNActionStatus;
+import org.dspace.app.ldn.model.Notification;
+import org.dspace.app.ldn.utility.LDNUtils;
+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.service.HandleService;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * Linked Data Notification metadata processor for consuming notifications. The
+ * storage of notification details are within item metadata.
+ */
+public class LDNMetadataProcessor implements LDNProcessor {
+
+ private final static Logger log = LogManager.getLogger(LDNMetadataProcessor.class);
+
+ @Autowired
+ private ItemService itemService;
+
+ @Autowired
+ private HandleService handleService;
+
+ private LDNContextRepeater repeater = new LDNContextRepeater();
+
+ private List actions = new ArrayList<>();
+
+ /**
+ * Initialize velocity engine for templating.
+ */
+ private LDNMetadataProcessor() {
+
+ }
+
+ /**
+ * Process notification by repeating over context, processing each context
+ * notification, and running actions post processing.
+ *
+ * @param notification received notification
+ * @throws Exception something went wrong processing the notification
+ */
+ @Override
+ public void process(Context context, Notification notification) throws Exception {
+ Item item = lookupItem(context, notification);
+ runActions(context, notification, item);
+ }
+
+ /**
+ * Run all actions defined for the processor.
+ *
+ * @param notification current context notification
+ * @param item associated item
+ *
+ * @return ActionStatus result status of running the action
+ *
+ * @throws Exception failed execute the action
+ */
+ private LDNActionStatus runActions(Context context, Notification notification, Item item) throws Exception {
+ LDNActionStatus operation = LDNActionStatus.CONTINUE;
+ for (LDNAction action : actions) {
+ log.info("Running action {} for notification {} {}",
+ action.getClass().getSimpleName(),
+ notification.getId(),
+ notification.getType());
+
+ operation = action.execute(context, notification, item);
+ if (operation == LDNActionStatus.ABORT) {
+ break;
+ }
+ }
+
+ return operation;
+ }
+
+ /**
+ * @return LDNContextRepeater
+ */
+ public LDNContextRepeater getRepeater() {
+ return repeater;
+ }
+
+ /**
+ * @param repeater
+ */
+ public void setRepeater(LDNContextRepeater repeater) {
+ this.repeater = repeater;
+ }
+
+ /**
+ * @return List
+ */
+ public List getActions() {
+ return actions;
+ }
+
+ /**
+ * @param actions
+ */
+ public void setActions(List actions) {
+ this.actions = actions;
+ }
+
+ /**
+ * Lookup associated item to the notification context. If UUID in URL, lookup bu
+ * UUID, else lookup by handle.
+ *
+ * @param context current context
+ * @param notification current context notification
+ *
+ * @return Item associated item
+ *
+ * @throws SQLException failed to lookup item
+ * @throws HttpResponseException redirect failure
+ */
+ private Item lookupItem(Context context, Notification notification) throws SQLException, HttpResponseException {
+ Item item = null;
+
+ String url = null;
+ if (notification.getContext() != null) {
+ url = notification.getContext().getId();
+ } else {
+ url = notification.getObject().getId();
+ }
+
+ log.info("Looking up item {}", url);
+
+ if (LDNUtils.hasUUIDInURL(url)) {
+ UUID uuid = LDNUtils.getUUIDFromURL(url);
+
+ item = itemService.find(context, uuid);
+
+ if (Objects.isNull(item)) {
+ throw new HttpResponseException(HttpStatus.SC_NOT_FOUND,
+ format("Item with uuid %s not found", uuid));
+ }
+
+ } else {
+ String handle = handleService.resolveUrlToHandle(context, url);
+
+ if (Objects.isNull(handle)) {
+ throw new HttpResponseException(HttpStatus.SC_NOT_FOUND,
+ format("Handle not found for %s", url));
+ }
+
+ DSpaceObject object = handleService.resolveToObject(context, handle);
+
+ if (Objects.isNull(object)) {
+ throw new HttpResponseException(HttpStatus.SC_NOT_FOUND,
+ format("Item with handle %s not found", handle));
+ }
+
+ if (object.getType() == Constants.ITEM) {
+ item = (Item) object;
+ } else {
+ throw new HttpResponseException(HttpStatus.SC_UNPROCESSABLE_ENTITY,
+ format("Handle %s does not resolve to an item", handle));
+ }
+ }
+
+ return item;
+ }
+
+}
\ No newline at end of file
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/processor/LDNProcessor.java b/dspace-api/src/main/java/org/dspace/app/ldn/processor/LDNProcessor.java
new file mode 100644
index 000000000000..279ec5cedc4b
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/processor/LDNProcessor.java
@@ -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.app.ldn.processor;
+
+import org.dspace.app.ldn.model.Notification;
+import org.dspace.core.Context;
+
+/**
+ * Processor interface to allow for custom implementations of process.
+ */
+public interface LDNProcessor {
+
+ /**
+ * Process received notification.
+ *
+ * @param notification received notification
+ * @throws Exception something went wrong processing the notification
+ */
+ public void process(Context context, Notification notification) throws Exception;
+}
\ No newline at end of file
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/service/LDNMessageService.java b/dspace-api/src/main/java/org/dspace/app/ldn/service/LDNMessageService.java
new file mode 100644
index 000000000000..c7d3f588881f
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/service/LDNMessageService.java
@@ -0,0 +1,148 @@
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in the LICENSE and NOTICE files at the root of the source
+ * tree and available online at
+ *
+ * http://www.dspace.org/license/
+ */
+package org.dspace.app.ldn.service;
+
+import java.sql.SQLException;
+import java.util.List;
+
+import org.dspace.app.ldn.LDNMessageEntity;
+import org.dspace.app.ldn.NotifyServiceEntity;
+import org.dspace.app.ldn.model.Notification;
+import org.dspace.app.ldn.model.NotifyRequestStatus;
+import org.dspace.app.ldn.model.Service;
+import org.dspace.content.Item;
+import org.dspace.core.Context;
+
+/**
+ * Service interface class for the {@link LDNMessageEntity} object.
+ *
+ * @author Mohamed Eskander (mohamed.eskander at 4science dot it)
+ */
+public interface LDNMessageService {
+
+ /**
+ * find the ldn message by id
+ *
+ * @param context the context
+ * @param id the uri
+ * @return the ldn message by id
+ * @throws SQLException If something goes wrong in the database
+ */
+ public LDNMessageEntity find(Context context, String id) throws SQLException;
+
+ /**
+ * find all ldn messages
+ *
+ * @param context the context
+ * @return all ldn messages by id
+ * @throws SQLException If something goes wrong in the database
+ */
+ public List findAll(Context context) throws SQLException;
+
+ /**
+ * Creates a new LDNMessage
+ *
+ * @param context The DSpace context
+ * @param id the uri
+ * @return the created LDN Message
+ * @throws SQLException If something goes wrong in the database
+ */
+ public LDNMessageEntity create(Context context, String id) throws SQLException;
+
+ /**
+ * Creates a new LDNMessage
+ *
+ * @param context The DSpace context
+ * @param notification the requested notification
+ * @param sourceIp the source ip
+ * @return the created LDN Message
+ * @throws SQLException If something goes wrong in the database
+ */
+ public LDNMessageEntity create(Context context, Notification notification, String sourceIp) throws SQLException;
+
+ /**
+ * Update the provided LDNMessage
+ *
+ * @param context The DSpace context
+ * @param ldnMessage the LDNMessage
+ * @throws SQLException If something goes wrong in the database
+ */
+ public void update(Context context, LDNMessageEntity ldnMessage) throws SQLException;
+
+ /**
+ * Find the oldest queued LDNMessages that still can be elaborated
+ *
+ * @return list of LDN messages
+ * @param context The DSpace context
+ * @throws SQLException If something goes wrong in the database
+ */
+ public List findOldestMessagesToProcess(Context context) throws SQLException;
+
+ /**
+ * Find all messages in the queue with the Processing status but timed-out
+ *
+ * @return all the LDN Messages to be fixed on their queue_ attributes
+ * @param context The DSpace context
+ * @throws SQLException If something goes wrong in the database
+ */
+ public List findProcessingTimedoutMessages(Context context) throws SQLException;
+
+ /**
+ * Find all messages in the queue with the Processing status but timed-out and modify their queue_status
+ * considering the queue_attempts
+ *
+ * @return number of messages fixed
+ * @param context The DSpace context
+ */
+ public int checkQueueMessageTimeout(Context context);
+
+ /**
+ * Elaborates the oldest enqueued message
+ *
+ * @return number of messages fixed
+ * @param context The DSpace context
+ */
+ public int extractAndProcessMessageFromQueue(Context context) throws SQLException;
+
+ /**
+ * find the related notify service entity
+ *
+ * @param context the context
+ * @param service the service
+ * @return the NotifyServiceEntity
+ * @throws SQLException if something goes wrong
+ */
+ public NotifyServiceEntity findNotifyService(Context context, Service service) throws SQLException;
+
+ /**
+ * find the ldn messages of Requests by item uuid
+ *
+ * @param context the context
+ * @param item the item
+ * @return the item requests object
+ * @throws SQLException If something goes wrong in the database
+ */
+ public NotifyRequestStatus findRequestsByItem(Context context, Item item) throws SQLException;
+
+ /**
+ * delete the provided ldn message
+ *
+ * @param context the context
+ * @param ldnMessage the ldn message
+ * @throws SQLException if something goes wrong
+ */
+ public void delete(Context context, LDNMessageEntity ldnMessage) throws SQLException;
+
+ /**
+ * check if IP number is included in the configured ip-range on the Notify Service
+ *
+ * @param origin the Notify Service entity
+ * @param sourceIp the ip to evaluate
+ */
+ public boolean isValidIp(NotifyServiceEntity origin, String sourceIp);
+}
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/service/NotifyPatternToTriggerService.java b/dspace-api/src/main/java/org/dspace/app/ldn/service/NotifyPatternToTriggerService.java
new file mode 100644
index 000000000000..c2c3de7e06d5
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/service/NotifyPatternToTriggerService.java
@@ -0,0 +1,84 @@
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in the LICENSE and NOTICE files at the root of the source
+ * tree and available online at
+ *
+ * http://www.dspace.org/license/
+ */
+package org.dspace.app.ldn.service;
+
+import java.sql.SQLException;
+import java.util.List;
+
+import org.dspace.app.ldn.NotifyPatternToTrigger;
+import org.dspace.content.Item;
+import org.dspace.core.Context;
+
+/**
+ * Service interface class for the {@link NotifyPatternToTrigger} object.
+ *
+ * @author Mohamed Eskander (mohamed.eskander at 4science.com)
+ */
+public interface NotifyPatternToTriggerService {
+
+ /**
+ * find all notify patterns to be triggered
+ *
+ * @param context the context
+ * @return all notify patterns to be trigger
+ * @throws SQLException if database error
+ */
+ public List findAll(Context context) throws SQLException;
+
+ /**
+ * find list of Notify Patterns To be Triggered by item
+ *
+ * @param context the context
+ * @param item the item of NotifyPatternToTrigger
+ * @return the matched NotifyPatternToTrigger list by item
+ * @throws SQLException if database error
+ */
+ public List findByItem(Context context, Item item)
+ throws SQLException;
+
+ /**
+ * find list of Notify Patterns To be Triggered by item and pattern
+ *
+ * @param context the context
+ * @param item the item of NotifyPatternToTrigger
+ * @param pattern the pattern of NotifyPatternToTrigger
+ *
+ * @return the matched NotifyPatternToTrigger list by item and pattern
+ * @throws SQLException if database error
+ */
+ public List findByItemAndPattern(Context context, Item item, String pattern)
+ throws SQLException;
+
+ /**
+ * create new notifyPatternToTrigger
+ *
+ * @param context the context
+ * @return the created NotifyPatternToTrigger
+ * @throws SQLException if database error
+ */
+ public NotifyPatternToTrigger create(Context context) throws SQLException;
+
+ /**
+ * update the provided notifyPatternToTrigger
+ *
+ * @param context the context
+ * @param notifyPatternToTrigger the notifyPatternToTrigger
+ * @throws SQLException if database error
+ */
+ public void update(Context context, NotifyPatternToTrigger notifyPatternToTrigger) throws SQLException;
+
+ /**
+ * delete the provided notifyPatternToTrigger
+ *
+ * @param context the context
+ * @param notifyPatternToTrigger the notifyPatternToTrigger
+ * @throws SQLException if database error
+ */
+ public void delete(Context context, NotifyPatternToTrigger notifyPatternToTrigger) throws SQLException;
+
+}
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/service/NotifyService.java b/dspace-api/src/main/java/org/dspace/app/ldn/service/NotifyService.java
new file mode 100644
index 000000000000..6ff4c34780c5
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/service/NotifyService.java
@@ -0,0 +1,91 @@
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in the LICENSE and NOTICE files at the root of the source
+ * tree and available online at
+ *
+ * http://www.dspace.org/license/
+ */
+package org.dspace.app.ldn.service;
+
+import java.sql.SQLException;
+import java.util.List;
+
+import org.dspace.app.ldn.NotifyServiceEntity;
+import org.dspace.core.Context;
+
+/**
+ * Service interface class for the {@link NotifyServiceEntity} object.
+ *
+ * @author Mohamed Eskander (mohamed.eskander at 4science.com)
+ */
+public interface NotifyService {
+
+ /**
+ * find all notify service entities
+ *
+ * @param context the context
+ * @return all notify service entities
+ * @throws SQLException if database error
+ */
+ public List findAll(Context context) throws SQLException;
+
+ /**
+ * find one NotifyServiceEntity by id
+ *
+ * @param context the context
+ * @param id the id of NotifyServiceEntity
+ * @return the matched NotifyServiceEntity by id
+ * @throws SQLException if database error
+ */
+ public NotifyServiceEntity find(Context context, Integer id) throws SQLException;
+
+ /**
+ * create new notifyServiceEntity
+ *
+ * @param context the context
+ * @return the created NotifyServiceEntity
+ * @throws SQLException if database error
+ */
+ public NotifyServiceEntity create(Context context) throws SQLException;
+
+ /**
+ * update the provided notifyServiceEntity
+ *
+ * @param context the context
+ * @param notifyServiceEntity the notifyServiceEntity
+ * @throws SQLException if database error
+ */
+ public void update(Context context, NotifyServiceEntity notifyServiceEntity) throws SQLException;
+
+ /**
+ * delete the provided notifyServiceEntity
+ *
+ * @param context the context
+ * @param notifyServiceEntity the notifyServiceEntity
+ * @throws SQLException if database error
+ */
+ public void delete(Context context, NotifyServiceEntity notifyServiceEntity) throws SQLException;
+
+ /**
+ * find the NotifyServiceEntity matched with the provided ldnUrl
+ *
+ * @param context the context
+ * @param ldnUrl the ldnUrl
+ * @return the NotifyServiceEntity matched the provided ldnUrl
+ * @throws SQLException if database error
+ */
+ public NotifyServiceEntity findByLdnUrl(Context context, String ldnUrl) throws SQLException;
+
+ /**
+ * find all NotifyServiceEntity matched the provided inbound pattern
+ * from its related notifyServiceInboundPatterns
+ * also with 'automatic' equals to false
+ *
+ * @param context the context
+ * @param pattern the ldnUrl
+ * @return all NotifyServiceEntity matched the provided pattern
+ * @throws SQLException if database error
+ */
+ public List findManualServicesByInboundPattern(Context context, String pattern)
+ throws SQLException;
+}
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/service/NotifyServiceInboundPatternService.java b/dspace-api/src/main/java/org/dspace/app/ldn/service/NotifyServiceInboundPatternService.java
new file mode 100644
index 000000000000..8cd92d45dd30
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/service/NotifyServiceInboundPatternService.java
@@ -0,0 +1,76 @@
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in the LICENSE and NOTICE files at the root of the source
+ * tree and available online at
+ *
+ * http://www.dspace.org/license/
+ */
+package org.dspace.app.ldn.service;
+
+import java.sql.SQLException;
+import java.util.List;
+
+import org.dspace.app.ldn.NotifyServiceEntity;
+import org.dspace.app.ldn.NotifyServiceInboundPattern;
+import org.dspace.core.Context;
+
+/**
+ * Service interface class for the {@link NotifyServiceInboundPattern} object.
+ *
+ * @author Mohamed Eskander (mohamed.eskander at 4science.com)
+ */
+public interface NotifyServiceInboundPatternService {
+
+ /**
+ * find all notifyServiceInboundPatterns matched with
+ * the provided notifyServiceEntity and pattern
+ *
+ * @param context the context
+ * @param notifyServiceEntity the notifyServiceEntity
+ * @param pattern the pattern
+ * @return all notifyServiceInboundPatterns matched with
+ * the provided notifyServiceEntity and pattern
+ * @throws SQLException if database error
+ */
+ public NotifyServiceInboundPattern findByServiceAndPattern(Context context,
+ NotifyServiceEntity notifyServiceEntity,
+ String pattern) throws SQLException;
+
+ /**
+ * find all automatic notifyServiceInboundPatterns
+ *
+ * @param context the context
+ * @return all automatic notifyServiceInboundPatterns
+ * @throws SQLException if database error
+ */
+ public List findAutomaticPatterns(Context context) throws SQLException;
+
+ /**
+ * create new notifyServiceInboundPattern
+ *
+ * @param context the context
+ * @param notifyServiceEntity the notifyServiceEntity
+ * @return the created notifyServiceInboundPattern
+ * @throws SQLException if database error
+ */
+ public NotifyServiceInboundPattern create(Context context, NotifyServiceEntity notifyServiceEntity)
+ throws SQLException;
+
+ /**
+ * update the provided notifyServiceInboundPattern
+ *
+ * @param context the context
+ * @param inboundPattern the notifyServiceInboundPattern
+ * @throws SQLException if database error
+ */
+ public void update(Context context, NotifyServiceInboundPattern inboundPattern) throws SQLException;
+
+ /**
+ * delete the provided notifyServiceInboundPattern
+ *
+ * @param context the context
+ * @param inboundPattern the notifyServiceInboundPattern
+ * @throws SQLException if database error
+ */
+ public void delete(Context context, NotifyServiceInboundPattern inboundPattern) throws SQLException;
+}
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/service/impl/LDNMessageServiceImpl.java b/dspace-api/src/main/java/org/dspace/app/ldn/service/impl/LDNMessageServiceImpl.java
new file mode 100644
index 000000000000..b69ecec6a142
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/service/impl/LDNMessageServiceImpl.java
@@ -0,0 +1,360 @@
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in the LICENSE and NOTICE files at the root of the source
+ * tree and available online at
+ *
+ * http://www.dspace.org/license/
+ */
+package org.dspace.app.ldn.service.impl;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.Set;
+import java.util.UUID;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.commons.lang.time.DateUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.logging.log4j.Logger;
+import org.dspace.app.ldn.LDNMessageEntity;
+import org.dspace.app.ldn.LDNRouter;
+import org.dspace.app.ldn.NotifyServiceEntity;
+import org.dspace.app.ldn.dao.LDNMessageDao;
+import org.dspace.app.ldn.dao.NotifyServiceDao;
+import org.dspace.app.ldn.model.Notification;
+import org.dspace.app.ldn.model.NotifyRequestStatus;
+import org.dspace.app.ldn.model.NotifyRequestStatusEnum;
+import org.dspace.app.ldn.model.RequestStatus;
+import org.dspace.app.ldn.model.Service;
+import org.dspace.app.ldn.processor.LDNProcessor;
+import org.dspace.app.ldn.service.LDNMessageService;
+import org.dspace.app.ldn.utility.LDNUtils;
+import org.dspace.content.DSpaceObject;
+import org.dspace.content.Item;
+import org.dspace.content.service.ItemService;
+import org.dspace.core.Context;
+import org.dspace.handle.service.HandleService;
+import org.dspace.services.ConfigurationService;
+import org.springframework.beans.factory.annotation.Autowired;
+
+
+/**
+ * Implementation of {@link LDNMessageService}
+ *
+ * @author Mohamed Eskander (mohamed.eskander at 4science dot it)
+ */
+public class LDNMessageServiceImpl implements LDNMessageService {
+
+ @Autowired(required = true)
+ private LDNMessageDao ldnMessageDao;
+ @Autowired(required = true)
+ private NotifyServiceDao notifyServiceDao;
+ @Autowired(required = true)
+ private ConfigurationService configurationService;
+ @Autowired(required = true)
+ private HandleService handleService;
+ @Autowired(required = true)
+ private ItemService itemService;
+ @Autowired(required = true)
+ private LDNRouter ldnRouter;
+
+ private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(LDNMessageServiceImpl.class);
+
+ protected LDNMessageServiceImpl() {
+
+ }
+
+ @Override
+ public LDNMessageEntity find(Context context, String id) throws SQLException {
+ return ldnMessageDao.findByID(context, LDNMessageEntity.class, id);
+ }
+
+ @Override
+ public List findAll(Context context) throws SQLException {
+ return ldnMessageDao.findAll(context, LDNMessageEntity.class);
+ }
+
+ @Override
+ public LDNMessageEntity create(Context context, String id) throws SQLException {
+ LDNMessageEntity result = ldnMessageDao.findByID(context, LDNMessageEntity.class, id);
+ if (result != null) {
+ throw new SQLException("Duplicate LDN Message ID [" + id + "] detected. This message is rejected.");
+ }
+ return ldnMessageDao.create(context, new LDNMessageEntity(id));
+ }
+
+ @Override
+ public LDNMessageEntity create(Context context, Notification notification, String sourceIp) throws SQLException {
+ LDNMessageEntity ldnMessage = create(context, notification.getId());
+ ldnMessage.setObject(findDspaceObjectByUrl(context, notification.getObject().getId()));
+ if (null != notification.getContext()) {
+ ldnMessage.setContext(findDspaceObjectByUrl(context, notification.getContext().getId()));
+ }
+ ldnMessage.setOrigin(findNotifyService(context, notification.getOrigin()));
+ ldnMessage.setInReplyTo(find(context, notification.getInReplyTo()));
+ ObjectMapper mapper = new ObjectMapper();
+ String message = null;
+ try {
+ message = mapper.writeValueAsString(notification);
+ ldnMessage.setMessage(message);
+ } catch (JsonProcessingException e) {
+ log.error("Notification json can't be correctly processed " +
+ "and stored inside the LDN Message Entity" + ldnMessage);
+ log.error(e);
+ }
+ ldnMessage.setType(StringUtils.joinWith(",", notification.getType()));
+ Set notificationType = notification.getType();
+ if (notificationType == null) {
+ log.error("Notification has no notificationType attribute! " + notification);
+ return null;
+ }
+ ArrayList notificationTypeArrayList = new ArrayList(notificationType);
+ // sorting the list
+ Collections.sort(notificationTypeArrayList);
+ ldnMessage.setActivityStreamType(notificationTypeArrayList.get(0));
+ if (notificationTypeArrayList.size() > 1) {
+ ldnMessage.setCoarNotifyType(notificationTypeArrayList.get(1));
+ }
+ ldnMessage.setQueueStatus(LDNMessageEntity.QUEUE_STATUS_QUEUED);
+ ldnMessage.setSourceIp(sourceIp);
+ if (ldnMessage.getOrigin() == null && !"Offer".equalsIgnoreCase(ldnMessage.getActivityStreamType())) {
+ ldnMessage.setQueueStatus(LDNMessageEntity.QUEUE_STATUS_UNTRUSTED);
+ } else {
+
+ boolean ipCheckRangeEnabled = configurationService.getBooleanProperty("ldn.ip-range.enabled", true);
+ if (ipCheckRangeEnabled && !isValidIp(ldnMessage.getOrigin(), sourceIp)) {
+ ldnMessage.setQueueStatus(LDNMessageEntity.QUEUE_STATUS_UNTRUSTED_IP);
+ }
+ }
+ ldnMessage.setQueueTimeout(new Date());
+
+ update(context, ldnMessage);
+ return ldnMessage;
+ }
+
+ @Override
+ public boolean isValidIp(NotifyServiceEntity origin, String sourceIp) {
+
+ String lowerIp = origin.getLowerIp();
+ String upperIp = origin.getUpperIp();
+
+ try {
+ InetAddress ip = InetAddress.getByName(sourceIp);
+ InetAddress lowerBoundAddress = InetAddress.getByName(lowerIp);
+ InetAddress upperBoundAddress = InetAddress.getByName(upperIp);
+
+ long ipLong = ipToLong(ip);
+ long lowerBoundLong = ipToLong(lowerBoundAddress);
+ long upperBoundLong = ipToLong(upperBoundAddress);
+
+ return ipLong >= lowerBoundLong && ipLong <= upperBoundLong;
+ } catch (UnknownHostException e) {
+ return false;
+ }
+ }
+
+ private long ipToLong(InetAddress ip) {
+ byte[] octets = ip.getAddress();
+ long result = 0;
+ for (byte octet : octets) {
+ result <<= 8;
+ result |= octet & 0xff;
+ }
+ return result;
+ }
+
+ @Override
+ public void update(Context context, LDNMessageEntity ldnMessage) throws SQLException {
+ if (ldnMessage.getOrigin() != null &&
+ LDNMessageEntity.QUEUE_STATUS_UNTRUSTED.compareTo(ldnMessage.getQueueStatus()) == 0) {
+ ldnMessage.setQueueStatus(LDNMessageEntity.QUEUE_STATUS_QUEUED);
+ }
+ ldnMessageDao.save(context, ldnMessage);
+ }
+
+ private DSpaceObject findDspaceObjectByUrl(Context context, String url) throws SQLException {
+ String dspaceUrl = configurationService.getProperty("dspace.ui.url") + "/handle/";
+
+ if (url.startsWith(dspaceUrl)) {
+ return handleService.resolveToObject(context, url.substring(dspaceUrl.length()));
+ }
+
+ String handleResolver = configurationService.getProperty("handle.canonical.prefix", "https://hdl.handle.net/");
+ if (url.startsWith(handleResolver)) {
+ return handleService.resolveToObject(context, url.substring(handleResolver.length()));
+ }
+
+ dspaceUrl = configurationService.getProperty("dspace.ui.url") + "/items/";
+ if (url.startsWith(dspaceUrl)) {
+ return itemService.find(context, UUID.fromString(url.substring(dspaceUrl.length())));
+ }
+
+ return null;
+ }
+
+ public NotifyServiceEntity findNotifyService(Context context, Service service) throws SQLException {
+ return notifyServiceDao.findByLdnUrl(context, service.getInbox());
+ }
+
+ @Override
+ public List findOldestMessagesToProcess(Context context) throws SQLException {
+ List result = null;
+ int max_attempts = configurationService.getIntProperty("ldn.processor.max.attempts");
+ result = ldnMessageDao.findOldestMessageToProcess(context, max_attempts);
+ return result;
+ }
+
+ @Override
+ public List findProcessingTimedoutMessages(Context context) throws SQLException {
+ List result = null;
+ int max_attempts = configurationService.getIntProperty("ldn.processor.max.attempts");
+ result = ldnMessageDao.findProcessingTimedoutMessages(context, max_attempts);
+ return result;
+ }
+
+ @Override
+ public int extractAndProcessMessageFromQueue(Context context) throws SQLException {
+ int result = 0;
+ int timeoutInMinutes = configurationService.getIntProperty("ldn.processor.queue.msg.timeout");
+ if (timeoutInMinutes == 0) {
+ timeoutInMinutes = 60;
+ }
+ List msgs = null;
+ try {
+ msgs = findOldestMessagesToProcess(context);
+ if (msgs != null && msgs.size() > 0) {
+ LDNMessageEntity msg = null;
+ LDNProcessor processor = null;
+ for (int i = 0; processor == null && i < msgs.size() && msgs.get(i) != null; i++) {
+ processor = ldnRouter.route(msgs.get(i));
+ msg = msgs.get(i);
+ if (processor == null) {
+ log.info(
+ "No processor found for LDN message " + msgs.get(i));
+ msg.setQueueStatus(LDNMessageEntity.QUEUE_STATUS_UNMAPPED_ACTION);
+ msg.setQueueAttempts(msg.getQueueAttempts() + 1);
+ update(context, msg);
+ }
+ }
+ if (processor != null) {
+ try {
+ msg.setQueueLastStartTime(new Date());
+ msg.setQueueStatus(LDNMessageEntity.QUEUE_STATUS_PROCESSING);
+ msg.setQueueTimeout(DateUtils.addMinutes(new Date(), timeoutInMinutes));
+ update(context, msg);
+ ObjectMapper mapper = new ObjectMapper();
+ Notification notification = mapper.readValue(msg.getMessage(), Notification.class);
+ processor.process(context, notification);
+ msg.setQueueStatus(LDNMessageEntity.QUEUE_STATUS_PROCESSED);
+ result = 1;
+ } catch (Exception e) {
+ result = -1;
+ log.error(e);
+ } finally {
+ msg.setQueueAttempts(msg.getQueueAttempts() + 1);
+ update(context, msg);
+ }
+ } else {
+ log.info("Found x" + msgs.size() + " LDN messages but none processor found.");
+ }
+ }
+ } catch (SQLException e) {
+ result = -1;
+ log.error(e);
+ }
+ return result;
+ }
+
+ @Override
+ public int checkQueueMessageTimeout(Context context) {
+ int result = 0;
+ int timeoutInMinutes = configurationService.getIntProperty("ldn.processor.queue.msg.timeout");
+ if (timeoutInMinutes == 0) {
+ timeoutInMinutes = 60;
+ }
+ int maxAttempts = configurationService.getIntProperty("ldn.processor.max.attempts");
+ if (maxAttempts == 0) {
+ maxAttempts = 5;
+ }
+ log.debug("Using parameters: [timeoutInMinutes]=" + timeoutInMinutes + ",[maxAttempts]=" + maxAttempts);
+ /*
+ * put failed on processing messages with timed-out timeout and
+ * attempts >= configured_max_attempts put queue on processing messages with
+ * timed-out timeout and attempts < configured_max_attempts
+ */
+ List msgsToCheck = null;
+ try {
+ msgsToCheck = findProcessingTimedoutMessages(context);
+ } catch (SQLException e) {
+ result = -1;
+ log.error("An error occured on searching for timedout LDN messages!", e);
+ return result;
+ }
+ if (msgsToCheck == null || msgsToCheck.isEmpty()) {
+ return result;
+ }
+ for (int i = 0; i < msgsToCheck.size() && msgsToCheck.get(i) != null; i++) {
+ LDNMessageEntity msg = msgsToCheck.get(i);
+ try {
+ if (msg.getQueueAttempts() >= maxAttempts) {
+ msg.setQueueStatus(LDNMessageEntity.QUEUE_STATUS_FAILED);
+ } else {
+ msg.setQueueStatus(LDNMessageEntity.QUEUE_STATUS_QUEUED);
+ }
+ update(context, msg);
+ result++;
+ } catch (SQLException e) {
+ log.error("Can't update LDN message " + msg);
+ log.error(e);
+ }
+ }
+ return result;
+ }
+
+ @Override
+ public NotifyRequestStatus findRequestsByItem(Context context, Item item) throws SQLException {
+ NotifyRequestStatus result = new NotifyRequestStatus();
+ result.setItemUuid(item.getID());
+ List msgs = ldnMessageDao.findAllMessagesByItem(
+ context, item, "Offer");
+ if (msgs != null && !msgs.isEmpty()) {
+ for (LDNMessageEntity msg : msgs) {
+ RequestStatus offer = new RequestStatus();
+ offer.setServiceName(msg.getOrigin() == null ? "Unknown Service" : msg.getOrigin().getName());
+ offer.setServiceUrl(msg.getOrigin() == null ? "" : msg.getOrigin().getUrl());
+ offer.setOfferType(LDNUtils.getNotifyType(msg.getCoarNotifyType()));
+ List acks = ldnMessageDao.findAllRelatedMessagesByItem(
+ context, msg, item, "Accept", "TentativeReject", "TentativeAccept", "Announce");
+ if (acks == null || acks.isEmpty()) {
+ offer.setStatus(NotifyRequestStatusEnum.REQUESTED);
+ } else if (acks.stream()
+ .filter(c -> (c.getActivityStreamType().equalsIgnoreCase("TentativeAccept") ||
+ c.getActivityStreamType().equalsIgnoreCase("Accept")))
+ .findAny().isPresent()) {
+ offer.setStatus(NotifyRequestStatusEnum.ACCEPTED);
+ } else if (acks.stream()
+ .filter(c -> c.getActivityStreamType().equalsIgnoreCase("TentativeReject"))
+ .findAny().isPresent()) {
+ offer.setStatus(NotifyRequestStatusEnum.REJECTED);
+ }
+ if (acks.stream().filter(
+ c -> c.getActivityStreamType().equalsIgnoreCase("Announce"))
+ .findAny().isEmpty()) {
+ result.addRequestStatus(offer);
+ }
+ }
+ }
+ return result;
+ }
+
+ public void delete(Context context, LDNMessageEntity ldnMessage) throws SQLException {
+ ldnMessageDao.delete(context, ldnMessage);
+ }
+
+}
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/service/impl/NotifyPatternToTriggerImpl.java b/dspace-api/src/main/java/org/dspace/app/ldn/service/impl/NotifyPatternToTriggerImpl.java
new file mode 100644
index 000000000000..89ec4abe58d9
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/service/impl/NotifyPatternToTriggerImpl.java
@@ -0,0 +1,62 @@
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in the LICENSE and NOTICE files at the root of the source
+ * tree and available online at
+ *
+ * http://www.dspace.org/license/
+ */
+package org.dspace.app.ldn.service.impl;
+
+import java.sql.SQLException;
+import java.util.List;
+
+import org.dspace.app.ldn.NotifyPatternToTrigger;
+import org.dspace.app.ldn.dao.NotifyPatternToTriggerDao;
+import org.dspace.app.ldn.service.NotifyPatternToTriggerService;
+import org.dspace.content.Item;
+import org.dspace.core.Context;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * Implementation of {@link NotifyPatternToTriggerService}.
+ *
+ * @author Mohamed Eskander (mohamed.eskander at 4science.com)
+ */
+public class NotifyPatternToTriggerImpl implements NotifyPatternToTriggerService {
+
+ @Autowired(required = true)
+ private NotifyPatternToTriggerDao notifyPatternToTriggerDao;
+
+ @Override
+ public List findAll(Context context) throws SQLException {
+ return notifyPatternToTriggerDao.findAll(context, NotifyPatternToTrigger.class);
+ }
+
+ @Override
+ public List findByItem(Context context, Item item) throws SQLException {
+ return notifyPatternToTriggerDao.findByItem(context, item);
+ }
+
+ @Override
+ public List findByItemAndPattern(Context context, Item item, String pattern)
+ throws SQLException {
+ return notifyPatternToTriggerDao.findByItemAndPattern(context, item, pattern);
+ }
+
+ @Override
+ public NotifyPatternToTrigger create(Context context) throws SQLException {
+ NotifyPatternToTrigger notifyPatternToTrigger = new NotifyPatternToTrigger();
+ return notifyPatternToTriggerDao.create(context, notifyPatternToTrigger);
+ }
+
+ @Override
+ public void update(Context context, NotifyPatternToTrigger notifyPatternToTrigger) throws SQLException {
+ notifyPatternToTriggerDao.save(context, notifyPatternToTrigger);
+ }
+
+ @Override
+ public void delete(Context context, NotifyPatternToTrigger notifyPatternToTrigger) throws SQLException {
+ notifyPatternToTriggerDao.delete(context, notifyPatternToTrigger);
+ }
+
+}
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/service/impl/NotifyServiceImpl.java b/dspace-api/src/main/java/org/dspace/app/ldn/service/impl/NotifyServiceImpl.java
new file mode 100644
index 000000000000..d2289fd77a1c
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/service/impl/NotifyServiceImpl.java
@@ -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.app.ldn.service.impl;
+
+import java.sql.SQLException;
+import java.util.List;
+
+import org.dspace.app.ldn.NotifyServiceEntity;
+import org.dspace.app.ldn.dao.NotifyServiceDao;
+import org.dspace.app.ldn.service.NotifyService;
+import org.dspace.core.Context;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * Implementation of {@link NotifyService}.
+ *
+ * @author Mohamed Eskander (mohamed.eskander at 4science.com)
+ */
+public class NotifyServiceImpl implements NotifyService {
+
+ @Autowired(required = true)
+ private NotifyServiceDao notifyServiceDao;
+
+ @Override
+ public List findAll(Context context) throws SQLException {
+ return notifyServiceDao.findAll(context, NotifyServiceEntity.class);
+ }
+
+ @Override
+ public NotifyServiceEntity find(Context context, Integer id) throws SQLException {
+ return notifyServiceDao.findByID(context, NotifyServiceEntity.class, id);
+ }
+
+ @Override
+ public NotifyServiceEntity create(Context context) throws SQLException {
+ NotifyServiceEntity notifyServiceEntity = new NotifyServiceEntity();
+ return notifyServiceDao.create(context, notifyServiceEntity);
+ }
+
+ @Override
+ public void update(Context context, NotifyServiceEntity notifyServiceEntity) throws SQLException {
+ notifyServiceDao.save(context, notifyServiceEntity);
+ }
+
+ @Override
+ public void delete(Context context, NotifyServiceEntity notifyServiceEntity) throws SQLException {
+ notifyServiceDao.delete(context, notifyServiceEntity);
+ }
+
+ @Override
+ public NotifyServiceEntity findByLdnUrl(Context context, String ldnUrl) throws SQLException {
+ return notifyServiceDao.findByLdnUrl(context, ldnUrl);
+ }
+
+ @Override
+ public List findManualServicesByInboundPattern(Context context, String pattern)
+ throws SQLException {
+ return notifyServiceDao.findManualServicesByInboundPattern(context, pattern);
+ }
+
+}
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/service/impl/NotifyServiceInboundPatternServiceImpl.java b/dspace-api/src/main/java/org/dspace/app/ldn/service/impl/NotifyServiceInboundPatternServiceImpl.java
new file mode 100644
index 000000000000..c699d9fd0376
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/service/impl/NotifyServiceInboundPatternServiceImpl.java
@@ -0,0 +1,59 @@
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in the LICENSE and NOTICE files at the root of the source
+ * tree and available online at
+ *
+ * http://www.dspace.org/license/
+ */
+package org.dspace.app.ldn.service.impl;
+
+import java.sql.SQLException;
+import java.util.List;
+
+import org.dspace.app.ldn.NotifyServiceEntity;
+import org.dspace.app.ldn.NotifyServiceInboundPattern;
+import org.dspace.app.ldn.dao.NotifyServiceInboundPatternDao;
+import org.dspace.app.ldn.service.NotifyServiceInboundPatternService;
+import org.dspace.core.Context;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * Implementation Service class for the {@link NotifyServiceInboundPatternService}.
+ *
+ * @author Mohamed Eskander (mohamed.eskander at 4science.com)
+ */
+public class NotifyServiceInboundPatternServiceImpl implements NotifyServiceInboundPatternService {
+
+ @Autowired
+ private NotifyServiceInboundPatternDao inboundPatternDao;
+
+ @Override
+ public NotifyServiceInboundPattern findByServiceAndPattern(Context context,
+ NotifyServiceEntity notifyServiceEntity,
+ String pattern) throws SQLException {
+ return inboundPatternDao.findByServiceAndPattern(context, notifyServiceEntity, pattern);
+ }
+
+ @Override
+ public List findAutomaticPatterns(Context context) throws SQLException {
+ return inboundPatternDao.findAutomaticPatterns(context);
+ }
+
+ @Override
+ public NotifyServiceInboundPattern create(Context context, NotifyServiceEntity notifyServiceEntity)
+ throws SQLException {
+ NotifyServiceInboundPattern inboundPattern = new NotifyServiceInboundPattern();
+ inboundPattern.setNotifyService(notifyServiceEntity);
+ return inboundPatternDao.create(context, inboundPattern);
+ }
+
+ @Override
+ public void update(Context context, NotifyServiceInboundPattern inboundPattern) throws SQLException {
+ inboundPatternDao.save(context, inboundPattern);
+ }
+
+ @Override
+ public void delete(Context context, NotifyServiceInboundPattern inboundPattern) throws SQLException {
+ inboundPatternDao.delete(context, inboundPattern);
+ }
+}
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/utility/LDNUtils.java b/dspace-api/src/main/java/org/dspace/app/ldn/utility/LDNUtils.java
new file mode 100644
index 000000000000..949da655bc70
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/utility/LDNUtils.java
@@ -0,0 +1,96 @@
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in the LICENSE and NOTICE files at the root of the source
+ * tree and available online at
+ *
+ * http://www.dspace.org/license/
+ */
+package org.dspace.app.ldn.utility;
+
+import static org.apache.commons.lang3.StringUtils.EMPTY;
+
+import java.util.UUID;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Some linked data notification utilities.
+ */
+public class LDNUtils {
+
+ private final static Pattern UUID_REGEX_PATTERN = Pattern.compile(
+ "\\p{XDigit}{8}-\\p{XDigit}{4}-\\p{XDigit}{4}-\\p{XDigit}{4}-\\p{XDigit}{12}");
+
+ private final static String SIMPLE_PROTOCOL_REGEX = "^(http[s]?://www\\.|http[s]?://|www\\.)";
+
+ /**
+ *
+ */
+ private LDNUtils() {
+
+ }
+
+ /**
+ * Whether the URL contains an UUID. Used to determine item id from item URL.
+ *
+ * @param url item URL
+ * @return boolean true if URL has UUID, false otherwise
+ */
+ public static boolean hasUUIDInURL(String url) {
+ Matcher matcher = UUID_REGEX_PATTERN.matcher(url);
+
+ return matcher.find();
+ }
+
+ /**
+ * Extract UUID from URL.
+ *
+ * @param url item URL
+ * @return UUID item id
+ */
+ public static UUID getUUIDFromURL(String url) {
+ Matcher matcher = UUID_REGEX_PATTERN.matcher(url);
+ StringBuilder handle = new StringBuilder();
+ if (matcher.find()) {
+ handle.append(matcher.group(0));
+ }
+ return UUID.fromString(handle.toString());
+ }
+
+ /**
+ * Remove http or https protocol from URL.
+ *
+ * @param url URL
+ * @return String URL without protocol
+ */
+ public static String removedProtocol(String url) {
+ return url.replaceFirst(SIMPLE_PROTOCOL_REGEX, EMPTY);
+ }
+
+ /**
+ * Custom context resolver processing. Currently converting DOI URL to DOI id.
+ *
+ * @param value context ietf:cite-as
+ * @return String ietf:cite-as identifier
+ */
+ public static String processContextResolverId(String value) {
+ String resolverId = value;
+ resolverId = resolverId.replace("https://doi.org/", "doi:");
+
+ return resolverId;
+ }
+
+ /**
+ * Clear the coarNotifyType from the source code.
+ *
+ * @param coarNotifyType coar Notify Type to sanitize
+ * @return String just the notify type
+ */
+ public static String getNotifyType(String coarNotifyType) {
+ String justNotifyType = coarNotifyType;
+ justNotifyType = justNotifyType.substring(justNotifyType.lastIndexOf(":") + 1);
+ justNotifyType = justNotifyType.replace("Action", "");
+ return justNotifyType;
+ }
+
+}
\ No newline at end of file
diff --git a/dspace-api/src/main/java/org/dspace/app/mediafilter/MediaFilterScriptConfiguration.java b/dspace-api/src/main/java/org/dspace/app/mediafilter/MediaFilterScriptConfiguration.java
index 867e684db86b..7465fa6e1279 100644
--- a/dspace-api/src/main/java/org/dspace/app/mediafilter/MediaFilterScriptConfiguration.java
+++ b/dspace-api/src/main/java/org/dspace/app/mediafilter/MediaFilterScriptConfiguration.java
@@ -33,7 +33,8 @@ public Options getOptions() {
options.addOption("v", "verbose", false, "print all extracted text and other details to STDOUT");
options.addOption("q", "quiet", false, "do not print anything except in the event of errors.");
options.addOption("f", "force", false, "force all bitstreams to be processed");
- options.addOption("i", "identifier", true, "ONLY process bitstreams belonging to identifier");
+ options.addOption("i", "identifier", true,
+ "ONLY process bitstreams belonging to the provided handle identifier");
options.addOption("m", "maximum", true, "process no more than maximum items");
options.addOption("h", "help", false, "help");
diff --git a/dspace-api/src/main/java/org/dspace/app/mediafilter/MediaFilterServiceImpl.java b/dspace-api/src/main/java/org/dspace/app/mediafilter/MediaFilterServiceImpl.java
index b50fb22355a3..a6ba9fde88d9 100644
--- a/dspace-api/src/main/java/org/dspace/app/mediafilter/MediaFilterServiceImpl.java
+++ b/dspace-api/src/main/java/org/dspace/app/mediafilter/MediaFilterServiceImpl.java
@@ -132,12 +132,18 @@ public void applyFiltersAllItems(Context context) throws Exception {
@Override
public void applyFiltersCommunity(Context context, Community community)
throws Exception { //only apply filters if community not in skip-list
+ // ensure that the community is attached to the current hibernate session
+ // as we are committing after each item (handles, sub-communties and
+ // collections are lazy attributes)
+ community = context.reloadEntity(community);
if (!inSkipList(community.getHandle())) {
List subcommunities = community.getSubcommunities();
for (Community subcommunity : subcommunities) {
applyFiltersCommunity(context, subcommunity);
}
-
+ // ensure that the community is attached to the current hibernate session
+ // as we are committing after each item
+ community = context.reloadEntity(community);
List collections = community.getCollections();
for (Collection collection : collections) {
applyFiltersCollection(context, collection);
@@ -148,6 +154,9 @@ public void applyFiltersCommunity(Context context, Community community)
@Override
public void applyFiltersCollection(Context context, Collection collection)
throws Exception {
+ // ensure that the collection is attached to the current hibernate session
+ // as we are committing after each item (handles are lazy attributes)
+ collection = context.reloadEntity(collection);
//only apply filters if collection not in skip-list
if (!inSkipList(collection.getHandle())) {
Iterator- itemIterator = itemService.findAllByCollection(context, collection);
@@ -171,6 +180,8 @@ public void applyFiltersItem(Context c, Item item) throws Exception {
}
// clear item objects from context cache and internal cache
c.uncacheEntity(currentItem);
+ // commit after each item to release DB resources
+ c.commit();
currentItem = null;
}
}
diff --git a/dspace-api/src/main/java/org/dspace/app/suggestion/SolrSuggestionStorageServiceImpl.java b/dspace-api/src/main/java/org/dspace/app/suggestion/SolrSuggestionStorageServiceImpl.java
index 6281b6910701..3c2ad71846a6 100644
--- a/dspace-api/src/main/java/org/dspace/app/suggestion/SolrSuggestionStorageServiceImpl.java
+++ b/dspace-api/src/main/java/org/dspace/app/suggestion/SolrSuggestionStorageServiceImpl.java
@@ -56,6 +56,7 @@
*
*/
public class SolrSuggestionStorageServiceImpl implements SolrSuggestionStorageService {
+
private static final Logger log = LogManager.getLogger(SolrSuggestionStorageServiceImpl.class);
protected SolrClient solrSuggestionClient;
diff --git a/dspace-api/src/main/java/org/dspace/app/suggestion/openaire/PublicationLoaderRunnable.java b/dspace-api/src/main/java/org/dspace/app/suggestion/openaire/PublicationLoaderRunnable.java
index a408c448e9f5..76e8213cd7d2 100644
--- a/dspace-api/src/main/java/org/dspace/app/suggestion/openaire/PublicationLoaderRunnable.java
+++ b/dspace-api/src/main/java/org/dspace/app/suggestion/openaire/PublicationLoaderRunnable.java
@@ -12,6 +12,8 @@
import java.util.List;
import org.apache.commons.cli.ParseException;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
import org.dspace.content.Item;
import org.dspace.core.Context;
import org.dspace.discovery.DiscoverQuery;
@@ -23,8 +25,6 @@
import org.dspace.scripts.DSpaceRunnable;
import org.dspace.sort.SortOption;
import org.dspace.utils.DSpace;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
/**
* Runner responsible to import metadata about authors from OpenAIRE to Solr.
@@ -33,13 +33,13 @@
* with this UUID will be used.
* Invocation without any parameter results in massive import, processing all
* authors registered in DSpace.
- *
+ *
* @author Alessandro Martelli (alessandro.martelli at 4science.it)
*/
public class PublicationLoaderRunnable
extends DSpaceRunnable> {
- private static final Logger LOGGER = LoggerFactory.getLogger(PublicationLoaderRunnable.class);
+ private static final Logger LOGGER = LogManager.getLogger();
private PublicationLoader oairePublicationLoader = null;
@@ -63,9 +63,9 @@ public void setup() throws ParseException {
profile = commandLine.getOptionValue("s");
if (profile == null) {
- LOGGER.info("No argument for -s, process all profile");
+ LOGGER.info("No argument for -s, process all profiles");
} else {
- LOGGER.info("Process eperson item with UUID " + profile);
+ LOGGER.info("Process eperson item with UUID {}", profile);
}
}
@@ -87,7 +87,7 @@ public void internalRun() throws Exception {
* the researcher with this UUID will be chosen. If the uuid doesn't match any
* researcher, the method returns an empty array list. If uuid is null, all
* research will be return.
- *
+ *
* @param profileUUID uuid of the researcher. If null, all researcher will be
* returned.
* @return the researcher with specified UUID or all researchers
@@ -96,10 +96,10 @@ public void internalRun() throws Exception {
private Iterator
- getResearchers(String profileUUID) {
SearchService searchService = new DSpace().getSingletonService(SearchService.class);
DiscoverQueryBuilder queryBuilder = SearchUtils.getQueryBuilder();
- List filters = new ArrayList();
+ List filters = new ArrayList<>();
String query = "*:*";
if (profileUUID != null) {
- query = "search.resourceid:" + profileUUID.toString();
+ query = "search.resourceid:" + profileUUID;
}
try {
DiscoverQuery discoverQuery = queryBuilder.buildQuery(context, null,
diff --git a/dspace-api/src/main/java/org/dspace/app/util/AbstractDSpaceWebapp.java b/dspace-api/src/main/java/org/dspace/app/util/AbstractDSpaceWebapp.java
index c0cb5d226c1f..48df3dbc12d5 100644
--- a/dspace-api/src/main/java/org/dspace/app/util/AbstractDSpaceWebapp.java
+++ b/dspace-api/src/main/java/org/dspace/app/util/AbstractDSpaceWebapp.java
@@ -12,13 +12,13 @@
import java.sql.Timestamp;
import java.util.Date;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
import org.dspace.app.util.factory.UtilServiceFactory;
import org.dspace.app.util.service.WebAppService;
import org.dspace.core.Context;
import org.dspace.services.ConfigurationService;
import org.dspace.services.factory.DSpaceServicesFactory;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
/**
* Represent a DSpace application while it is running. This helps us report
@@ -29,11 +29,10 @@
*/
abstract public class AbstractDSpaceWebapp
implements DSpaceWebappMXBean {
- private static final Logger log = LoggerFactory.getLogger(AbstractDSpaceWebapp.class);
+ private static final Logger log = LogManager.getLogger();
protected final WebAppService webAppService = UtilServiceFactory.getInstance().getWebAppService();
-
protected String kind;
protected Date started;
diff --git a/dspace-api/src/main/java/org/dspace/app/util/DCInput.java b/dspace-api/src/main/java/org/dspace/app/util/DCInput.java
index 11f9aadd869b..c5093d3320a8 100644
--- a/dspace-api/src/main/java/org/dspace/app/util/DCInput.java
+++ b/dspace-api/src/main/java/org/dspace/app/util/DCInput.java
@@ -16,10 +16,10 @@
import javax.annotation.Nullable;
import org.apache.commons.lang3.StringUtils;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
import org.dspace.content.MetadataSchemaEnum;
import org.dspace.core.Utils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
/**
* Class representing a line in an input form.
@@ -28,7 +28,7 @@
*/
public class DCInput {
- private static final Logger log = LoggerFactory.getLogger(DCInput.class);
+ private static final Logger log = LogManager.getLogger();
/**
* the DC element name
@@ -183,7 +183,7 @@ public DCInput(Map fieldMap, Map> listMap)
}
//check if the input have a language tag
- language = Boolean.valueOf(fieldMap.get("language"));
+ language = Boolean.parseBoolean(fieldMap.get("language"));
valueLanguageList = new ArrayList<>();
if (language) {
String languageNameTmp = fieldMap.get("value-pairs-name");
@@ -219,7 +219,7 @@ public DCInput(Map fieldMap, Map> listMap)
|| "yes".equalsIgnoreCase(closedVocabularyStr);
// parsing of the element (using the colon as split separator)
- typeBind = new ArrayList();
+ typeBind = new ArrayList<>();
String typeBindDef = fieldMap.get("type-bind");
if (typeBindDef != null && typeBindDef.trim().length() > 0) {
String[] types = typeBindDef.split(",");
@@ -523,7 +523,7 @@ public boolean isClosedVocabulary() {
* @return true when there is no type restriction or typeName is allowed
*/
public boolean isAllowedFor(String typeName) {
- if (typeBind.size() == 0) {
+ if (typeBind.isEmpty()) {
return true;
}
diff --git a/dspace-api/src/main/java/org/dspace/authenticate/AuthenticationServiceImpl.java b/dspace-api/src/main/java/org/dspace/authenticate/AuthenticationServiceImpl.java
index 1d67da37ecb3..a1e096dc4d64 100644
--- a/dspace-api/src/main/java/org/dspace/authenticate/AuthenticationServiceImpl.java
+++ b/dspace-api/src/main/java/org/dspace/authenticate/AuthenticationServiceImpl.java
@@ -15,6 +15,8 @@
import java.util.List;
import javax.servlet.http.HttpServletRequest;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
import org.dspace.authenticate.service.AuthenticationService;
import org.dspace.authorize.AuthorizeException;
import org.dspace.core.Context;
@@ -22,8 +24,6 @@
import org.dspace.eperson.EPerson;
import org.dspace.eperson.Group;
import org.dspace.eperson.service.EPersonService;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
/**
@@ -49,15 +49,14 @@
* specified first (in the configuration) thus getting highest priority.
*
* @author Larry Stone
- * @version $Revision$
* @see AuthenticationMethod
*/
public class AuthenticationServiceImpl implements AuthenticationService {
/**
- * SLF4J logging category
+ * Logging category
*/
- private final Logger log = (Logger) LoggerFactory.getLogger(AuthenticationServiceImpl.class);
+ private final Logger log = LogManager.getLogger();
@Autowired(required = true)
protected EPersonService ePersonService;
@@ -121,6 +120,7 @@ protected int authenticateInternal(Context context,
return bestRet;
}
+ @Override
public void updateLastActiveDate(Context context) {
EPerson me = context.getCurrentUser();
if (me != null) {
diff --git a/dspace-api/src/main/java/org/dspace/authenticate/OidcAuthenticationBean.java b/dspace-api/src/main/java/org/dspace/authenticate/OidcAuthenticationBean.java
index 8a4ac190c816..c628f0dce27d 100644
--- a/dspace-api/src/main/java/org/dspace/authenticate/OidcAuthenticationBean.java
+++ b/dspace-api/src/main/java/org/dspace/authenticate/OidcAuthenticationBean.java
@@ -24,6 +24,8 @@
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
import org.dspace.authenticate.oidc.OidcClient;
import org.dspace.authenticate.oidc.model.OidcTokenResponseDTO;
import org.dspace.core.Context;
@@ -31,8 +33,6 @@
import org.dspace.eperson.Group;
import org.dspace.eperson.service.EPersonService;
import org.dspace.services.ConfigurationService;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
/**
@@ -51,7 +51,7 @@ public class OidcAuthenticationBean implements AuthenticationMethod {
private final static String LOGIN_PAGE_URL_FORMAT = "%s?client_id=%s&response_type=code&scope=%s&redirect_uri=%s";
- private static final Logger LOGGER = LoggerFactory.getLogger(OidcAuthenticationBean.class);
+ private static final Logger LOGGER = LogManager.getLogger();
private static final String OIDC_AUTHENTICATED = "oidc.authenticated";
@@ -174,7 +174,7 @@ public String loginPageURL(Context context, HttpServletRequest request, HttpServ
final Entry entry = iterator.next();
if (isBlank(entry.getValue())) {
- LOGGER.error(" * {} is missing", entry.getKey());
+ LOGGER.error(" * {} is missing", entry::getKey);
}
}
return "";
@@ -183,7 +183,7 @@ public String loginPageURL(Context context, HttpServletRequest request, HttpServ
try {
return format(LOGIN_PAGE_URL_FORMAT, authorizeUrl, clientId, scopes, encode(redirectUri, "UTF-8"));
} catch (UnsupportedEncodingException e) {
- LOGGER.error(e.getMessage(), e);
+ LOGGER.error(e::getMessage, e);
return "";
}
diff --git a/dspace-api/src/main/java/org/dspace/authenticate/OrcidAuthenticationBean.java b/dspace-api/src/main/java/org/dspace/authenticate/OrcidAuthenticationBean.java
index a11bbfc867b4..6804ad9160dd 100644
--- a/dspace-api/src/main/java/org/dspace/authenticate/OrcidAuthenticationBean.java
+++ b/dspace-api/src/main/java/org/dspace/authenticate/OrcidAuthenticationBean.java
@@ -23,6 +23,8 @@
import org.apache.commons.collections4.CollectionUtils;
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.core.Context;
import org.dspace.eperson.EPerson;
@@ -39,8 +41,6 @@
import org.dspace.services.ConfigurationService;
import org.orcid.jaxb.model.v3.release.record.Email;
import org.orcid.jaxb.model.v3.release.record.Person;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
/**
@@ -53,7 +53,7 @@ public class OrcidAuthenticationBean implements AuthenticationMethod {
public static final String ORCID_AUTH_ATTRIBUTE = "orcid-authentication";
- private final static Logger LOGGER = LoggerFactory.getLogger(OrcidAuthenticationBean.class);
+ private final static Logger LOGGER = LogManager.getLogger();
private final static String LOGIN_PAGE_URL_FORMAT = "%s?client_id=%s&response_type=code&scope=%s&redirect_uri=%s";
@@ -282,7 +282,8 @@ private Person getPersonFromOrcid(OrcidTokenResponseDTO token) {
try {
return orcidClient.getPerson(token.getAccessToken(), token.getOrcid());
} catch (Exception ex) {
- LOGGER.error("An error occurs retriving the ORCID record with id " + token.getOrcid(), ex);
+ LOGGER.error("An error occurs retriving the ORCID record with id {}",
+ token.getOrcid(), ex);
return null;
}
}
diff --git a/dspace-api/src/main/java/org/dspace/coarnotify/NotifyConfigurationService.java b/dspace-api/src/main/java/org/dspace/coarnotify/NotifyConfigurationService.java
new file mode 100644
index 000000000000..c9d65186b631
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/coarnotify/NotifyConfigurationService.java
@@ -0,0 +1,34 @@
+/**
+ * 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.coarnotify;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Simple bean to manage different COAR Notify configuration
+ *
+ * @author Mohamed Eskander (mohamed.eskander at 4science.com)
+ */
+public class NotifyConfigurationService {
+
+ /**
+ * Mapping the submission step process identifier with the configuration
+ * (see configuration at coar-notify.xml)
+ */
+ private Map> patterns;
+
+ public Map> getPatterns() {
+ return patterns;
+ }
+
+ public void setPatterns(Map> patterns) {
+ this.patterns = patterns;
+ }
+
+}
diff --git a/dspace-api/src/main/java/org/dspace/coarnotify/NotifyPattern.java b/dspace-api/src/main/java/org/dspace/coarnotify/NotifyPattern.java
new file mode 100644
index 000000000000..d678aa052303
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/coarnotify/NotifyPattern.java
@@ -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/
+ */
+package org.dspace.coarnotify;
+
+/**
+ * A collection of configured patterns to be met when adding COAR Notify services.
+ *
+ * @author Mohamed Eskander (mohamed.eskander at 4science.com)
+ */
+public class NotifyPattern {
+
+ private String pattern;
+ private boolean multipleRequest;
+
+ public NotifyPattern() {
+
+ }
+
+ public NotifyPattern(String pattern, boolean multipleRequest) {
+ this.pattern = pattern;
+ this.multipleRequest = multipleRequest;
+ }
+
+ public String getPattern() {
+ return pattern;
+ }
+
+ public void setPattern(String pattern) {
+ this.pattern = pattern;
+ }
+
+ public boolean isMultipleRequest() {
+ return multipleRequest;
+ }
+
+ public void setMultipleRequest(boolean multipleRequest) {
+ this.multipleRequest = multipleRequest;
+ }
+}
diff --git a/dspace-api/src/main/java/org/dspace/coarnotify/NotifySubmissionConfiguration.java b/dspace-api/src/main/java/org/dspace/coarnotify/NotifySubmissionConfiguration.java
new file mode 100644
index 000000000000..ee23ee346521
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/coarnotify/NotifySubmissionConfiguration.java
@@ -0,0 +1,65 @@
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in the LICENSE and NOTICE files at the root of the source
+ * tree and available online at
+ *
+ * http://www.dspace.org/license/
+ */
+package org.dspace.coarnotify;
+
+import java.util.List;
+
+/**
+ * this class represents the Configuration of Submission COAR Notify
+ *
+ * @author Mohamed Eskander (mohamed.eskander at 4science.com)
+ */
+public class NotifySubmissionConfiguration {
+
+ /**
+ * the map key of configured bean of COARNotifyConfigurationService
+ * in coar-notify.xml
+ */
+ private String id;
+
+ /**
+ * the map values of configured bean of COARNotifyConfigurationService
+ * in coar-notify.xml
+ */
+ private List patterns;
+
+ public NotifySubmissionConfiguration() {
+
+ }
+
+ public NotifySubmissionConfiguration(String id, List patterns) {
+ super();
+ this.id = id;
+ this.patterns = patterns;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ /**
+ * Gets the list of configured COAR Notify Patterns
+ *
+ * @return the list of configured COAR Notify Patterns
+ */
+ public List getPatterns() {
+ return patterns;
+ }
+
+ /**
+ * Sets the list of configured COAR Notify Patterns
+ * @param patterns
+ */
+ public void setPatterns(final List patterns) {
+ this.patterns = patterns;
+ }
+}
diff --git a/dspace-api/src/main/java/org/dspace/coarnotify/SubmissionNotifyServiceImpl.java b/dspace-api/src/main/java/org/dspace/coarnotify/SubmissionNotifyServiceImpl.java
new file mode 100644
index 000000000000..afb771529f4a
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/coarnotify/SubmissionNotifyServiceImpl.java
@@ -0,0 +1,53 @@
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in the LICENSE and NOTICE files at the root of the source
+ * tree and available online at
+ *
+ * http://www.dspace.org/license/
+ */
+package org.dspace.coarnotify;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.dspace.coarnotify.service.SubmissionNotifyService;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * Service implementation of {@link SubmissionNotifyService}
+ *
+ * @author Mohamed Eskander (mohamed.eskander at 4science.com)
+ */
+public class SubmissionNotifyServiceImpl implements SubmissionNotifyService {
+
+ @Autowired(required = true)
+ private NotifyConfigurationService coarNotifyConfigurationService;
+
+ protected SubmissionNotifyServiceImpl() {
+
+ }
+
+ @Override
+ public NotifySubmissionConfiguration findOne(String id) {
+ List patterns =
+ coarNotifyConfigurationService.getPatterns().get(id);
+
+ if (patterns == null) {
+ return null;
+ }
+
+ return new NotifySubmissionConfiguration(id, patterns);
+ }
+
+ @Override
+ public List findAll() {
+ List coarNotifies = new ArrayList<>();
+
+ coarNotifyConfigurationService.getPatterns().forEach((id, patterns) ->
+ coarNotifies.add(new NotifySubmissionConfiguration(id, patterns)
+ ));
+
+ return coarNotifies;
+ }
+
+}
diff --git a/dspace-api/src/main/java/org/dspace/coarnotify/service/SubmissionNotifyService.java b/dspace-api/src/main/java/org/dspace/coarnotify/service/SubmissionNotifyService.java
new file mode 100644
index 000000000000..43f3ea17330b
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/coarnotify/service/SubmissionNotifyService.java
@@ -0,0 +1,39 @@
+/**
+ * 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.coarnotify.service;
+
+import java.util.List;
+
+import org.dspace.coarnotify.NotifySubmissionConfiguration;
+
+/**
+ * Service interface class for the Creative Submission COAR Notify.
+ * The implementation of this class is responsible for all business logic calls for the Creative Submission COAR Notify
+ * and is autowired by spring
+ *
+ * @author Mohamed Eskander (mohamed.eskander at 4science.com)
+ */
+public interface SubmissionNotifyService {
+
+ /**
+ * Find the COARE Notify corresponding to the provided ID
+ * found in the configuration
+ *
+ * @param id - the ID of the COAR Notify to be found
+ * @return the corresponding COAR Notify if found or null when not found
+ */
+ public NotifySubmissionConfiguration findOne(String id);
+
+ /**
+ * Find all configured COAR Notifies
+ *
+ * @return all configured COAR Notifies
+ */
+ public List findAll();
+
+}
diff --git a/dspace-api/src/main/java/org/dspace/content/ItemFilterService.java b/dspace-api/src/main/java/org/dspace/content/ItemFilterService.java
new file mode 100644
index 000000000000..8b664a972605
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/content/ItemFilterService.java
@@ -0,0 +1,33 @@
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in the LICENSE and NOTICE files at the root of the source
+ * tree and available online at
+ *
+ * http://www.dspace.org/license/
+ */
+package org.dspace.content;
+
+import java.util.List;
+
+import org.dspace.app.ldn.ItemFilter;
+
+/**
+ * Service interface class for the Item Filter Object
+ *
+ * @author Mohamed Eskander (mohamed.eskander at 4science.com)
+ */
+public interface ItemFilterService {
+
+ /**
+ * @param id the bean name of item filter
+ * @return one logical item filter by id
+ * defined in item-filter.xml
+ */
+ public ItemFilter findOne(String id);
+
+ /**
+ * @return all logical item filters
+ * defined in item-filter.xml
+ */
+ public List findAll();
+}
diff --git a/dspace-api/src/main/java/org/dspace/content/ItemFilterServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/ItemFilterServiceImpl.java
new file mode 100644
index 000000000000..bdb23d65666c
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/content/ItemFilterServiceImpl.java
@@ -0,0 +1,58 @@
+/**
+ * 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;
+
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import org.dspace.app.ldn.ItemFilter;
+import org.dspace.content.logic.LogicalStatement;
+import org.dspace.kernel.ServiceManager;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * Service implementation for {@link ItemFilterService}
+ *
+ * @author Mohamd Eskander (mohamed.eskander at 4science.com)
+ */
+public class ItemFilterServiceImpl implements ItemFilterService {
+
+ @Autowired
+ private ServiceManager serviceManager;
+
+ @Override
+ public ItemFilter findOne(String id) {
+ return findAll()
+ .stream()
+ .filter(itemFilter ->
+ itemFilter.getId().equals(id))
+ .findFirst()
+ .orElse(null);
+ }
+
+ @Override
+ public List findAll() {
+ Map ldnFilters =
+ serviceManager.getServiceByName("ldnItemFilters", Map.class);
+ return ldnFilters.keySet()
+ .stream()
+ .sorted()
+ .map(ItemFilter::new)
+ .collect(Collectors.toList());
+ }
+
+ public ServiceManager getServiceManager() {
+ return serviceManager;
+ }
+
+ public void setServiceManager(ServiceManager serviceManager) {
+ this.serviceManager = serviceManager;
+ }
+
+}
\ No newline at end of file
diff --git a/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java
index 9791f69abbc5..264685be7432 100644
--- a/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java
+++ b/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java
@@ -49,6 +49,7 @@
import org.dspace.content.service.RelationshipService;
import org.dspace.content.service.WorkspaceItemService;
import org.dspace.content.virtual.VirtualMetadataPopulator;
+import org.dspace.contentreport.QueryPredicate;
import org.dspace.core.Constants;
import org.dspace.core.Context;
import org.dspace.core.LogHelper;
@@ -175,7 +176,6 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl
- implements It
private QAEventsDAO qaEventsDao;
protected ItemServiceImpl() {
- super();
}
@Override
@@ -275,9 +275,8 @@ public Item createTemplateItem(Context context, Collection collection) throws SQ
+ template.getID()));
return template;
- } else {
- return collection.getTemplateItem();
}
+ return collection.getTemplateItem();
}
@Override
@@ -1190,9 +1189,8 @@ public boolean canEdit(Context context, Item item) throws SQLException {
if (item.getOwningCollection() == null) {
if (!isInProgressSubmission(context, item)) {
return true;
- } else {
- return false;
}
+ return false;
}
return collectionService.canEditBoolean(context, item.getOwningCollection(), false);
@@ -1284,8 +1282,8 @@ protected void addDefaultPoliciesNotInPlace(Context context, DSpaceObject dso,
if (!authorizeService
.isAnIdenticalPolicyAlreadyInPlace(context, dso, defaultPolicy.getGroup(), Constants.READ,
defaultPolicy.getID()) &&
- (!appendMode && this.isNotAlreadyACustomRPOfThisTypeOnDSO(context, dso) ||
- appendMode && this.shouldBeAppended(context, dso, defaultPolicy))) {
+ (!appendMode && isNotAlreadyACustomRPOfThisTypeOnDSO(context, dso) ||
+ appendMode && shouldBeAppended(context, dso, defaultPolicy))) {
ResourcePolicy newPolicy = resourcePolicyService.clone(context, defaultPolicy);
newPolicy.setdSpaceObject(dso);
newPolicy.setAction(Constants.READ);
@@ -1384,9 +1382,8 @@ public Iterator
- findArchivedByMetadataField(Context context,
if (Item.ANY.equals(value)) {
return itemDAO.findByMetadataField(context, mdf, null, true);
- } else {
- return itemDAO.findByMetadataField(context, mdf, value, true);
}
+ return itemDAO.findByMetadataField(context, mdf, value, true);
}
@Override
@@ -1430,9 +1427,24 @@ public Iterator
- findByMetadataField(Context context,
if (Item.ANY.equals(value)) {
return itemDAO.findByMetadataField(context, mdf, null, true);
- } else {
- return itemDAO.findByMetadataField(context, mdf, value, true);
}
+ return itemDAO.findByMetadataField(context, mdf, value, true);
+ }
+
+ @Override
+ public List
- findByMetadataQuery(Context context, List queryPredicates,
+ List collectionUuids, long offset, int limit)
+ throws SQLException {
+ return itemDAO.findByMetadataQuery(context, queryPredicates, collectionUuids, "text_value ~ ?",
+ offset, limit);
+ }
+
+
+ @Override
+ public long countForMetadataQuery(Context context, List queryPredicates,
+ List collectionUuids)
+ throws SQLException {
+ return itemDAO.countForMetadataQuery(context, queryPredicates, collectionUuids, "text_value ~ ?");
}
@Override
@@ -1498,20 +1510,19 @@ public DSpaceObject getParentObject(Context context, Item item) throws SQLExcept
Collection ownCollection = item.getOwningCollection();
if (ownCollection != null) {
return ownCollection;
- } else {
- InProgressSubmission inprogress = ContentServiceFactory.getInstance().getWorkspaceItemService()
- .findByItem(context,
- item);
- if (inprogress == null) {
- inprogress = WorkflowServiceFactory.getInstance().getWorkflowItemService().findByItem(context, item);
- }
+ }
+ InProgressSubmission inprogress = ContentServiceFactory.getInstance().getWorkspaceItemService()
+ .findByItem(context,
+ item);
+ if (inprogress == null) {
+ inprogress = WorkflowServiceFactory.getInstance().getWorkflowItemService().findByItem(context, item);
+ }
- if (inprogress != null) {
- return inprogress.getCollection();
- }
- // is a template item?
- return item.getTemplateItemOf();
+ if (inprogress != null) {
+ return inprogress.getCollection();
}
+ // is a template item?
+ return item.getTemplateItemOf();
}
@Override
@@ -1611,9 +1622,8 @@ public Item findByIdOrLegacyId(Context context, String id) throws SQLException {
try {
if (StringUtils.isNumeric(id)) {
return findByLegacyId(context, Integer.parseInt(id));
- } else {
- return find(context, UUID.fromString(id));
}
+ return find(context, UUID.fromString(id));
} catch (IllegalArgumentException e) {
// Not a valid legacy ID or valid UUID
return null;
diff --git a/dspace-api/src/main/java/org/dspace/content/QAEvent.java b/dspace-api/src/main/java/org/dspace/content/QAEvent.java
index d78d140dcfe5..0f2608ef705c 100644
--- a/dspace-api/src/main/java/org/dspace/content/QAEvent.java
+++ b/dspace-api/src/main/java/org/dspace/content/QAEvent.java
@@ -14,6 +14,7 @@
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import org.dspace.qaevent.service.dto.CorrectionTypeMessageDTO;
+import org.dspace.qaevent.service.dto.NotifyMessageDTO;
import org.dspace.qaevent.service.dto.OpenaireMessageDTO;
import org.dspace.qaevent.service.dto.QAMessageDTO;
import org.dspace.util.RawJsonDeserializer;
@@ -22,6 +23,7 @@
* This class represent the Quality Assurance broker data as loaded in our solr
* qaevent core
*
+ * @author Andrea Bollini (andrea.bollini at 4science.it)
*/
public class QAEvent {
public static final char[] HEX_DIGITS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e',
@@ -30,8 +32,9 @@ public class QAEvent {
public static final String REJECTED = "rejected";
public static final String DISCARDED = "discarded";
- public static final String OPENAIRE_SOURCE = "OpenAIRE";
+ public static final String OPENAIRE_SOURCE = "openaire";
public static final String DSPACE_USERS_SOURCE = "DSpaceUsers";
+ public static final String COAR_NOTIFY_SOURCE = "coar-notify";
private String source;
@@ -205,6 +208,8 @@ public Class extends QAMessageDTO> getMessageDtoClass() {
switch (getSource()) {
case OPENAIRE_SOURCE:
return OpenaireMessageDTO.class;
+ case COAR_NOTIFY_SOURCE:
+ return NotifyMessageDTO.class;
case DSPACE_USERS_SOURCE:
return CorrectionTypeMessageDTO.class;
default:
diff --git a/dspace-api/src/main/java/org/dspace/content/crosswalk/XSLTCrosswalk.java b/dspace-api/src/main/java/org/dspace/content/crosswalk/XSLTCrosswalk.java
index d4ccebf82e2c..1d07da0e51c7 100644
--- a/dspace-api/src/main/java/org/dspace/content/crosswalk/XSLTCrosswalk.java
+++ b/dspace-api/src/main/java/org/dspace/content/crosswalk/XSLTCrosswalk.java
@@ -18,12 +18,12 @@
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamSource;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
import org.dspace.core.SelfNamedPlugin;
import org.dspace.services.ConfigurationService;
import org.dspace.services.factory.DSpaceServicesFactory;
import org.jdom2.Namespace;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
/**
* Configurable XSLT-driven Crosswalk
@@ -88,7 +88,7 @@ public abstract class XSLTCrosswalk extends SelfNamedPlugin {
/**
* log4j category
*/
- private static final Logger LOG = LoggerFactory.getLogger(XSLTCrosswalk.class);
+ private static final Logger LOG = LogManager.getLogger();
/**
* DSpace XML Namespace in JDOM form.
@@ -168,8 +168,8 @@ protected Transformer getTransformer(String direction) {
transformFile.lastModified() > transformLastModified) {
try {
LOG.debug(
- (transformer == null ? "Loading {} XSLT stylesheet from {}" : "Reloading {} XSLT stylesheet from " +
- "{}"),
+ (transformer == null ? "Loading {} XSLT stylesheet from {}"
+ : "Reloading {} XSLT stylesheet from {}"),
getPluginInstanceName(), transformFile.toString());
Source transformSource
diff --git a/dspace-api/src/main/java/org/dspace/content/crosswalk/XSLTDisseminationCrosswalk.java b/dspace-api/src/main/java/org/dspace/content/crosswalk/XSLTDisseminationCrosswalk.java
index 26371b46aab0..70bb3f1316e0 100644
--- a/dspace-api/src/main/java/org/dspace/content/crosswalk/XSLTDisseminationCrosswalk.java
+++ b/dspace-api/src/main/java/org/dspace/content/crosswalk/XSLTDisseminationCrosswalk.java
@@ -23,6 +23,8 @@
import javax.xml.transform.TransformerException;
import org.apache.commons.lang3.ArrayUtils;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
import org.dspace.authorize.AuthorizeException;
import org.dspace.content.Collection;
import org.dspace.content.Community;
@@ -51,8 +53,6 @@
import org.jdom2.output.XMLOutputter;
import org.jdom2.transform.JDOMResult;
import org.jdom2.transform.JDOMSource;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
/**
* Configurable XSLT-driven dissemination Crosswalk
@@ -88,7 +88,7 @@ public class XSLTDisseminationCrosswalk
/**
* log4j category
*/
- private static final Logger LOG = LoggerFactory.getLogger(XSLTDisseminationCrosswalk.class);
+ private static final Logger LOG = LogManager.getLogger();
/**
* DSpace context, will be created if XSLTDisseminationCrosswalk had been started by command-line.
@@ -140,12 +140,13 @@ private void init()
// right format for value of "schemaLocation" attribute.
schemaLocation = configurationService.getProperty(prefix + "schemaLocation");
if (schemaLocation == null) {
- LOG.warn("No schemaLocation for crosswalk=" + myAlias + ", key=" + prefix + "schemaLocation");
+ LOG.warn("No schemaLocation for crosswalk={}, key={}schemaLocation", myAlias, prefix);
} else if (schemaLocation.length() > 0 && schemaLocation.indexOf(' ') < 0) {
// sanity check: schemaLocation should have space.
- LOG.warn("Possible INVALID schemaLocation (no space found) for crosswalk=" +
- myAlias + ", key=" + prefix + "schemaLocation" +
- "\n\tCorrect format is \"{namespace} {schema-URL}\"");
+ LOG.warn("Possible INVALID schemaLocation (no space found) for crosswalk={},"
+ + " key={}schemaLocation"
+ + "\n\tCorrect format is \"{namespace} {schema-URL}\"",
+ myAlias, prefix);
}
// grovel for namespaces of the form:
@@ -172,7 +173,7 @@ public Namespace[] getNamespaces() {
try {
init();
} catch (CrosswalkInternalException e) {
- LOG.error(e.toString());
+ LOG.error(e::toString);
}
return (Namespace[]) ArrayUtils.clone(namespaces);
}
@@ -187,7 +188,7 @@ public String getSchemaLocation() {
try {
init();
} catch (CrosswalkInternalException e) {
- LOG.error(e.toString());
+ LOG.error(e::toString);
}
return schemaLocation;
}
@@ -220,7 +221,7 @@ public Element disseminateElement(Context context, DSpaceObject dso,
}
for (Map.Entry parameter : parameters.entrySet()) {
- LOG.debug("Setting parameter {} to {}", parameter.getKey(), parameter.getValue());
+ LOG.debug("Setting parameter {} to {}", parameter::getKey, parameter::getValue);
xform.setParameter(parameter.getKey(), parameter.getValue());
}
@@ -232,7 +233,7 @@ public Element disseminateElement(Context context, DSpaceObject dso,
root.detach();
return root;
} catch (TransformerException e) {
- LOG.error("Got error: " + e.toString());
+ LOG.error("Got error: ()", e::toString);
throw new CrosswalkInternalException("XSL translation failed: " + e.toString(), e);
}
}
@@ -278,13 +279,13 @@ public List disseminateList(Context context, DSpaceObject dso)
.map(Element.class::cast).collect(Collectors.toList());
return elementList;
} catch (TransformerException e) {
- LOG.error("Got error: " + e.toString());
+ LOG.error("Got error: {}", e::toString);
throw new CrosswalkInternalException("XSL translation failed: " + e.toString(), e);
}
}
/**
- * Determine is this crosswalk can dessiminate the given object.
+ * Determine is this crosswalk can disseminate the given object.
*
* @see DisseminationCrosswalk
*/
@@ -304,7 +305,7 @@ public boolean preferList() {
try {
init();
} catch (CrosswalkInternalException e) {
- LOG.error(e.toString());
+ LOG.error(e::toString);
}
return preferList;
}
@@ -312,7 +313,7 @@ public boolean preferList() {
/**
* Generate an intermediate representation of a DSpace object.
*
- * @param dso The dspace object to build a representation of.
+ * @param dso The DSpace object to build a representation of.
* @param dcvs list of metadata
* @return element
*/
@@ -480,9 +481,7 @@ private static String checkedString(String value) {
if (reason == null) {
return value;
} else {
- if (LOG.isDebugEnabled()) {
- LOG.debug("Filtering out non-XML characters in string, reason=" + reason);
- }
+ LOG.debug("Filtering out non-XML characters in string, reason={}", reason);
StringBuilder result = new StringBuilder(value.length());
for (int i = 0; i < value.length(); ++i) {
char c = value.charAt(i);
@@ -567,11 +566,11 @@ public static void main(String[] argv) throws Exception {
System.err.println("=== Stack Trace ===");
e.printStackTrace(System.err);
System.err.println("=====================");
- LOG.error("Caught: {}.", e.toString());
- LOG.error(e.getMessage());
+ LOG.error("Caught: {}.", e::toString);
+ LOG.error(e::getMessage);
CharArrayWriter traceWriter = new CharArrayWriter(2048);
e.printStackTrace(new PrintWriter(traceWriter));
- LOG.error(traceWriter.toString());
+ LOG.error(traceWriter::toString);
System.exit(1);
}
@@ -588,11 +587,11 @@ public static void main(String[] argv) throws Exception {
System.err.println("=== Stack Trace ===");
e.printStackTrace(System.err);
System.err.println("=====================");
- LOG.error("Caught: {}.", e.toString());
- LOG.error(e.getMessage());
+ LOG.error("Caught: {}.", e::toString);
+ LOG.error(e::getMessage);
CharArrayWriter traceWriter = new CharArrayWriter(2048);
e.printStackTrace(new PrintWriter(traceWriter));
- LOG.error(traceWriter.toString());
+ LOG.error(traceWriter::toString);
System.exit(1);
}
diff --git a/dspace-api/src/main/java/org/dspace/content/dao/ItemDAO.java b/dspace-api/src/main/java/org/dspace/content/dao/ItemDAO.java
index 49d3527a358a..92dc0fb237c9 100644
--- a/dspace-api/src/main/java/org/dspace/content/dao/ItemDAO.java
+++ b/dspace-api/src/main/java/org/dspace/content/dao/ItemDAO.java
@@ -11,11 +11,13 @@
import java.util.Date;
import java.util.Iterator;
import java.util.List;
+import java.util.UUID;
import org.dspace.content.Collection;
import org.dspace.content.Community;
import org.dspace.content.Item;
import org.dspace.content.MetadataField;
+import org.dspace.contentreport.QueryPredicate;
import org.dspace.core.Context;
import org.dspace.eperson.EPerson;
@@ -27,12 +29,11 @@
* @author kevinvandevelde at atmire.com
*/
public interface ItemDAO extends DSpaceObjectLegacySupportDAO
- {
- public Iterator
- findAll(Context context, boolean archived) throws SQLException;
+ Iterator
- findAll(Context context, boolean archived) throws SQLException;
- public Iterator
- findAll(Context context, boolean archived, int limit, int offset) throws SQLException;
+ Iterator
- findAll(Context context, boolean archived, int limit, int offset) throws SQLException;
- @Deprecated
- public Iterator
- findAll(Context context, boolean archived, boolean withdrawn) throws SQLException;
+ @Deprecated Iterator
- findAll(Context context, boolean archived, boolean withdrawn) throws SQLException;
/**
* Find all items that are:
@@ -45,7 +46,7 @@ public interface ItemDAO extends DSpaceObjectLegacySupportDAO
- {
* @return iterator over all regular items.
* @throws SQLException if database error.
*/
- public Iterator
- findAllRegularItems(Context context) throws SQLException;
+ Iterator
- findAllRegularItems(Context context) throws SQLException;
/**
* Find all Items modified since a Date.
@@ -55,10 +56,10 @@ public interface ItemDAO extends DSpaceObjectLegacySupportDAO
- {
* @return iterator over items
* @throws SQLException if database error
*/
- public Iterator
- findByLastModifiedSince(Context context, Date since)
+ Iterator
- findByLastModifiedSince(Context context, Date since)
throws SQLException;
- public Iterator
- findBySubmitter(Context context, EPerson eperson) throws SQLException;
+ Iterator
- findBySubmitter(Context context, EPerson eperson) throws SQLException;
/**
* Find all the items by a given submitter. The order is
@@ -70,19 +71,40 @@ public Iterator
- findByLastModifiedSince(Context context, Date since)
* @return an iterator over the items submitted by eperson
* @throws SQLException if database error
*/
- public Iterator
- findBySubmitter(Context context, EPerson eperson, boolean retrieveAllItems)
+ Iterator
- findBySubmitter(Context context, EPerson eperson, boolean retrieveAllItems)
throws SQLException;
- public Iterator
- findBySubmitter(Context context, EPerson eperson, MetadataField metadataField, int limit)
+ Iterator
- findBySubmitter(Context context, EPerson eperson, MetadataField metadataField, int limit)
throws SQLException;
- public Iterator
- findByMetadataField(Context context, MetadataField metadataField, String value,
+ Iterator
- findByMetadataField(Context context, MetadataField metadataField, String value,
boolean inArchive) throws SQLException;
- public Iterator
- findByAuthorityValue(Context context, MetadataField metadataField, String authority,
+ /**
+ * Returns all the Items that belong to the specified aollections (if any)
+ * and match the provided predicates.
+ * @param context The relevant DSpace context
+ * @param queryPredicates List of predicates that returned items are required to match
+ * @param collectionUuids UUIDs of the collections to search.
+ * If none are provided, the entire repository will be searched.
+ * @param regexClause Syntactic expression used to query the database using a regular expression
+ * (e.g.: "text_value ~ ?")
+ * @param offset The offset for the query
+ * @param limit Maximum number of items to return
+ * @return A list containing the items that match the provided criteria
+ * @throws SQLException if something goes wrong
+ */
+ List
- findByMetadataQuery(Context context, List queryPredicates,
+ List collectionUuids, String regexClause,
+ long offset, int limit) throws SQLException;
+
+ long countForMetadataQuery(Context context, List queryPredicates,
+ List collectionUuids, String regexClause) throws SQLException;
+
+ Iterator
- findByAuthorityValue(Context context, MetadataField metadataField, String authority,
boolean inArchive) throws SQLException;
- public Iterator
- findArchivedByCollection(Context context, Collection collection, Integer limit,
+ Iterator
- findArchivedByCollection(Context context, Collection collection, Integer limit,
Integer offset) throws SQLException;
/**
@@ -95,7 +117,7 @@ public Iterator
- findArchivedByCollection(Context context, Collection colle
* @return An iterator containing the items for which the constraints hold true
* @throws SQLException If something goes wrong
*/
- public Iterator
- findArchivedByCollectionExcludingOwning(Context context, Collection collection, Integer limit,
+ Iterator
- findArchivedByCollectionExcludingOwning(Context context, Collection collection, Integer limit,
Integer offset) throws SQLException;
/**
@@ -106,11 +128,11 @@ public Iterator
- findArchivedByCollectionExcludingOwning(Context context, C
* @return The total amount of items that fit the constraints
* @throws SQLException If something goes wrong
*/
- public int countArchivedByCollectionExcludingOwning(Context context, Collection collection) throws SQLException;
+ int countArchivedByCollectionExcludingOwning(Context context, Collection collection) throws SQLException;
- public Iterator
- findAllByCollection(Context context, Collection collection) throws SQLException;
+ Iterator
- findAllByCollection(Context context, Collection collection) throws SQLException;
- public Iterator
- findAllByCollection(Context context, Collection collection, Integer limit, Integer offset)
+ Iterator
- findAllByCollection(Context context, Collection collection, Integer limit, Integer offset)
throws SQLException;
/**
@@ -123,7 +145,7 @@ public Iterator
- findAllByCollection(Context context, Collection collection
* @return item count
* @throws SQLException if database error
*/
- public int countItems(Context context, Collection collection, boolean includeArchived, boolean includeWithdrawn)
+ int countItems(Context context, Collection collection, boolean includeArchived, boolean includeWithdrawn)
throws SQLException;
/**
@@ -139,7 +161,7 @@ public int countItems(Context context, Collection collection, boolean includeArc
* @return item count
* @throws SQLException if database error
*/
- public int countItems(Context context, List collections, boolean includeArchived,
+ int countItems(Context context, List collections, boolean includeArchived,
boolean includeWithdrawn) throws SQLException;
/**
@@ -153,7 +175,7 @@ public int countItems(Context context, List collections, boolean inc
* @return iterator over items
* @throws SQLException if database error
*/
- public Iterator
- findAll(Context context, boolean archived,
+ Iterator
- findAll(Context context, boolean archived,
boolean withdrawn, boolean discoverable, Date lastModified)
throws SQLException;
@@ -187,7 +209,7 @@ public Iterator
- findAll(Context context, boolean archived,
* @return count of items
* @throws SQLException if database error
*/
- public int countItems(Context context, EPerson submitter, boolean includeArchived, boolean includeWithdrawn)
+ int countItems(Context context, EPerson submitter, boolean includeArchived, boolean includeWithdrawn)
throws SQLException;
}
diff --git a/dspace-api/src/main/java/org/dspace/content/dao/impl/BitstreamDAOImpl.java b/dspace-api/src/main/java/org/dspace/content/dao/impl/BitstreamDAOImpl.java
index 0e051625aaee..276ea2b3430a 100644
--- a/dspace-api/src/main/java/org/dspace/content/dao/impl/BitstreamDAOImpl.java
+++ b/dspace-api/src/main/java/org/dspace/content/dao/impl/BitstreamDAOImpl.java
@@ -12,6 +12,7 @@
import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import java.util.UUID;
import javax.persistence.Query;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
@@ -26,6 +27,7 @@
import org.dspace.core.AbstractHibernateDSODAO;
import org.dspace.core.Constants;
import org.dspace.core.Context;
+import org.dspace.core.UUIDIterator;
/**
* Hibernate implementation of the Database Access Object interface class for the Bitstream object.
@@ -76,7 +78,7 @@ public List findBitstreamsWithNoRecentChecksum(Context context) throw
@Override
public Iterator findByCommunity(Context context, Community community) throws SQLException {
- Query query = createQuery(context, "select b from Bitstream b " +
+ Query query = createQuery(context, "select b.id from Bitstream b " +
"join b.bundles bitBundles " +
"join bitBundles.items item " +
"join item.collections itemColl " +
@@ -84,40 +86,45 @@ public Iterator findByCommunity(Context context, Community community)
"WHERE :community IN community");
query.setParameter("community", community);
-
- return iterate(query);
+ @SuppressWarnings("unchecked")
+ List uuids = query.getResultList();
+ return new UUIDIterator(context, uuids, Bitstream.class, this);
}
@Override
public Iterator findByCollection(Context context, Collection collection) throws SQLException {
- Query query = createQuery(context, "select b from Bitstream b " +
+ Query query = createQuery(context, "select b.id from Bitstream b " +
"join b.bundles bitBundles " +
"join bitBundles.items item " +
"join item.collections c " +
"WHERE :collection IN c");
query.setParameter("collection", collection);
-
- return iterate(query);
+ @SuppressWarnings("unchecked")
+ List uuids = query.getResultList();
+ return new UUIDIterator(context, uuids, Bitstream.class, this);
}
@Override
public Iterator findByItem(Context context, Item item) throws SQLException {
- Query query = createQuery(context, "select b from Bitstream b " +
+ Query query = createQuery(context, "select b.id from Bitstream b " +
"join b.bundles bitBundles " +
"join bitBundles.items item " +
"WHERE :item IN item");
query.setParameter("item", item);
-
- return iterate(query);
+ @SuppressWarnings("unchecked")
+ List uuids = query.getResultList();
+ return new UUIDIterator(context, uuids, Bitstream.class, this);
}
@Override
public Iterator findByStoreNumber(Context context, Integer storeNumber) throws SQLException {
- Query query = createQuery(context, "select b from Bitstream b where b.storeNumber = :storeNumber");
+ Query query = createQuery(context, "select b.id from Bitstream b where b.storeNumber = :storeNumber");
query.setParameter("storeNumber", storeNumber);
- return iterate(query);
+ @SuppressWarnings("unchecked")
+ List uuids = query.getResultList();
+ return new UUIDIterator(context, uuids, Bitstream.class, this);
}
@Override
diff --git a/dspace-api/src/main/java/org/dspace/content/dao/impl/ItemDAOImpl.java b/dspace-api/src/main/java/org/dspace/content/dao/impl/ItemDAOImpl.java
index 5c840f68e998..0a36fdeb2ab0 100644
--- a/dspace-api/src/main/java/org/dspace/content/dao/impl/ItemDAOImpl.java
+++ b/dspace-api/src/main/java/org/dspace/content/dao/impl/ItemDAOImpl.java
@@ -8,25 +8,37 @@
package org.dspace.content.dao.impl;
import java.sql.SQLException;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
+import java.util.UUID;
import javax.persistence.Query;
import javax.persistence.TemporalType;
import javax.persistence.criteria.CriteriaBuilder;
+import javax.persistence.criteria.CriteriaBuilder.In;
import javax.persistence.criteria.CriteriaQuery;
+import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
+import javax.persistence.criteria.Subquery;
import org.apache.logging.log4j.Logger;
import org.dspace.content.Collection;
+import org.dspace.content.DSpaceObject_;
import org.dspace.content.Item;
import org.dspace.content.Item_;
import org.dspace.content.MetadataField;
+import org.dspace.content.MetadataValue;
+import org.dspace.content.MetadataValue_;
import org.dspace.content.dao.ItemDAO;
+import org.dspace.contentreport.QueryOperator;
+import org.dspace.contentreport.QueryPredicate;
import org.dspace.core.AbstractHibernateDSODAO;
import org.dspace.core.Context;
+import org.dspace.core.UUIDIterator;
import org.dspace.eperson.EPerson;
+import org.dspace.util.JpaCriteriaBuilderKit;
/**
* Hibernate implementation of the Database Access Object interface class for the Item object.
@@ -39,33 +51,38 @@ public class ItemDAOImpl extends AbstractHibernateDSODAO
- implements ItemDA
private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(ItemDAOImpl.class);
protected ItemDAOImpl() {
- super();
}
@Override
public Iterator
- findAll(Context context, boolean archived) throws SQLException {
- Query query = createQuery(context, "FROM Item WHERE inArchive=:in_archive ORDER BY id");
+ Query query = createQuery(context, "SELECT i.id FROM Item i WHERE inArchive=:in_archive ORDER BY id");
query.setParameter("in_archive", archived);
- return iterate(query);
+ @SuppressWarnings("unchecked")
+ List uuids = query.getResultList();
+ return new UUIDIterator
- (context, uuids, Item.class, this);
}
@Override
public Iterator
- findAll(Context context, boolean archived, int limit, int offset) throws SQLException {
- Query query = createQuery(context, "FROM Item WHERE inArchive=:in_archive ORDER BY id");
+ Query query = createQuery(context, "SELECT i.id FROM Item i WHERE inArchive=:in_archive ORDER BY id");
query.setParameter("in_archive", archived);
query.setFirstResult(offset);
query.setMaxResults(limit);
- return iterate(query);
+ @SuppressWarnings("unchecked")
+ List uuids = query.getResultList();
+ return new UUIDIterator
- (context, uuids, Item.class, this);
}
@Override
public Iterator
- findAll(Context context, boolean archived, boolean withdrawn) throws SQLException {
Query query = createQuery(context,
- "FROM Item WHERE inArchive=:in_archive or withdrawn=:withdrawn ORDER BY id");
+ "SELECT i.id FROM Item i WHERE inArchive=:in_archive or withdrawn=:withdrawn ORDER BY id");
query.setParameter("in_archive", archived);
query.setParameter("withdrawn", withdrawn);
- return iterate(query);
+ @SuppressWarnings("unchecked")
+ List uuids = query.getResultList();
+ return new UUIDIterator
- (context, uuids, Item.class, this);
}
@Override
@@ -74,12 +91,14 @@ public Iterator
- findAllRegularItems(Context context) throws SQLException {
// It does not include workspace, workflow or template items.
Query query = createQuery(
context,
- "SELECT i FROM Item as i " +
+ "SELECT i.id FROM Item as i " +
"LEFT JOIN Version as v ON i = v.item " +
"WHERE i.inArchive=true or i.withdrawn=true or (i.inArchive=false and v.id IS NOT NULL) " +
"ORDER BY i.id"
);
- return iterate(query);
+ @SuppressWarnings("unchecked")
+ List uuids = query.getResultList();
+ return new UUIDIterator
- (context, uuids, Item.class, this);
}
@Override
@@ -87,7 +106,7 @@ public Iterator
- findAll(Context context, boolean archived,
boolean withdrawn, boolean discoverable, Date lastModified)
throws SQLException {
StringBuilder queryStr = new StringBuilder();
- queryStr.append("SELECT i FROM Item i");
+ queryStr.append("SELECT i.id FROM Item i");
queryStr.append(" WHERE (inArchive = :in_archive OR withdrawn = :withdrawn)");
queryStr.append(" AND discoverable = :discoverable");
@@ -103,16 +122,20 @@ public Iterator
- findAll(Context context, boolean archived,
if (lastModified != null) {
query.setParameter("last_modified", lastModified, TemporalType.TIMESTAMP);
}
- return iterate(query);
+ @SuppressWarnings("unchecked")
+ List uuids = query.getResultList();
+ return new UUIDIterator
- (context, uuids, Item.class, this);
}
@Override
public Iterator
- findBySubmitter(Context context, EPerson eperson) throws SQLException {
Query query = createQuery(context,
- "FROM Item WHERE inArchive=:in_archive and submitter=:submitter ORDER BY id");
+ "SELECT i.id FROM Item i WHERE inArchive=:in_archive and submitter=:submitter ORDER BY id");
query.setParameter("in_archive", true);
query.setParameter("submitter", eperson);
- return iterate(query);
+ @SuppressWarnings("unchecked")
+ List uuids = query.getResultList();
+ return new UUIDIterator
- (context, uuids, Item.class, this);
}
@Override
@@ -121,16 +144,18 @@ public Iterator
- findBySubmitter(Context context, EPerson eperson, boolean
if (!retrieveAllItems) {
return findBySubmitter(context, eperson);
}
- Query query = createQuery(context, "FROM Item WHERE submitter=:submitter ORDER BY id");
+ Query query = createQuery(context, "SELECT i.id FROM Item i WHERE submitter=:submitter ORDER BY id");
query.setParameter("submitter", eperson);
- return iterate(query);
+ @SuppressWarnings("unchecked")
+ List uuids = query.getResultList();
+ return new UUIDIterator
- (context, uuids, Item.class, this);
}
@Override
public Iterator
- findBySubmitter(Context context, EPerson eperson, MetadataField metadataField, int limit)
throws SQLException {
StringBuilder query = new StringBuilder();
- query.append("SELECT item FROM Item as item ");
+ query.append("SELECT item.id FROM Item as item ");
addMetadataLeftJoin(query, Item.class.getSimpleName().toLowerCase(), Collections.singletonList(metadataField));
query.append(" WHERE item.inArchive = :in_archive");
query.append(" AND item.submitter =:submitter");
@@ -142,13 +167,15 @@ public Iterator