[CST-10632] Implement the consumer to enqueue outgoing LDN messages

This commit is contained in:
mohamed eskander
2023-11-20 20:02:45 +02:00
parent af0686d50f
commit 989d718b9b
17 changed files with 1224 additions and 13 deletions

View File

@@ -0,0 +1,189 @@
/**
* 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.List;
import java.util.Locale;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import org.apache.commons.collections4.CollectionUtils;
import org.dspace.app.ldn.factory.NotifyServiceFactory;
import org.dspace.app.ldn.service.LDNMessageService;
import org.dspace.app.ldn.service.NotifyPatternToTriggerService;
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.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.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 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();
}
@Override
public void consume(Context context, Event event) throws Exception {
if (event.getSubjectType() != Constants.ITEM ||
event.getEventType() != Event.INSTALL) {
return;
}
createLDNMessages(context, (Item) event.getSubject(context));
}
private void createLDNMessages(Context context, Item item) throws SQLException {
List<NotifyPatternToTrigger> patternsToTrigger =
notifyPatternToTriggerService.findByItem(context, item);
patternsToTrigger.forEach(patternToTrigger -> {
try {
createLDNMessage(context, patternToTrigger);
} catch (SQLException e) {
throw new RuntimeException(e);
}
});
}
private void createLDNMessage(Context context, NotifyPatternToTrigger patternToTrigger)
throws SQLException {
LDN ldn = getLDNMessage(patternToTrigger.getPattern());
LDNMessageEntity ldnMessage =
ldnMessageService.create(context, format("urn:uuid:%s", UUID.randomUUID()));
ldnMessage.setObject(patternToTrigger.getItem());
ldnMessage.setTarget(patternToTrigger.getNotifyService());
ldnMessage.setQueueStatus(LDNMessageEntity.QUEUE_STATUS_QUEUED);
appendGeneratedMessage(ldn, ldnMessage, patternToTrigger.getPattern());
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<Bitstream> findPrimaryBitstream(Item item) {
List<Bundle> 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> 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 {
}
}

View File

@@ -21,7 +21,8 @@ import org.dspace.app.ldn.processor.LDNProcessor;
*/ */
public class LDNRouter { public class LDNRouter {
private Map<Set<String>, LDNProcessor> processors = new HashMap<>(); private Map<Set<String>, LDNProcessor> incomingProcessors = new HashMap<>();
private Map<Set<String>, LDNProcessor> outcomingProcessors = new HashMap<>();
private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(LDNRouter.class); private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(LDNRouter.class);
/** /**
@@ -41,26 +42,50 @@ public class LDNRouter {
Set<String> ldnMessageTypeSet = new HashSet<String>(); Set<String> ldnMessageTypeSet = new HashSet<String>();
ldnMessageTypeSet.add(ldnMessage.getActivityStreamType()); ldnMessageTypeSet.add(ldnMessage.getActivityStreamType());
ldnMessageTypeSet.add(ldnMessage.getCoarNotifyType()); ldnMessageTypeSet.add(ldnMessage.getCoarNotifyType());
LDNProcessor processor = processors.get(ldnMessageTypeSet);
LDNProcessor processor = null;
if (ldnMessage.getTarget() == null) {
processor = incomingProcessors.get(ldnMessageTypeSet);
} else if (ldnMessage.getOrigin() == null) {
processor = outcomingProcessors.get(ldnMessageTypeSet);
}
return processor; return processor;
} }
/** /**
* Get all routes. * Get all incoming routes.
* *
* @return Map<Set<String>, LDNProcessor> * @return Map<Set<String>, LDNProcessor>
*/ */
public Map<Set<String>, LDNProcessor> getProcessors() { public Map<Set<String>, LDNProcessor> getIncomingProcessors() {
return processors; return incomingProcessors;
} }
/** /**
* Set all routes. * Set all incoming routes.
* *
* @param processors * @param incomingProcessors
*/ */
public void setProcessors(Map<Set<String>, LDNProcessor> processors) { public void setIncomingProcessors(Map<Set<String>, LDNProcessor> incomingProcessors) {
this.processors = processors; this.incomingProcessors = incomingProcessors;
} }
/**
* Get all outcoming routes.
*
* @return Map<Set<String>, LDNProcessor>
*/
public Map<Set<String>, LDNProcessor> getOutcomingProcessors() {
return outcomingProcessors;
}
/**
* Set all outcoming routes.
*
* @param outcomingProcessors
*/
public void setOutcomingProcessors(Map<Set<String>, LDNProcessor> outcomingProcessors) {
this.outcomingProcessors = outcomingProcessors;
}
} }

View File

