Skip to content

Commit 474becc

Browse files
authored
Merge pull request DSpace#10090 from amgciadev/fix-10053-b
DSpace#10053: Implement support for PCI Endorsement workflow in COAR Notify
2 parents 774cc0a + cf7ce5b commit 474becc

36 files changed

+672
-42
lines changed

dspace-api/src/main/java/org/dspace/app/ldn/LDNMessageConsumer.java

Lines changed: 95 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
import java.util.UUID;
2222

2323
import com.fasterxml.jackson.core.JsonProcessingException;
24-
import com.fasterxml.jackson.databind.JsonMappingException;
2524
import com.fasterxml.jackson.databind.ObjectMapper;
2625
import org.apache.commons.collections4.CollectionUtils;
2726
import org.apache.commons.lang3.StringUtils;
@@ -48,6 +47,10 @@
4847
import org.dspace.services.ConfigurationService;
4948
import org.dspace.services.factory.DSpaceServicesFactory;
5049
import org.dspace.utils.DSpace;
50+
import org.dspace.versioning.Version;
51+
import org.dspace.versioning.VersionHistory;
52+
import org.dspace.versioning.factory.VersionServiceFactory;
53+
import org.dspace.versioning.service.VersionHistoryService;
5154
import org.dspace.web.ContextUtil;
5255

