Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IQSS/8914 COAR compliant LDN messaging #10490

Draft
wants to merge 22 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
a9f72f2
HDC 3b 1.5 initial update
qqmyers Aug 11, 2022
63aeed1
add workflow settings to main list per qa
qqmyers Aug 11, 2022
a410659
start adding default bestID
qqmyers Aug 11, 2022
8b69c0f
replace system.out.println call
qqmyers Aug 11, 2022
cbd20fa
Improve default compound field handling and some reformatting
qqmyers Aug 11, 2022
94d40e6
add template custom instructions info
qqmyers Aug 11, 2022
aa88182
get blocks from metadataroot
qqmyers Aug 12, 2022
c9c7c91
Merge remote-tracking branch 'IQSS/develop' into GDCC/8914-COAR-compl…
qqmyers Aug 12, 2022
62fe214
fix parsing to match spec/dash msg
qqmyers Sep 1, 2022
e5228af
use getString for 'Relationship', revert other changes
qqmyers Sep 1, 2022
fb56d04
temporarily drop name/type in display
qqmyers Sep 1, 2022
8ecb8e4
misplaced }
qqmyers Sep 1, 2022
789b3c9
Add callback loop and make display tolerant wrt name/type being found
qqmyers Sep 6, 2022
d06c5dc
Merge remote-tracking branch 'IQSS/develop' into GDCC/8914-COAR-compl…
qqmyers Sep 6, 2022
d84ae12
Merge remote-tracking branch 'IQSS/develop' into GDCC/8914-COAR-compl…
qqmyers Sep 23, 2022
37ebf7d
add fields missing in https://notify.coar-repositories.org/scenarios/10/
qqmyers Sep 23, 2022
ceb3ff5
debugging
qqmyers Oct 26, 2022
62888e2
added logging/switched to info for HDC debugging
qqmyers Oct 27, 2022
6c46571
Merge remote-tracking branch 'IQSS/develop' into
qqmyers Apr 12, 2024
1a28089
Merge remote-tracking branch 'IQSS/develop' into GDCC/8914-COAR-compl…
qqmyers Apr 23, 2024
d714a1a
merge issues
qqmyers Apr 23, 2024
0b967a9
Merge remote-tracking branch 'IQSS/develop' into GDCC/8914-COAR-compl…
qqmyers Oct 2, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/main/java/edu/harvard/iq/dataverse/MailServiceBean.java
Original file line number Diff line number Diff line change
Expand Up @@ -720,9 +720,9 @@ public String getMessageTextBasedOnNotification(UserNotification userNotificatio
Object[] paramArrayDatasetMentioned = {
userNotification.getUser().getName(),
BrandingUtil.getInstallationBrandName(),
citingResource.getString("@type"),
citingResource.getString("@type", "External Resource"),
citingResource.getString("@id"),
citingResource.getString("name"),
citingResource.getString("name", citingResource.getString("@id")),
citingResource.getString("relationship"),
systemConfig.getDataverseSiteUrl(),
dataset.getGlobalId().toString(),
Expand Down
198 changes: 121 additions & 77 deletions src/main/java/edu/harvard/iq/dataverse/api/LDNInbox.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,25 +13,24 @@
import edu.harvard.iq.dataverse.authorization.groups.impl.ipaddress.ip.IpAddress;
import edu.harvard.iq.dataverse.engine.command.DataverseRequest;
import edu.harvard.iq.dataverse.pidproviders.PidProvider;
import edu.harvard.iq.dataverse.pidproviders.doi.AbstractDOIProvider;
import edu.harvard.iq.dataverse.pidproviders.handle.HandlePidProvider;
import edu.harvard.iq.dataverse.settings.SettingsServiceBean;
import edu.harvard.iq.dataverse.util.json.JSONLDUtil;
import edu.harvard.iq.dataverse.util.json.JsonLDNamespace;
import edu.harvard.iq.dataverse.util.json.JsonLDTerm;
import edu.harvard.iq.dataverse.util.json.JsonUtil;

import java.util.Date;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.io.StringWriter;
import java.net.URI;
import java.sql.Timestamp;
import java.util.logging.Logger;

import jakarta.ejb.EJB;
import jakarta.json.Json;
import jakarta.json.JsonObject;
import jakarta.json.JsonValue;
import jakarta.json.JsonObjectBuilder;
import jakarta.json.JsonWriter;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.ws.rs.BadRequestException;
Expand All @@ -43,6 +42,12 @@
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.Response;

import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;

@Path("inbox")
public class LDNInbox extends AbstractApiBean {

Expand Down Expand Up @@ -72,12 +77,11 @@ public class LDNInbox extends AbstractApiBean {
@Path("/")
@Consumes("application/ld+json, application/json-ld")
public Response acceptMessage(String body) {
try {
IpAddress origin = new DataverseRequest(null, httpRequest).getSourceAddress();
String whitelist = settingsService.get(SettingsServiceBean.Key.LDNMessageHosts.toString(), "");
// Only do something if we listen to this host
if (whitelist.equals("*") || whitelist.contains(origin.toString())) {
String citingPID = null;
String citingType = null;
boolean sent = false;

JsonObject jsonld = null;
Expand All @@ -101,98 +105,138 @@ public Response acceptMessage(String body) {
if (jsonld == null) {
throw new BadRequestException("Could not parse message to find acceptable citation link to a dataset.");
}
String relationship = "isRelatedTo";
//To Do - lower level for PR
logger.info(JsonUtil.prettyPrint(jsonld));
//String relationship = "isRelatedTo";
String name = null;
String itemType = null;
JsonLDNamespace activityStreams = JsonLDNamespace.defineNamespace("as",
"https://www.w3.org/ns/activitystreams#");
JsonLDNamespace ietf = JsonLDNamespace.defineNamespace("ietf", "http://www.iana.org/assignments/relation/");
//JsonLDNamespace ietf = JsonLDNamespace.defineNamespace("ietf", "http://www.iana.org/assignments/relation/");
String objectKey = new JsonLDTerm(activityStreams, "object").getUrl();
if (jsonld.containsKey(objectKey)) {
JsonObject msgObject = jsonld.getJsonObject(objectKey);

citingPID = msgObject.getJsonObject(new JsonLDTerm(ietf, "cite-as").getUrl()).getString("@id");
logger.fine("Citing PID: " + citingPID);
if (msgObject.containsKey("@type")) {
citingType = msgObject.getString("@type");
if (citingType.startsWith(JsonLDNamespace.schema.getUrl())) {
citingType = citingType.replace(JsonLDNamespace.schema.getUrl(), "");
}
if (msgObject.containsKey(JsonLDTerm.schemaOrg("name").getUrl())) {
name = msgObject.getString(JsonLDTerm.schemaOrg("name").getUrl());
if (new JsonLDTerm(activityStreams, "Relationship").getUrl().equals(msgObject.getString("@type"))) {
// We have a relationship message - need to get the subject and object and
// relationship type
String subjectId = msgObject.getJsonObject(new JsonLDTerm(activityStreams, "subject").getUrl()).getString("@id");
String objectId = msgObject.getJsonObject(new JsonLDTerm(activityStreams, "object").getUrl()).getString("@id");
// Best-effort to get name by following redirects and looing for a 'name' field in the returned json
try (CloseableHttpClient client = HttpClients.createDefault()) {
logger.info("Getting " + subjectId);
HttpGet get = new HttpGet(new URI(subjectId));
get.addHeader("Accept", "application/json");

int statusCode=0;
do {
CloseableHttpResponse response = client.execute(get);
statusCode = response.getStatusLine().getStatusCode();
switch (statusCode) {
case 302:
case 303:
String location=response.getFirstHeader("location").getValue();
logger.info("Redirecting to: " + location);
get = new HttpGet(location);
get.addHeader("Accept", "application/json");

break;
case 200:
String responseString = EntityUtils.toString(response.getEntity(), "UTF-8");
logger.info("Received: " + responseString);
JsonObject job = JsonUtil.getJsonObject(responseString);
name = job.getString("name", null);
itemType = job.getString("type", null);
break;
default:
logger.info("Received " + statusCode + " when accessing " + subjectId);
}
} while(statusCode == 302);
} catch (Exception e) {
logger.info("Unable to get name from " + subjectId);
logger.info(e.getLocalizedMessage());
}
logger.fine("Citing Type: " + citingType);
String contextKey = new JsonLDTerm(activityStreams, "context").getUrl();

if (jsonld.containsKey(contextKey)) {
JsonObject context = jsonld.getJsonObject(contextKey);
for (Map.Entry<String, JsonValue> entry : context.entrySet()) {

relationship = entry.getKey().replace("_:", "");
// Assuming only one for now - should check for array and loop
JsonObject citedResource = (JsonObject) entry.getValue();
String pid = citedResource.getJsonObject(new JsonLDTerm(ietf, "cite-as").getUrl())
.getString("@id");
if (citedResource.getString("@type").equals(JsonLDTerm.schemaOrg("Dataset").getUrl())) {
logger.fine("Raw PID: " + pid);
if (pid.startsWith(AbstractDOIProvider.DOI_RESOLVER_URL)) {
pid = pid.replace(AbstractDOIProvider.DOI_RESOLVER_URL, AbstractDOIProvider.DOI_PROTOCOL + ":");
} else if (pid.startsWith(HandlePidProvider.HDL_RESOLVER_URL)) {
pid = pid.replace(HandlePidProvider.HDL_RESOLVER_URL, HandlePidProvider.HDL_PROTOCOL + ":");
String relationshipId = msgObject
.getJsonObject(new JsonLDTerm(activityStreams, "relationship").getUrl()).getString("@id");
if (subjectId != null && objectId != null && relationshipId != null) {
// Strip the URL part from a relationship ID/URL assuming a usable label exists
// after a # char. Default is to use the whole URI.
int index = relationshipId.indexOf("#");
logger.info("Found # at " + index + " in " + relationshipId);
String relationship = relationshipId.substring(index + 1);
// Parse the URI as a PID and see if this Dataverse instance has this dataset
Optional<GlobalId> id = PidProvider.parse(objectId);
if (id.isPresent()) {
//ToDo - avoid reparsing GlobalId by making a findByGlobalId(GlobalId) method?
Dataset dataset = datasetSvc.findByGlobalId(objectId);
if (dataset != null) {
JsonObjectBuilder citingResourceBuilder = Json.createObjectBuilder().add("@id", subjectId)
.add("relationship", relationship);
if(name!=null && !name.isBlank()) {
citingResourceBuilder.add("name",name);
}
if(itemType!=null && !itemType.isBlank()) {
citingResourceBuilder.add("@type",itemType.substring(0,1).toUpperCase() + itemType.substring(1));
}
logger.fine("Protocol PID: " + pid);
Optional<GlobalId> id = PidProvider.parse(pid);
Dataset dataset = datasetSvc.findByGlobalId(pid);
if (dataset != null) {
JsonObject citingResource = Json.createObjectBuilder().add("@id", citingPID)
.add("@type", citingType).add("relationship", relationship)
.add("name", name).build();
StringWriter sw = new StringWriter(128);
try (JsonWriter jw = Json.createWriter(sw)) {
jw.write(citingResource);
}
String jsonstring = sw.toString();
Set<RoleAssignment> ras = roleService.rolesAssignments(dataset);

roleService.rolesAssignments(dataset).stream()
.filter(ra -> ra.getRole().permissions()
.contains(Permission.PublishDataset))
.flatMap(
ra -> roleAssigneeService
.getExplicitUsers(roleAssigneeService
.getRoleAssignee(ra.getAssigneeIdentifier()))
.stream())
.distinct() // prevent double-send
.forEach(au -> {

if (au.isSuperuser()) {
userNotificationService.sendNotification(au,
new Timestamp(new Date().getTime()),
UserNotification.Type.DATASETMENTIONED, dataset.getId(),
null, null, true, jsonstring);

}
});
sent = true;
JsonObject citingResource = citingResourceBuilder.build();
StringWriter sw = new StringWriter(128);
try (JsonWriter jw = Json.createWriter(sw)) {
jw.write(citingResource);
}
String jsonstring = sw.toString();
logger.info("Storing: " + jsonstring);
//Set<RoleAssignment> ras = roleService.rolesAssignments(dataset);

roleService.rolesAssignments(dataset).stream()
.filter(ra -> ra.getRole().permissions().contains(Permission.PublishDataset))
.flatMap(ra -> roleAssigneeService
.getExplicitUsers(
roleAssigneeService.getRoleAssignee(ra.getAssigneeIdentifier()))
.stream())
.distinct() // prevent double-send
.forEach(au -> {

if (au.isSuperuser()) {
userNotificationService.sendNotification(au,
new Timestamp(new Date().getTime()),
UserNotification.Type.DATASETMENTIONED, dataset.getId(), null,
null, true, jsonstring);

}
});
sent = true;
}
} else {
logger.info("Didn't find dataset");
// We don't have a dataset corresponding to the object of the relationship - do
// nothing
}

} else {
// Can't get subject, relationship, object from message
logger.info("Can't find the subject, relationship or object in the message - ignoring");

}
} else {

// This isn't a Relationship announcement message - ignore
logger.info("This is not a relationship announcement - ignoring message of type "
+ msgObject.getJsonString("@type"));
}
}

if (!sent) {
if (citingPID == null || citingType == null) {
throw new BadRequestException(
"Could not parse message to find acceptable citation link to a dataset.");
} else {
throw new ServiceUnavailableException(
"Unable to process message. Please contact the administrators.");
}
throw new ServiceUnavailableException("Unable to process message. Please contact the administrators.");
}
} else {
logger.info("Ignoring message from IP address: " + origin.toString());
throw new ForbiddenException("Inbox does not acept messages from this address");
}
} catch (Throwable t) {
logger.severe(t.getLocalizedMessage());
t.printStackTrace();

throw t;
}
return ok("Message Received");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -552,7 +552,7 @@ public static JsonObject decontextualizeJsonLD(String jsonLDString) {
logger.fine("Decontextualized object: " + jsonld);
return jsonld;
} catch (JsonLdError e) {
System.out.println(e.getMessage());
logger.warning(e.getMessage());
return null;
}
}
Expand Down
Loading
Loading