@@ -0,0 +1,65 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.ldn.action;
import static org.dspace.app.ldn.RdfMediaType.APPLICATION_JSON_LD;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.dspace.app.ldn.converter.JsonLdHttpMessageConverter;
import org.dspace.app.ldn.model.Notification;
import org.dspace.content.Item;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;
/**
* 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 final RestTemplate restTemplate;
public SendLDNMessageAction() {
restTemplate = new RestTemplate();
restTemplate.getMessageConverters().add(new JsonLdHttpMessageConverter());
}
@Override
public ActionStatus execute(Notification notification, Item item) throws Exception {
//TODO authorization with Bearer token should be supported.
HttpHeaders headers = new HttpHeaders();
headers.add("Content-Type", APPLICATION_JSON_LD.toString());
HttpEntity<Notification> request = new HttpEntity<>(notification, headers);
log.info("Announcing notification {}", request);
// https://notify-inbox.info/inbox/ for test
ResponseEntity<String> response = restTemplate.postForEntity(
notification.getTarget().getInbox(),
request,
String.class
);
if (response.getStatusCode() == HttpStatus.ACCEPTED ||
response.getStatusCode() == HttpStatus.CREATED) {
return ActionStatus.CONTINUE;
}
return ActionStatus.ABORT;
}
}

View File

@@ -7,6 +7,7 @@
*/ */
package org.dspace.app.ldn.factory; 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.NotifyPatternToTriggerService;
import org.dspace.app.ldn.service.NotifyService; import org.dspace.app.ldn.service.NotifyService;
import org.dspace.app.ldn.service.NotifyServiceInboundPatternService; import org.dspace.app.ldn.service.NotifyServiceInboundPatternService;
@@ -26,6 +27,8 @@ public abstract class NotifyServiceFactory {
public abstract NotifyPatternToTriggerService getNotifyPatternToTriggerService(); public abstract NotifyPatternToTriggerService getNotifyPatternToTriggerService();
public abstract LDNMessageService getLDNMessageService();
public static NotifyServiceFactory getInstance() { public static NotifyServiceFactory getInstance() {
return DSpaceServicesFactory.getInstance().getServiceManager().getServiceByName( return DSpaceServicesFactory.getInstance().getServiceManager().getServiceByName(
"notifyServiceFactory", NotifyServiceFactory.class); "notifyServiceFactory", NotifyServiceFactory.class);

View File

@@ -7,6 +7,7 @@
*/ */
package org.dspace.app.ldn.factory; 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.NotifyPatternToTriggerService;
import org.dspace.app.ldn.service.NotifyService; import org.dspace.app.ldn.service.NotifyService;
import org.dspace.app.ldn.service.NotifyServiceInboundPatternService; import org.dspace.app.ldn.service.NotifyServiceInboundPatternService;
@@ -29,6 +30,9 @@ public class NotifyServiceFactoryImpl extends NotifyServiceFactory {
@Autowired(required = true) @Autowired(required = true)
private NotifyPatternToTriggerService notifyPatternToTriggerService; private NotifyPatternToTriggerService notifyPatternToTriggerService;
@Autowired(required = true)
private LDNMessageService ldnMessageService;
@Override @Override
public NotifyService getNotifyService() { public NotifyService getNotifyService() {
return notifyService; return notifyService;
@@ -44,4 +48,9 @@ public class NotifyServiceFactoryImpl extends NotifyServiceFactory {
return notifyPatternToTriggerService; return notifyPatternToTriggerService;
} }
@Override
public LDNMessageService getLDNMessageService() {
return ldnMessageService;
}
} }

View File

@@ -33,6 +33,15 @@ public interface LDNMessageService {
*/ */
public LDNMessageEntity find(Context context, String id) throws SQLException; 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<LDNMessageEntity> findAll(Context context) throws SQLException;
/** /**
* Creates a new LDNMessage * Creates a new LDNMessage
* *
@@ -106,4 +115,12 @@ public interface LDNMessageService {
* @throws SQLException if something goes wrong * @throws SQLException if something goes wrong
*/ */
public NotifyServiceEntity findNotifyService(Context context, Service service) throws SQLException; public NotifyServiceEntity findNotifyService(Context context, Service service) 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;
} }

View File

@@ -69,6 +69,11 @@ public class LDNMessageServiceImpl implements LDNMessageService {
return ldnMessageDao.findByID(context, LDNMessageEntity.class, id); return ldnMessageDao.findByID(context, LDNMessageEntity.class, id);
} }
@Override
public List<LDNMessageEntity> findAll(Context context) throws SQLException {
return ldnMessageDao.findAll(context, LDNMessageEntity.class);
}
@Override @Override
public LDNMessageEntity create(Context context, String id) throws SQLException { public LDNMessageEntity create(Context context, String id) throws SQLException {
return ldnMessageDao.create(context, new LDNMessageEntity(id)); return ldnMessageDao.create(context, new LDNMessageEntity(id));
@@ -268,4 +273,10 @@ public class LDNMessageServiceImpl implements LDNMessageService {
} }
return result; return result;
} }
@Override
public void delete(Context context, LDNMessageEntity ldnMessage) throws SQLException {
ldnMessageDao.delete(context, ldnMessage);
}
} }

View File