5356
/**
@@ -63,6 +66,9 @@ public class LDNMessageConsumer implements Consumer {
6366
private ConfigurationService configurationService;
6467
private ItemService itemService;
6568
private BitstreamService bitstreamService;
69+
private final String RESUBMISSION_SUFFIX = "-resubmission";
70+
private final String ENDORSEMENT_PATTERN = "request-endorsement";
71+
private final String REVIEW_PATTERN = "request-review";
6672

6773
@Override
6874
public void initialize() throws Exception {
@@ -83,17 +89,34 @@ public void consume(Context context, Event event) throws Exception {
8389
}
8490

8591
Item item = (Item) event.getSubject(context);
92+
if (item == null) {
93+
return;
94+
}
8695
createManualLDNMessages(context, item);
8796
createAutomaticLDNMessages(context, item);
8897
}
8998

9099
private void createManualLDNMessages(Context context, Item item) throws SQLException, JsonProcessingException {
91100
List<NotifyPatternToTrigger> patternsToTrigger =
92101
notifyPatternToTriggerService.findByItem(context, item);
102+
// Note that multiple patterns can be submitted and not all support resubmission
103+
// 1. Extract all patterns that accept resubmissions, i.e. endorsement and review
104+
List<Integer> patternsSupportingResubmission = patternsToTrigger.stream()
105+
.filter(p -> p.getPattern().equals(REVIEW_PATTERN) || p.getPattern().equals(ENDORSEMENT_PATTERN))
106+
.map(NotifyPatternToTrigger::getID).toList();
107+
108+
String resubmissionReplyToID = null;
93109

94110
for (NotifyPatternToTrigger patternToTrigger : patternsToTrigger) {
111+
// Only try to fetch resubmission ID if the pattern support resubmission
112+
if (patternsSupportingResubmission.contains(patternToTrigger.getID())) {
113+
resubmissionReplyToID = findResubmissionReplyToUUID(context, item, patternToTrigger.getNotifyService());
114+
}
115+
95116
createLDNMessage(context,patternToTrigger.getItem(),
96-
patternToTrigger.getNotifyService(), patternToTrigger.getPattern());
117+
patternToTrigger.getNotifyService(),
118+
patternToTrigger.getPattern(),
119+
resubmissionReplyToID);
97120
}
98121
}
99122

@@ -104,9 +127,31 @@ private void createAutomaticLDNMessages(Context context, Item item) throws SQLEx
104127
for (NotifyServiceInboundPattern inboundPattern : inboundPatterns) {
105128
if (StringUtils.isEmpty(inboundPattern.getConstraint()) ||
106129
evaluateFilter(context, item, inboundPattern.getConstraint())) {
107-
createLDNMessage(context, item, inboundPattern.getNotifyService(), inboundPattern.getPattern());
130+
createLDNMessage(context, item, inboundPattern.getNotifyService(),
131+
inboundPattern.getPattern(), null);
132+
}
133+
}
134+
}
135+
136+
private String findResubmissionReplyToUUID(Context context, Item item, NotifyServiceEntity service)
137+
throws SQLException {
138+
// 1.1 Check whether this is a new version submission
139+
VersionHistoryService versionHistoryService = VersionServiceFactory.getInstance()
140+
.getVersionHistoryService();
141+
VersionHistory versionHistory = versionHistoryService.findByItem(context, item);
142+
143+
if (versionHistory != null) {
144+
Version currentVersion = versionHistoryService.getVersion(context, versionHistory, item);
145+
Version previousVersion = versionHistoryService.getPrevious(context, versionHistory, currentVersion);
146+
if (previousVersion != null) {
147+
// 1.2 and a TentativeReject notification, matching the current pattern's service, was received for the
148+
// previous item version
149+
return ldnMessageService.findEndorsementOrReviewResubmissionIdByItem(context,
150+
previousVersion.getItem(), service);
108151
}
109152
}
153+
// New submission (new item, or previous version with a tentativeReject notification not found)
154+
return null;
110155
}
111156

112157
private boolean evaluateFilter(Context context, Item item, String constraint) {
@@ -116,19 +161,37 @@ private boolean evaluateFilter(Context context, Item item, String constraint) {
116161
return filter != null && filter.getResult(context, item);
117162
}
118163

119-
private void createLDNMessage(Context context, Item item, NotifyServiceEntity service, String pattern)
120-
throws SQLException, JsonMappingException, JsonProcessingException {
121-
122-
LDN ldn = getLDNMessage(pattern);
164+
private void createLDNMessage(Context context, Item item, NotifyServiceEntity service, String pattern,
165+
String resubmissionID)
166+
throws SQLException, JsonProcessingException {
167+
// Amend current pattern name to trigger
168+
// Endorsement or Review offer resubmissions: append '-resubmission' to pattern name to choose the correct
169+
// LDN message template: e.g. request-endorsement-resubmission or request-review-resubmission
170+
LDN ldn = (resubmissionID != null)
171+
? getLDNMessage(pattern + RESUBMISSION_SUFFIX) : getLDNMessage(pattern);
123172
LDNMessageEntity ldnMessage =
124-
ldnMessageService.create(context, format("urn:uuid:%s", UUID.randomUUID()));
173+
ldnMessageService.create(context, format("urn:uuid:%s", UUID.randomUUID()));
125174

126175
ldnMessage.setObject(item);
127176
ldnMessage.setTarget(service);
128177
ldnMessage.setQueueStatus(LDNMessageEntity.QUEUE_STATUS_QUEUED);
129178
ldnMessage.setQueueTimeout(Instant.now());
130179

131-
appendGeneratedMessage(ldn, ldnMessage, pattern);
180+
String actorID = null;
181+
if (service.isUsesActorEmailId()) {
182+
// If the service has been configured to use actorEmailId, we use the submitter's email and name
183+
if (item.getSubmitter() != null) {
184+
actorID = item.getSubmitter().getEmail();
185+
} else {
186+
// Use configured fallback email (defaults to mail.admin property)
187+
actorID = configurationService.getProperty("ldn.notification.email.submitter.fallback");
188+
}
189+
}
190+
appendGeneratedMessage(ldn,
191+
ldnMessage,
192+
actorID,
193+
(actorID != null && item.getSubmitter() != null) ? item.getSubmitter().getFullName() : null,
194+
resubmissionID);
132195

133196
ObjectMapper mapper = new ObjectMapper();
134197
Notification notification = mapper.readValue(ldnMessage.getMessage(), Notification.class);
@@ -139,6 +202,10 @@ private void createLDNMessage(Context context, Item item, NotifyServiceEntity se
139202
Collections.sort(notificationTypeArrayList);
140203
ldnMessage.setActivityStreamType(notificationTypeArrayList.get(0));
141204
ldnMessage.setCoarNotifyType(notificationTypeArrayList.get(1));
205+
// If a resubmission, set inReplyTo
206+
if (resubmissionID != null) {
207+
ldnMessage.setInReplyTo(ldnMessageService.find(context, resubmissionID));
208+
}
142209

143210
ldnMessageService.update(context, ldnMessage);
144211
}
@@ -151,11 +218,16 @@ private LDN getLDNMessage(String pattern) {
151218
}
152219
}
153220

154-
private void appendGeneratedMessage(LDN ldn, LDNMessageEntity ldnMessage, String pattern) {
221+
private void appendGeneratedMessage(LDN ldn, LDNMessageEntity ldnMessage, String actorID, String actorName,
222+
String resubmissionId) {
155223
Item item = (Item) ldnMessage.getObject();
156-
ldn.addArgument(getUiUrl());
224+
if (actorID != null) {
225+
ldn.addArgument("mailto:" + actorID);
226+
} else {
227+
ldn.addArgument(getUiUrl());
228+
}
157229
ldn.addArgument(configurationService.getProperty("ldn.notify.inbox"));
158-
ldn.addArgument(configurationService.getProperty("dspace.name"));
230+
ldn.addArgument(actorName != null ? actorName : configurationService.getProperty("dspace.name"));
159231
ldn.addArgument(Objects.requireNonNullElse(ldnMessage.getTarget().getUrl(), ""));
160232
ldn.addArgument(Objects.requireNonNullElse(ldnMessage.getTarget().getLdnUrl(), ""));
161233
ldn.addArgument(getUiUrl() + "/handle/" + ldnMessage.getObject().getHandle());
@@ -166,6 +238,17 @@ private void appendGeneratedMessage(LDN ldn, LDNMessageEntity ldnMessage, String
166238
ldn.addArgument(getRelationUri(item));
167239
ldn.addArgument("http://purl.org/vocab/frbr/core#supplement");
168240
ldn.addArgument(format("urn:uuid:%s", UUID.randomUUID()));
241+
if (actorID != null) {
242+
ldn.addArgument("Person");
243+
} else {
244+
ldn.addArgument("Service");
245+
}
246+
// Param 14: UI URL, LDN message origin
247+
ldn.addArgument(getUiUrl());
248+
// Param 15: inReplyTo ID, used in endorsement resubmission notifications
249+
if (resubmissionId != null) {
250+
ldn.addArgument(String.format("\"inReplyTo\": \"%s\",", resubmissionId));
251+
}
169252

170253
ldnMessage.setMessage(ldn.generateLDNMessage());
171254
}

dspace-api/src/main/java/org/dspace/app/ldn/LDNMessageEntity.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
import jakarta.persistence.Table;
1919
import org.dspace.content.DSpaceObject;
2020
import org.dspace.core.ReloadableEntity;
21+
import org.dspace.services.ConfigurationService;
22+
import org.dspace.services.factory.DSpaceServicesFactory;
2123

2224
/**
2325
* Class representing ldnMessages stored in the DSpace system and, when locally resolvable,
@@ -285,7 +287,11 @@ public String toString() {
285287
}
286288

287289
public static String getNotificationType(LDNMessageEntity ldnMessage) {
288-
if (ldnMessage.getInReplyTo() != null || ldnMessage.getOrigin() != null) {
290+
// Resubmission outgoing notifications have the inReplyTo, therefore it cannot be used to determine
291+
// whether a notification is incoming
292+
ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService();
293+
if (ldnMessage.getOrigin() != null && !ldnMessage.getOrigin().getLdnUrl()
294+
.contains(configurationService.getProperty("dspace.ui.url"))) {
289295
return TYPE_INCOMING;
290296
}
291297
return TYPE_OUTGOING;

dspace-api/src/main/java/org/dspace/app/ldn/NotifyServiceEntity.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ public class NotifyServiceEntity implements ReloadableEntity<Integer> {
5454
@Column(name = "enabled")
5555
private boolean enabled = false;
5656

57+
@Column(name = "uses_actor_email_id")
58+
private boolean usesActorEmailId = false;
59+
5760
@Column(name = "score")
5861
private BigDecimal score;
5962

@@ -129,6 +132,14 @@ public void setEnabled(boolean enabled) {
129132
this.enabled = enabled;
130133
}
131134

135+
public boolean isUsesActorEmailId() {
136+
return usesActorEmailId;
137+
}
138+
139+
public void setUsesActorEmailId(boolean usesActorEmailId) {
140+
this.usesActorEmailId = usesActorEmailId;
141+
}
142+
132143
public BigDecimal getScore() {
133144
return score;
134145
}

dspace-api/src/main/java/org/dspace/app/ldn/action/LDNEmailAction.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,13 @@ private List<String> retrieveRecipientsEmail(Item item) {
136136
List<String> recipients = new LinkedList<String>();
137137

138138
if (actionSendFilter.startsWith("SUBMITTER")) {
139-
recipients.add(item.getSubmitter().getEmail());
139+
if (item.getSubmitter() != null) {
140+
recipients.add(item.getSubmitter().getEmail());
141+
} else {
142+
// Fallback configured option
143+
recipients.add(configurationService.getProperty("ldn.notification.email.submitter.fallback"));
144+
}
145+
140146
} else if (actionSendFilter.startsWith("GROUP:")) {
141147
String groupName = actionSendFilter.replace("GROUP:", "");
142148
String property = format("email.%s.list", groupName);

dspace-api/src/main/java/org/dspace/app/ldn/dao/impl/LDNMessageDaoImpl.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,8 +149,11 @@ public List<LDNMessageEntity> findAllMessagesByItem(
149149
Predicate activityPredicate = null;
150150
andPredicates.add(
151151
criteriaBuilder.equal(root.get(LDNMessageEntity_.queueStatus), LDNMessageEntity.QUEUE_STATUS_PROCESSED));
152+
// Added OR with object or context - inbound notifications make use of the context item to provide information
153+
// about the repository item the notification refers to
152154
andPredicates.add(
153-
criteriaBuilder.equal(root.get(LDNMessageEntity_.object), item));
155+
criteriaBuilder.or(criteriaBuilder.equal(root.get(LDNMessageEntity_.object), item),
156+
criteriaBuilder.equal(root.get(LDNMessageEntity_.context), item)));
154157
if (activities != null && activities.length > 0) {
155158
activityPredicate = root.get(LDNMessageEntity_.activityStreamType).in(activities);
156159
andPredicates.add(activityPredicate);

dspace-api/src/main/java/org/dspace/app/ldn/model/NotifyRequestStatusEnum.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,12 @@
88
package org.dspace.app.ldn.model;
99
/**
1010
* REQUESTED means acknowledgements not received yet
11-
* ACCEPTED means acknowledgements of "Accept" type received
12-
* REJECTED means ack of "TentativeReject" type received
11+
* ACCEPTED means acknowledgements of "Accept" or "TentativeAccept" type received
12+
* REJECTED means ack of "Reject" type received
13+
* TENTATIVE_REJECT means ack of "TentativeReject" type received
1314
*
1415
* @author Francesco Bacchelli (francesco.bacchelli at 4science.com)
1516
*/
1617
public enum NotifyRequestStatusEnum {
17-
REJECTED, ACCEPTED, REQUESTED
18+
REJECTED, ACCEPTED, REQUESTED, TENTATIVE_REJECT
1819
}