@@ -377,6 +377,22 @@ public class I18nUtil {
return templateName; return templateName;
} }
/**
* Get the appropriate localized version of a ldn template according to language settings
*
* @param locale Locale for this request
* @param name String - base name of the ldn template
* @return templateName
* String - localized filename of a ldn template
*/
public static String getLDNFilename(Locale locale, String name) {
String templateFile =
DSpaceServicesFactory.getInstance().getConfigurationService().getProperty("dspace.dir") +
File.separator + "config" + File.separator + "ldn" + File.separator + name;
return getFilename(locale, templateFile, "");
}
/** /**
* Creates array of Locales from text list of locale-specifications. * Creates array of Locales from text list of locale-specifications.
* Used to parse lists in DSpace configuration properties. * Used to parse lists in DSpace configuration properties.

View File

@@ -0,0 +1,212 @@
/**
* 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.core;
import static org.apache.commons.lang3.StringUtils.EMPTY;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Properties;
import javax.mail.MessagingException;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.exception.MethodInvocationException;
import org.apache.velocity.exception.ParseErrorException;
import org.apache.velocity.exception.ResourceNotFoundException;
import org.apache.velocity.runtime.resource.loader.StringResourceLoader;
import org.apache.velocity.runtime.resource.util.StringResourceRepository;
import org.dspace.services.ConfigurationService;
import org.dspace.services.factory.DSpaceServicesFactory;
/**
* Class representing an LDN message json
*
* @author Mohamed Eskander (mohamed.eskander at 4science.com)
*/
public class LDN {
/**
* The content of the ldn message
*/
private String content;
private String contentName;
/**
* The arguments to fill out
*/
private final List<Object> arguments;
private static final Logger LOG = LogManager.getLogger();
/** Velocity template settings. */
private static final String RESOURCE_REPOSITORY_NAME = "LDN";
private static final Properties VELOCITY_PROPERTIES = new Properties();
static {
VELOCITY_PROPERTIES.put(Velocity.RESOURCE_LOADERS, "string");
VELOCITY_PROPERTIES.put("resource.loader.string.description",
"Velocity StringResource loader");
VELOCITY_PROPERTIES.put("resource.loader.string.class",
StringResourceLoader.class.getName());
VELOCITY_PROPERTIES.put("resource.loader.string.repository.name",
RESOURCE_REPOSITORY_NAME);
VELOCITY_PROPERTIES.put("resource.loader.string.repository.static",
"false");
}
/** Velocity template for the message*/
private Template template;
/**
* Create a new ldn message.
*/
public LDN() {
arguments = new ArrayList<>(20);
template = null;
content = EMPTY;
}
/**
* Set the content of the message. Setting this also "resets" the message
* formatting - <code>addArgument</code> will start over. Comments and any
* "Subject:" line must be stripped.
*
* @param name a name for this message
* @param cnt the content of the message
*/
public void setContent(String name, String cnt) {
content = cnt;
contentName = name;
arguments.clear();
}
/**
* Fill out the next argument in the template
*
* @param arg the value for the next argument
*/
public void addArgument(Object arg) {
arguments.add(arg);
}
/**
* Generates the ldn message.
*
* @throws MessagingException if there was a problem sending the mail.
* @throws IOException if IO error
*/
public String generateLDNMessage() {
ConfigurationService config
= DSpaceServicesFactory.getInstance().getConfigurationService();
VelocityEngine templateEngine = new VelocityEngine();
templateEngine.init(VELOCITY_PROPERTIES);
VelocityContext vctx = new VelocityContext();
vctx.put("config", new LDN.UnmodifiableConfigurationService(config));
vctx.put("params", Collections.unmodifiableList(arguments));
if (null == template) {
if (StringUtils.isBlank(content)) {
LOG.error("template has no content");
throw new RuntimeException("template has no content");
}
// No template, so use a String of content.
StringResourceRepository repo = (StringResourceRepository)
templateEngine.getApplicationAttribute(RESOURCE_REPOSITORY_NAME);
repo.putStringResource(contentName, content);
// Turn content into a template.
template = templateEngine.getTemplate(contentName);
}
StringWriter writer = new StringWriter();
try {
template.merge(vctx, writer);
} catch (MethodInvocationException | ParseErrorException
| ResourceNotFoundException ex) {
LOG.error("Template not merged: {}", ex.getMessage());
throw new RuntimeException("Template not merged", ex);
}
return writer.toString();
}
/**
* Get the VTL template for a ldn message. The message is suitable
* for inserting values using Apache Velocity.
*
* @param ldnMessageFile
* full name for the ldn template, for example "/dspace/config/ldn/request-review".
*
* @return the ldn object, configured with body.
*
* @throws IOException if IO error,
* if the template couldn't be found, or there was some other
* error reading the template
*/
public static LDN getLDNMessage(String ldnMessageFile)
throws IOException {
StringBuilder contentBuffer = new StringBuilder();
try (
InputStream is = new FileInputStream(ldnMessageFile);
InputStreamReader ir = new InputStreamReader(is, "UTF-8");
BufferedReader reader = new BufferedReader(ir);
) {
boolean more = true;
while (more) {
String line = reader.readLine();
if (line == null) {
more = false;
} else {
contentBuffer.append(line);
contentBuffer.append("\n");
}
}
}
LDN ldn = new LDN();
ldn.setContent(ldnMessageFile, contentBuffer.toString());
return ldn;
}
/**
* Wrap ConfigurationService to prevent templates from modifying
* the configuration.
*/
public static class UnmodifiableConfigurationService {
private final ConfigurationService configurationService;
/**
* Swallow an instance of ConfigurationService.
*
* @param cs the real instance, to be wrapped.
*/
public UnmodifiableConfigurationService(ConfigurationService cs) {
configurationService = cs;
}
/**
* Look up a key in the actual ConfigurationService.
*
* @param key to be looked up in the DSpace configuration.
* @return whatever value ConfigurationService associates with {@code key}.
*/
public String get(String key) {
return configurationService.getProperty(key);
}
}
}

View File

@@ -95,14 +95,14 @@ loglevel.dspace = INFO
# IIIF TEST SETTINGS # # IIIF TEST SETTINGS #
######################## ########################
iiif.enabled = true iiif.enabled = true
event.dispatcher.default.consumers = versioning, discovery, eperson, orcidqueue, iiif, qaeventsdelete event.dispatcher.default.consumers = versioning, discovery, eperson, orcidqueue, iiif, qaeventsdelete, ldnmessage
########################################### ###########################################
# CUSTOM UNIT / INTEGRATION TEST SETTINGS # # CUSTOM UNIT / INTEGRATION TEST SETTINGS #
########################################### ###########################################
# custom dispatcher to be used by dspace-api IT that doesn't need SOLR # custom dispatcher to be used by dspace-api IT that doesn't need SOLR
event.dispatcher.exclude-discovery.class = org.dspace.event.BasicDispatcher event.dispatcher.exclude-discovery.class = org.dspace.event.BasicDispatcher
event.dispatcher.exclude-discovery.consumers = versioning, eperson, qaeventsdelete event.dispatcher.exclude-discovery.consumers = versioning, eperson, qaeventsdelete, ldnmessage
# Configure authority control for Unit Testing (in DSpaceControlledVocabularyTest) # Configure authority control for Unit Testing (in DSpaceControlledVocabularyTest)
# (This overrides default, commented out settings in dspace.cfg) # (This overrides default, commented out settings in dspace.cfg)

View File