dspace-api/src/main/java/org/dspace/app/ldn/processor/LDNMetadataProcessor.java

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import org.apache.http.client.HttpResponseException;
2121
import org.apache.logging.log4j.LogManager;
2222
import org.apache.logging.log4j.Logger;
23+
import org.dspace.app.ldn.LDNMessageEntity;
2324
import org.dspace.app.ldn.action.LDNAction;
2425
import org.dspace.app.ldn.action.LDNActionStatus;
2526
import org.dspace.app.ldn.model.Notification;
@@ -59,6 +60,8 @@ public class LDNMetadataProcessor implements LDNProcessor {
5960
"Announce",
6061
"TentativeReject",
6162
"Accept",
63+
"TentativeAccept",
64+
"Reject",
6265
"coar-notify:ReviewAction",
6366
"coar-notify:IngestAction",
6467
"coar-notify:EndorsementAction");
@@ -168,7 +171,22 @@ private Item lookupItem(Context context, Notification notification) throws SQLEx
168171
String url = null;
169172

170173
if (CONTEXT_ID_ITEM_TYPES.containsAll(notification.getType())) {
171-
url = notification.getContext().getId();
174+
if (notification.getContext() != null) {
175+
url = notification.getContext().getId();
176+
} else if (notification.getInReplyTo() != null) {
177+
// Find context information (the item this notification relates to) via the inReplyTo notification ID
178+
LDNMessageEntity inReplyToldnMessageEntity =
179+
ldnMessageService.find(context, notification.getInReplyTo());
180+
if (inReplyToldnMessageEntity != null) {
181+
String dspaceUrl = configurationService.getProperty("dspace.ui.url")
182+
+ "/handle/";
183+
url = dspaceUrl + inReplyToldnMessageEntity.getObject().getHandle();
184+
// Set context based on the inReplyTo and update in DB
185+
LDNMessageEntity ldnMessageEntity = ldnMessageService.find(context, notification.getId());
186+
ldnMessageEntity.setContext(inReplyToldnMessageEntity.getObject());
187+
ldnMessageService.update(context, ldnMessageEntity);
188+
}
189+
}
172190
} else if (OBJECT_ID_ITEM_TYPES.containsAll(notification.getType())) {
173191
url = notification.getObject().getId();
174192
} else if (OBJECT_SUBJECT_ITEM_TYPES.containsAll(notification.getType())) {

dspace-api/src/main/java/org/dspace/app/ldn/service/LDNMessageService.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,17 @@ public interface LDNMessageService {
130130
*/
131131
public NotifyRequestStatus findRequestsByItem(Context context, Item item) throws SQLException;
132132

133+
/**
134+
* find the UUID of a previous tentativeReject notification associated with a new resubmission (Endorsement or
135+
* Review patterns only)
136+
*
137+
* @param context the context
138+
* @param item the previousVersion item associated with a potential resubmission
139+
* @return the UUID of a previous tentativeReject notification associated with a potential resubmission if found
140+
*/
141+
public String findEndorsementOrReviewResubmissionIdByItem(Context context, Item item, NotifyServiceEntity service)
142+
throws SQLException;
143+
133144
/**
134145
* delete the provided ldn message
135146
*

0 commit comments

Comments
 (0)