@@ -0,0 +1,378 @@
/**
* 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 org.dspace.app.ldn.LDNMessageEntity.QUEUE_STATUS_QUEUED;
import static org.dspace.matcher.NotifyServiceEntityMatcher.matchesNotifyServiceEntity;
import static org.hamcrest.CoreMatchers.containsString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import java.io.InputStream;
import java.sql.SQLException;
import java.util.List;
import java.util.Set;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.collections4.CollectionUtils;
import org.dspace.AbstractIntegrationTestWithDatabase;
import org.dspace.app.ldn.factory.NotifyServiceFactory;
import org.dspace.app.ldn.model.Notification;
import org.dspace.app.ldn.service.LDNMessageService;
import org.dspace.builder.CollectionBuilder;
import org.dspace.builder.CommunityBuilder;
import org.dspace.builder.EPersonBuilder;
import org.dspace.builder.NotifyServiceBuilder;
import org.dspace.builder.WorkspaceItemBuilder;
import org.dspace.content.Collection;
import org.dspace.content.Item;
import org.dspace.content.WorkspaceItem;
import org.dspace.content.factory.ContentServiceFactory;
import org.dspace.content.service.ItemService;
import org.dspace.core.Constants;
import org.dspace.eperson.EPerson;
import org.dspace.services.ConfigurationService;
import org.dspace.services.factory.DSpaceServicesFactory;
import org.dspace.workflow.WorkflowItem;
import org.dspace.workflow.WorkflowService;
import org.dspace.workflow.factory.WorkflowServiceFactory;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
/**
* Integration Tests against {@link LDNMessageConsumer}
*
* @author Mohamed Eskander (mohamed.eskander at 4science.com)
*/
public class LDNMessageConsumerIT extends AbstractIntegrationTestWithDatabase {
private Collection collection;
private EPerson submitter;
private LDNMessageService ldnMessageService = NotifyServiceFactory.getInstance().getLDNMessageService();
private ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService();
private WorkflowService workflowService = WorkflowServiceFactory.getInstance().getWorkflowService();
private ItemService itemService = ContentServiceFactory.getInstance().getItemService();
@Before
public void setUp() throws Exception {
super.setUp();
context.turnOffAuthorisationSystem();
//** GIVEN **
//1. create a normal user to use as submitter
submitter = EPersonBuilder.createEPerson(context)
.withEmail("submitter@example.com")
.withPassword(password)
.build();
//2. A community with one collection.
parentCommunity = CommunityBuilder.createCommunity(context)
.withName("Parent Community")
.build();
collection = CollectionBuilder.createCollection(context, parentCommunity)
.withName("Collection 1")
.withSubmitterGroup(submitter)
.build();
context.setCurrentUser(submitter);
context.restoreAuthSystemState();
}
@Test
public void testLDNMessageConsumerRequestReview() throws Exception {
context.turnOffAuthorisationSystem();
NotifyServiceEntity notifyService =
NotifyServiceBuilder.createNotifyServiceBuilder(context)
.withName("service name")
.withDescription("service description")
.withUrl("https://service.ldn.org/about")
.withLdnUrl("https://service.ldn.org/inbox")
.build();
//3. a workspace item ready to go
WorkspaceItem workspaceItem =
WorkspaceItemBuilder.createWorkspaceItem(context, collection)
.withTitle("Submission Item")
.withIssueDate("2023-11-20")
.withCOARNotifyService(notifyService, "request-review")
.withFulltext("test.txt", "test", InputStream.nullInputStream())
.grantLicense()
.build();
WorkflowItem workflowItem = workflowService.start(context, workspaceItem);
Item item = workflowItem.getItem();
context.dispatchEvents();
context.restoreAuthSystemState();
LDNMessageEntity ldnMessage =
ldnMessageService.findAll(context).stream().findFirst().orElse(null);
assertThat(notifyService, matchesNotifyServiceEntity(ldnMessage.getTarget()));
assertEquals(workflowItem.getItem().getID(), ldnMessage.getObject().getID());
assertEquals(QUEUE_STATUS_QUEUED, ldnMessage.getQueueStatus());
assertNull(ldnMessage.getOrigin());
assertNotNull(ldnMessage.getMessage());
ObjectMapper mapper = new ObjectMapper();
Notification notification = mapper.readValue(ldnMessage.getMessage(), Notification.class);
// check id
assertThat(notification.getId(), containsString("urn:uuid:"));
// check object
assertEquals(notification.getObject().getId(),
configurationService.getProperty("dspace.ui.url") + "/handle/" + item.getHandle());
assertEquals(notification.getObject().getIetfCiteAs(),
itemService.getMetadataByMetadataString(item, "dc.identifier.uri").get(0).getValue());
assertEquals(notification.getObject().getUrl().getId(),
configurationService.getProperty("dspace.ui.url") + "/bitstreams/" +
item.getBundles(Constants.CONTENT_BUNDLE_NAME).get(0).getBitstreams().get(0).getID() + "/download");
// check target
assertEquals(notification.getTarget().getId(), notifyService.getUrl());
assertEquals(notification.getTarget().getInbox(), notifyService.getLdnUrl());
assertEquals(notification.getTarget().getType(), Set.of("Service"));
// check origin
assertEquals(notification.getOrigin().getId(), configurationService.getProperty("dspace.ui.url"));
assertEquals(notification.getOrigin().getInbox(), configurationService.getProperty("ldn.notify.inbox"));
assertEquals(notification.getOrigin().getType(), Set.of("Service"));
// check actor
assertEquals(notification.getActor().getId(), configurationService.getProperty("dspace.ui.url"));
assertEquals(notification.getActor().getName(), configurationService.getProperty("dspace.name"));
assertEquals(notification.getOrigin().getType(), Set.of("Service"));
// check types
assertEquals(notification.getType(), Set.of("coar-notify:ReviewAction", "Offer"));
}
@Test
public void testLDNMessageConsumerRequestEndorsement() throws Exception {
context.turnOffAuthorisationSystem();
NotifyServiceEntity notifyService =
NotifyServiceBuilder.createNotifyServiceBuilder(context)
.withName("service name")
.withDescription("service description")
.withUrl("https://service.ldn.org/about")
.withLdnUrl("https://service.ldn.org/inbox")
.build();
//3. a workspace item ready to go
WorkspaceItem workspaceItem =
WorkspaceItemBuilder.createWorkspaceItem(context, collection)
.withTitle("Submission Item")
.withIssueDate("2023-11-20")
.withCOARNotifyService(notifyService, "request-endorsement")
.withFulltext("test.txt", "test", InputStream.nullInputStream())
.grantLicense()
.build();
WorkflowItem workflowItem = workflowService.start(context, workspaceItem);
Item item = workflowItem.getItem();
context.dispatchEvents();
context.restoreAuthSystemState();
LDNMessageEntity ldnMessage =
ldnMessageService.findAll(context).stream().findFirst().orElse(null);
assertThat(notifyService, matchesNotifyServiceEntity(ldnMessage.getTarget()));
assertEquals(workflowItem.getItem().getID(), ldnMessage.getObject().getID());
assertEquals(QUEUE_STATUS_QUEUED, ldnMessage.getQueueStatus());
assertNull(ldnMessage.getOrigin());
assertNotNull(ldnMessage.getMessage());
ObjectMapper mapper = new ObjectMapper();
Notification notification = mapper.readValue(ldnMessage.getMessage(), Notification.class);
// check id
assertThat(notification.getId(), containsString("urn:uuid:"));
// check object
assertEquals(notification.getObject().getId(),
configurationService.getProperty("dspace.ui.url") + "/handle/" + item.getHandle());
assertEquals(notification.getObject().getIetfCiteAs(),
itemService.getMetadataByMetadataString(item, "dc.identifier.uri").get(0).getValue());
assertEquals(notification.getObject().getUrl().getId(),
configurationService.getProperty("dspace.ui.url") + "/bitstreams/" +
item.getBundles(Constants.CONTENT_BUNDLE_NAME).get(0).getBitstreams().get(0).getID() + "/download");
// check target
assertEquals(notification.getTarget().getId(), notifyService.getUrl());
assertEquals(notification.getTarget().getInbox(), notifyService.getLdnUrl());
assertEquals(notification.getTarget().getType(), Set.of("Service"));
// check origin
assertEquals(notification.getOrigin().getId(), configurationService.getProperty("dspace.ui.url"));
assertEquals(notification.getOrigin().getInbox(), configurationService.getProperty("ldn.notify.inbox"));
assertEquals(notification.getOrigin().getType(), Set.of("Service"));
// check actor
assertEquals(notification.getActor().getId(), configurationService.getProperty("dspace.ui.url"));
assertEquals(notification.getActor().getName(), configurationService.getProperty("dspace.name"));
assertEquals(notification.getOrigin().getType(), Set.of("Service"));
// check types
assertEquals(notification.getType(), Set.of("coar-notify:EndorsementAction", "Offer"));
}
@Test
public void testLDNMessageConsumerRequestIngest() throws Exception {
context.turnOffAuthorisationSystem();
NotifyServiceEntity notifyService =
NotifyServiceBuilder.createNotifyServiceBuilder(context)
.withName("service name")
.withDescription("service description")
.withUrl("https://service.ldn.org/about")
.withLdnUrl("https://service.ldn.org/inbox")
.build();
//3. a workspace item ready to go
WorkspaceItem workspaceItem =
WorkspaceItemBuilder.createWorkspaceItem(context, collection)
.withTitle("Submission Item")
.withIssueDate("2023-11-20")
.withCOARNotifyService(notifyService, "request-ingest")
.withFulltext("test.txt", "test", InputStream.nullInputStream())
.grantLicense()
.build();
WorkflowItem workflowItem = workflowService.start(context, workspaceItem);
Item item = workflowItem.getItem();
context.dispatchEvents();
context.restoreAuthSystemState();
LDNMessageEntity ldnMessage =
ldnMessageService.findAll(context).stream().findFirst().orElse(null);
assertThat(notifyService, matchesNotifyServiceEntity(ldnMessage.getTarget()));
assertEquals(workflowItem.getItem().getID(), ldnMessage.getObject().getID());
assertEquals(QUEUE_STATUS_QUEUED, ldnMessage.getQueueStatus());
assertNull(ldnMessage.getOrigin());
assertNotNull(ldnMessage.getMessage());
ObjectMapper mapper = new ObjectMapper();
Notification notification = mapper.readValue(ldnMessage.getMessage(), Notification.class);
// check id
assertThat(notification.getId(), containsString("urn:uuid:"));
// check object
assertEquals(notification.getObject().getId(),
configurationService.getProperty("dspace.ui.url") + "/handle/" + item.getHandle());
assertEquals(notification.getObject().getIetfCiteAs(),
itemService.getMetadataByMetadataString(item, "dc.identifier.uri").get(0).getValue());
assertEquals(notification.getObject().getUrl().getId(),
configurationService.getProperty("dspace.ui.url") + "/bitstreams/" +
item.getBundles(Constants.CONTENT_BUNDLE_NAME).get(0).getBitstreams().get(0).getID() + "/download");
// check target
assertEquals(notification.getTarget().getId(), notifyService.getUrl());
assertEquals(notification.getTarget().getInbox(), notifyService.getLdnUrl());
assertEquals(notification.getTarget().getType(), Set.of("Service"));
// check origin
assertEquals(notification.getOrigin().getId(), configurationService.getProperty("dspace.ui.url"));
assertEquals(notification.getOrigin().getInbox(), configurationService.getProperty("ldn.notify.inbox"));
assertEquals(notification.getOrigin().getType(), Set.of("Service"));
// check actor
assertEquals(notification.getActor().getId(), configurationService.getProperty("dspace.ui.url"));
assertEquals(notification.getActor().getName(), configurationService.getProperty("dspace.name"));
assertEquals(notification.getOrigin().getType(), Set.of("Service"));
// check types
assertEquals(notification.getType(), Set.of("coar-notify:IngestAction", "Offer"));
}
@Test
public void testLDNMessageConsumerRequestFake() throws Exception {
context.turnOffAuthorisationSystem();
NotifyServiceEntity notifyService =
NotifyServiceBuilder.createNotifyServiceBuilder(context)
.withName("service name")
.withDescription("service description")
.withUrl("https://service.ldn.org/about")
.withLdnUrl("https://service.ldn.org/inbox")
.build();
//3. a workspace item ready to go
WorkspaceItem workspaceItem =
WorkspaceItemBuilder.createWorkspaceItem(context, collection)
.withTitle("Submission Item")
.withIssueDate("2023-11-20")
.withCOARNotifyService(notifyService, "request-fake")
.withFulltext("test.txt", "test", InputStream.nullInputStream())
.grantLicense()
.build();
workflowService.start(context, workspaceItem);
context.dispatchEvents();
context.restoreAuthSystemState();
LDNMessageEntity ldnMessage =
ldnMessageService.findAll(context).stream().findFirst().orElse(null);
assertNull(ldnMessage);
}
@Test
public void testLDNMessageConsumerNoRequests() throws Exception {
context.turnOffAuthorisationSystem();
//3. a workspace item ready to go
WorkspaceItem workspaceItem =
WorkspaceItemBuilder.createWorkspaceItem(context, collection)
.withTitle("Submission Item")
.withIssueDate("2023-11-20")
.grantLicense()
.build();
workflowService.start(context, workspaceItem);
context.dispatchEvents();
context.restoreAuthSystemState();
LDNMessageEntity ldnMessage =
ldnMessageService.findAll(context).stream().findFirst().orElse(null);
assertNull(ldnMessage);
}
@Override
@After
public void destroy() throws Exception {
List<LDNMessageEntity> ldnMessageEntities = ldnMessageService.findAll(context);
if (CollectionUtils.isNotEmpty(ldnMessageEntities)) {
ldnMessageEntities.forEach(ldnMessage -> {
try {
ldnMessageService.delete(context, ldnMessage);
} catch (SQLException e) {
throw new RuntimeException(e);
}
});
}
super.destroy();
}
}

View File

@@ -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.matcher;
import org.dspace.app.ldn.NotifyServiceEntity;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeMatcher;
/**
* Implementation of {@link Matcher} to match a NotifyServiceEntity by all its
* attributes.
*
* @author Mohamed Eskander (mohamed.eskander at 4science.com)
*
*/
public class NotifyServiceEntityMatcher extends TypeSafeMatcher<NotifyServiceEntity> {
private final NotifyServiceEntity expectedEntity;
private NotifyServiceEntityMatcher(NotifyServiceEntity expectedEntity) {
this.expectedEntity = expectedEntity;
}
public static NotifyServiceEntityMatcher matchesNotifyServiceEntity(NotifyServiceEntity expectedEntity) {
return new NotifyServiceEntityMatcher(expectedEntity);
}
@Override
protected boolean matchesSafely(NotifyServiceEntity actualEntity) {
return actualEntity.getName().equals(expectedEntity.getName()) &&
actualEntity.getDescription().equals(expectedEntity.getDescription()) &&
actualEntity.getUrl().equals(expectedEntity.getUrl()) &&
actualEntity.getLdnUrl().equals(expectedEntity.getLdnUrl()) &&
actualEntity.getInboundPatterns() == expectedEntity.getInboundPatterns() &&
actualEntity.getOutboundPatterns() == expectedEntity.getOutboundPatterns() &&
actualEntity.isEnabled() == expectedEntity.isEnabled() &&
actualEntity.getScore() == expectedEntity.getScore();
}
@Override
public void describeTo(Description description) {
description.appendText("a Notify Service Entity with the following attributes:")
.appendText(", name ").appendValue(expectedEntity.getName())
.appendText(", description ").appendValue(expectedEntity.getDescription())
.appendText(", URL ").appendValue(expectedEntity.getUrl())
.appendText(", LDN URL ").appendValue(expectedEntity.getLdnUrl())
.appendText(", inbound patterns ").appendValue(expectedEntity.getInboundPatterns())
.appendText(", outbound patterns ").appendValue(expectedEntity.getOutboundPatterns())
.appendText(", enabled ").appendValue(expectedEntity.isEnabled())
.appendText(", score ").appendValue(expectedEntity.getScore());
}
}

View File

@@ -780,7 +780,7 @@ event.dispatcher.default.class = org.dspace.event.BasicDispatcher
# Add rdf here, if you are using dspace-rdf to export your repository content as RDF. # Add rdf here, if you are using dspace-rdf to export your repository content as RDF.
# Add iiif here, if you are using dspace-iiif. # Add iiif here, if you are using dspace-iiif.
# Add orcidqueue here, if the integration with ORCID is configured and wish to enable the synchronization queue functionality # Add orcidqueue here, if the integration with ORCID is configured and wish to enable the synchronization queue functionality
event.dispatcher.default.consumers = versioning, discovery, eperson, qaeventsdelete event.dispatcher.default.consumers = versioning, discovery, eperson, qaeventsdelete, ldnmessage
# The noindex dispatcher will not create search or browse indexes (useful for batch item imports) # The noindex dispatcher will not create search or browse indexes (useful for batch item imports)
event.dispatcher.noindex.class = org.dspace.event.BasicDispatcher event.dispatcher.noindex.class = org.dspace.event.BasicDispatcher
@@ -826,6 +826,10 @@ event.consumer.iiif.filters = Item+Modify:Item+Modify_Metadata:Item+Delete:Item+
event.consumer.orcidqueue.class = org.dspace.orcid.consumer.OrcidQueueConsumer event.consumer.orcidqueue.class = org.dspace.orcid.consumer.OrcidQueueConsumer
event.consumer.orcidqueue.filters = Item+Install|Modify|Modify_Metadata|Delete|Remove event.consumer.orcidqueue.filters = Item+Install|Modify|Modify_Metadata|Delete|Remove
# consumer to store LDN Messages
event.consumer.ldnmessage.class = org.dspace.app.ldn.LDNMessageConsumer
event.consumer.ldnmessage.filters = Item+Install
# ...set to true to enable testConsumer messages to standard output # ...set to true to enable testConsumer messages to standard output
#testConsumer.verbose = true #testConsumer.verbose = true

View File

@@ -0,0 +1,52 @@
## generate LDN message json when request-endorsement of an item
##
## Parameters: {0} config 'dspace.ui.url'
## {1} config 'ldn.notify.inbox'
## {2} config 'dspace.name'
## {3} Notify Service url
## {4} Notify Service ldnUrl
## {5} 'dspace.ui.url'/handle/xxxx/yyy
## {6} metadata value of 'dc.identifier.uri'
## {7} the url to the primary bitstream or the first bitstream in the ORIGINAL bundle if there is no primary bitstream. The url is 'dspace.ui.url'/bitstreams/:uuid/download
## {8} the bitstream MimeType or get User Format MimeType if getFormat is 'Unknown'
## {9} id of the created LDNMessage
{
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://purl.org/coar/notify"
],
"actor": {
"id": "${params[0]}",
"name": "${params[2]}",
"type": "Service"
},
"id": "${params[9]}",
"object": {
"id": "${params[5]}",
"ietf:cite-as": "${params[6]}",
"type": "sorg:AboutPage",
"url": {
"id": "${params[7]}",
"mediaType": "${params[8]}",
"type": [
"Article",
"sorg:ScholarlyArticle"
]
}
},
"origin": {
"id": "${params[0]}",
"inbox": "${params[1]}",
"type": "Service"
},
"target": {
"id": "${params[3]}",
"inbox": "${params[4]}",
"type": "Service"
},
"type": [
"Offer",
"coar-notify:EndorsementAction"
]
}

View File

@@ -0,0 +1,52 @@
## generate LDN message json when request-ingest of an item
##
## Parameters: {0} config 'dspace.ui.url'
## {1} config 'ldn.notify.inbox'
## {2} config 'dspace.name'
## {3} Notify Service url
## {4} Notify Service ldnUrl
## {5} 'dspace.ui.url'/handle/xxxx/yyy
## {6} metadata value of 'dc.identifier.uri'
## {7} the url to the primary bitstream or the first bitstream in the ORIGINAL bundle if there is no primary bitstream. The url is 'dspace.ui.url'/bitstreams/:uuid/download
## {8} the bitstream MimeType or get User Format MimeType if getFormat is 'Unknown'
## {9} id of the created LDNMessage
{
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://purl.org/coar/notify"
],
"actor": {
"id": "${params[0]}",
"name": "${params[2]}",
"type": "Service"
},
"id": "${params[9]}",
"object": {
"id": "${params[5]}",
"ietf:cite-as": "${params[6]}",
"type": "sorg:AboutPage",
"url": {
"id": "${params[7]}",
"mediaType": "${params[8]}",
"type": [
"Article",
"sorg:ScholarlyArticle"
]
}
},
"origin": {
"id": "${params[0]}",
"inbox": "${params[1]}",
"type": "Service"
},
"target": {
"id": "${params[3]}",
"inbox": "${params[4]}",
"type": "Service"
},
"type": [
"Offer",
"coar-notify:IngestAction"
]
}

View File

@@ -0,0 +1,52 @@
## generate LDN message json when request-review of an item
##
## Parameters: {0} config 'dspace.ui.url'
## {1} config 'ldn.notify.inbox'
## {2} config 'dspace.name'
## {3} Notify Service url
## {4} Notify Service ldnUrl
## {5} 'dspace.ui.url'/handle/xxxx/yyy
## {6} metadata value of 'dc.identifier.uri'
## {7} the url to the primary bitstream or the first bitstream in the ORIGINAL bundle if there is no primary bitstream. The url is 'dspace.ui.url'/bitstreams/:uuid/download
## {8} the bitstream MimeType or get User Format MimeType if getFormat is 'Unknown'
## {9} id of the created LDNMessage
{
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://purl.org/coar/notify"
],
"actor": {
"id": "${params[0]}",
"name": "${params[2]}",
"type": "Service"
},
"id": "${params[9]}",
"object": {
"id": "${params[5]}",
"ietf:cite-as": "${params[6]}",
"type": "sorg:AboutPage",
"url": {
"id": "${params[7]}",
"mediaType": "${params[8]}",
"type": [
"Article",
"sorg:ScholarlyArticle"
]
}
},
"origin": {
"id": "${params[0]}",
"inbox": "${params[1]}",
"type": "Service"
},
"target": {
"id": "${params[3]}",
"inbox": "${params[4]}",
"type": "Service"
},
"type": [
"Offer",
"coar-notify:ReviewAction"
]
}

View File

@@ -25,7 +25,7 @@
<bean class="org.dspace.app.ldn.LDNBusinessDelegate"></bean> <bean class="org.dspace.app.ldn.LDNBusinessDelegate"></bean>
<bean name="ldnRouter" class="org.dspace.app.ldn.LDNRouter"> <bean name="ldnRouter" class="org.dspace.app.ldn.LDNRouter">
<property name="processors"> <property name="incomingProcessors">
<map key-type="java.util.Set" value-type="org.dspace.app.ldn.LDNProcessor"> <map key-type="java.util.Set" value-type="org.dspace.app.ldn.LDNProcessor">
<entry> <entry>
<key> <key>
@@ -74,6 +74,37 @@
</entry> </entry>
</map> </map>
</property> </property>
<property name="outcomingProcessors">
<map key-type="java.util.Set" value-type="org.dspace.app.ldn.LDNProcessor">
<entry>
<key>
<set>
<value>Announce</value>
<value>coar-notify:ReviewAction</value>
</set>
</key>
<ref bean="outcomingAnnounceReviewAction" />
</entry>
<entry>
<key>
<set>
<value>Announce</value>
<value>coar-notify:EndorsementAction</value>
</set>
</key>
<ref bean="outcomingAnnounceEndorsementAction" />
</entry>
<entry>
<key>
<set>
<value>Announce</value>
<value>coar-notify:IngestAction</value>
</set>
</key>
<ref bean="outcomingAnnounceIngestAction" />
</entry>
</map>
</property>
</bean> </bean>
<bean name="announceReviewAction" class="org.dspace.app.ldn.processor.LDNMetadataProcessor"> <bean name="announceReviewAction" class="org.dspace.app.ldn.processor.LDNMetadataProcessor">
@@ -269,4 +300,40 @@
</property> </property>
</bean> </bean>
<bean name="outcomingAnnounceReviewAction" class="org.dspace.app.ldn.processor.LDNMetadataProcessor">
<property name="actions">
<list value-type="org.dspace.app.ldn.action.LDNAction">
<!-- <bean class="org.dspace.app.ldn.action.LDNEmailAction">-->
<!-- <property name="actionSendFilter" value="william_welling@harvard.edu" />-->
<!-- <property name="actionSendEmailTextFile" value="coar_notify_reviewed" />-->
<!-- </bean>-->
<bean class="org.dspace.app.ldn.action.SendLDNMessageAction"/>
</list>
</property>
</bean>
<bean name="outcomingAnnounceEndorsementAction" class="org.dspace.app.ldn.processor.LDNMetadataProcessor">
<property name="actions">
<list value-type="org.dspace.app.ldn.action.LDNAction">
<!-- <bean class="org.dspace.app.ldn.action.LDNEmailAction">-->
<!-- <property name="actionSendFilter" value="william_welling@harvard.edu" />-->
<!-- <property name="actionSendEmailTextFile" value="coar_notify_reviewed" />-->
<!-- </bean>-->
<bean class="org.dspace.app.ldn.action.SendLDNMessageAction"/>
</list>
</property>
</bean>
<bean name="outcomingAnnounceIngestAction" class="org.dspace.app.ldn.processor.LDNMetadataProcessor">
<property name="actions">
<list value-type="org.dspace.app.ldn.action.LDNAction">
<!-- <bean class="org.dspace.app.ldn.action.LDNEmailAction">-->
<!-- <property name="actionSendFilter" value="william_welling@harvard.edu" />-->
<!-- <property name="actionSendEmailTextFile" value="coar_notify_reviewed" />-->
<!-- </bean>-->
<bean class="org.dspace.app.ldn.action.SendLDNMessageAction"/>
</list>
</property>
</bean>
</beans> </beans>