diff --git a/doc/release-notes/12224-local-contexts-rights-info-in-DataCite b/doc/release-notes/12224-local-contexts-rights-info-in-DataCite
new file mode 100644
index 00000000000..c0a4c7b4d7a
--- /dev/null
+++ b/doc/release-notes/12224-local-contexts-rights-info-in-DataCite
@@ -0,0 +1,8 @@
+For instances enabling use of Local Contexts integration, Dataverse will now add rights information
+related to the Notices and Labels from a Local Contexts Project associated with a dataset to the metadata
+sent to DataCite (when using DataCite DOIs) and available in metadata exports (DataCite, OAI-ORE, and JSON).
+
+It is now possible to use non-string values in the retrieval-filtering context entries for external vocabulary scripts.
+This can be used to allow filtered JSON that is not valid JSON-LD to be included in the OAI_ORE JSON-LD metadata export
+ in a way that JSON-LD parsers will accept (and not ignore/drop). The OAI_ORE export version has been updated to 1.0.3 with this change.
+
\ No newline at end of file
diff --git a/doc/sphinx-guides/source/installation/localcontexts.rst b/doc/sphinx-guides/source/installation/localcontexts.rst
index 2bafc2524d9..f1738cec091 100644
--- a/doc/sphinx-guides/source/installation/localcontexts.rst
+++ b/doc/sphinx-guides/source/installation/localcontexts.rst
@@ -27,6 +27,7 @@ There are several steps to LocalContexts integration.
The metadatablock contains one field allowing Dataverse to store the URL of an associated Local Contexts Hub project. Be sure to update the Solr schema after installing the metadatablock (see :ref:`update-solr-schema`).
The external vocabulary script interacts with the Local Contexts Hub (via the Dataverse server) to display the Labels and Notices associated with the proect and provide a link to it.
The script also supports adding/removing such a link from the dataset's metadata. Note that only a project that references the dataset's PID in its `Optional Project Information` field can be linked to a dataset.
+ Note that the Local Contexts script configuration JSON must be edited to include your Dataverse server's URL and the Local Contexts api key you use in Dataverse. (The latter is optional but is must be included for Dataverse to add information about Notices and Labels to exported metadata and the metadata sent to DataCite for DOIs.)
- Lastly, to show Local Contexts information in the summary section of the dataset page, as shown in the image above, you should add `LCProjectUrl` to list of custom summary fields via use of the :ref:`:CustomDatasetSummaryFields` setting.
- Optionally, one can also set the dataverse.feature.add-local-contexts-permission-check FeatureFlag to true. This assures that only users editing datasets can use the LocalContexts search functionality (e.g. via API).
This is not recommended unless problematic use is seen.
diff --git a/src/main/java/edu/harvard/iq/dataverse/pidproviders/doi/XmlMetadataTemplate.java b/src/main/java/edu/harvard/iq/dataverse/pidproviders/doi/XmlMetadataTemplate.java
index 1d14b89e11a..ce260501e9a 100644
--- a/src/main/java/edu/harvard/iq/dataverse/pidproviders/doi/XmlMetadataTemplate.java
+++ b/src/main/java/edu/harvard/iq/dataverse/pidproviders/doi/XmlMetadataTemplate.java
@@ -48,7 +48,6 @@
import edu.harvard.iq.dataverse.dataset.DatasetUtil;
import edu.harvard.iq.dataverse.license.License;
import edu.harvard.iq.dataverse.pidproviders.AbstractPidProvider;
-import edu.harvard.iq.dataverse.pidproviders.PidProvider;
import edu.harvard.iq.dataverse.pidproviders.PidUtil;
import edu.harvard.iq.dataverse.pidproviders.handle.HandlePidProvider;
import edu.harvard.iq.dataverse.pidproviders.perma.PermaLinkPidProvider;
@@ -1287,9 +1286,124 @@ private void writeAccessRights(XMLStreamWriter xmlw, DvObject dvObject) throws X
;
}
xmlw.writeEndElement(); //
+ for (DatasetField dsf : dv.getDatasetFields()) {
+ if ("LCProjectUrl".equals(dsf.getDatasetFieldType().getName())) {
+ if (!dsf.isEmpty()) {
+ String projectUrl = dsf.getValue();
+ if (projectUrl != null) {
+ JsonObject evv = getExternalVocabularyValue(projectUrl);
+ if (evv != null) {
+ if (evv.containsKey("notices")) {
+ JsonValue notices = evv.get("notices");
+ if (notices.getValueType() == ValueType.ARRAY) {
+ for (JsonValue notice : notices.asJsonArray()) {
+ if (notice.getValueType() == ValueType.OBJECT) {
+ JsonObject noticeObject = notice.asJsonObject();
+ writeLocalContextNoticeRightsElement(xmlw, projectUrl, noticeObject);
+
+ }
+ }
+ }
+ } else if (evv.containsKey("tk_labels")) {
+ JsonValue tkLabels = evv.get("tk_labels");
+ if (tkLabels.getValueType() == ValueType.ARRAY) {
+ for (JsonValue tkLabel : tkLabels.asJsonArray()) {
+ if (tkLabel.getValueType() == ValueType.OBJECT) {
+ JsonObject tkLabelObject = tkLabel.asJsonObject();
+ writeLocalContextLabelRightsElement(xmlw, projectUrl, tkLabelObject);
+
+ }
+ }
+ }
+ } else if (evv.containsKey("bc_labels")) {
+ JsonValue bcLabels = evv.get("bc_labels");
+ if (bcLabels.getValueType() == ValueType.ARRAY) {
+ for (JsonValue bcLabel : bcLabels.asJsonArray()) {
+ if (bcLabel.getValueType() == ValueType.OBJECT) {
+ JsonObject bcLabelObject = bcLabel.asJsonObject();
+ writeLocalContextLabelRightsElement(xmlw, projectUrl, bcLabelObject);
+
+ }
+ }
+ }
+ }
+ } else {
+
+ // No label or notice info - we'll still add a pointer to the project
+ xmlw.writeStartElement("rights"); //
+ xmlw.writeAttribute("rightsURI", projectUrl); // repeated in @id in evv
+ xmlw.writeAttribute("rightsIdentifierScheme", "Local Contexts");
+ xmlw.writeAttribute("schemeURI", "https://localcontexts.org");
+ xmlw.writeEndElement(); //
+ }
+
+ }
+
+ }
+ }
+
+ }
xmlw.writeEndElement(); //
}
+ private void writeLocalContextNoticeRightsElement(XMLStreamWriter xmlw, String projectUrl, JsonObject noticeObject) throws XMLStreamException {
+ xmlw.writeStartElement("rights"); //
+ xmlw.writeAttribute("rightsURI", projectUrl); // repeated in @id in evv
+ xmlw.writeAttribute("rightsIdentifierScheme", "Local Contexts");
+ xmlw.writeAttribute("schemeURI", "https://localcontexts.org");
+ String rightsValue = null;
+ String lang = null;
+ if (noticeObject.containsKey("name")) {
+ String name = noticeObject.getString("name");
+ xmlw.writeAttribute("rightsIdentifier", name);
+ rightsValue = "Local Contexts " + name;
+ }
+ if (noticeObject.containsKey("notice_page")) {
+ rightsValue = rightsValue + " " + noticeObject.getString("notice_page");
+ }
+ if (noticeObject.containsKey("default_text")) {
+ rightsValue = rightsValue + ": " + noticeObject.getString("default_text");
+ }
+ if (noticeObject.containsKey("language_tag")) {
+ lang = noticeObject.getString("language_tag");
+ }
+ if (rightsValue != null) {
+ if (lang != null) {
+ xmlw.writeAttribute("xml:lang", lang);
+ }
+ xmlw.writeCharacters(rightsValue);
+ }
+ xmlw.writeEndElement(); //
+ }
+
+ private void writeLocalContextLabelRightsElement(XMLStreamWriter xmlw, String projectUrl, JsonObject labelObject) throws XMLStreamException {
+ xmlw.writeStartElement("rights"); //
+ xmlw.writeAttribute("rightsURI", projectUrl); // repeated in @id in evv
+ xmlw.writeAttribute("rightsIdentifierScheme", "Local Contexts");
+ xmlw.writeAttribute("schemeURI", "https://localcontexts.org");
+ String rightsValue = null;
+ String lang = null;
+ if (labelObject.containsKey("name")) {
+ String name = labelObject.getString("name");
+ xmlw.writeAttribute("rightsIdentifier", name);
+ rightsValue = "Local Contexts " + name;
+ }
+ if (labelObject.containsKey("default_text")) {
+ rightsValue = rightsValue + ": " + labelObject.getString("default_text");
+ }
+ if (labelObject.containsKey("language_tag")) {
+ lang = labelObject.getString("language_tag");
+ }
+ if (rightsValue != null) {
+ if (lang != null) {
+ xmlw.writeAttribute("xml:lang", lang);
+ }
+ xmlw.writeCharacters(rightsValue);
+ }
+ xmlw.writeEndElement(); //
+ }
+
+
private void writeDescriptions(XMLStreamWriter xmlw, DvObject dvObject, boolean deaccessioned) throws XMLStreamException {
// descriptions -> description with descriptionType attribute
boolean descriptionsWritten = false;
diff --git a/src/main/java/edu/harvard/iq/dataverse/util/bagit/OREMap.java b/src/main/java/edu/harvard/iq/dataverse/util/bagit/OREMap.java
index 0d99a5bddd1..1e8fe184566 100644
--- a/src/main/java/edu/harvard/iq/dataverse/util/bagit/OREMap.java
+++ b/src/main/java/edu/harvard/iq/dataverse/util/bagit/OREMap.java
@@ -9,7 +9,6 @@
import edu.harvard.iq.dataverse.util.json.JsonLDNamespace;
import edu.harvard.iq.dataverse.util.json.JsonLDTerm;
import edu.harvard.iq.dataverse.util.json.JsonPrinter;
-
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.time.LocalDate;
@@ -26,7 +25,6 @@
import jakarta.json.JsonObject;
import jakarta.json.JsonObjectBuilder;
import jakarta.json.JsonValue;
-
import org.apache.commons.lang3.exception.ExceptionUtils;
/**
@@ -49,13 +47,13 @@ public class OREMap {
public static final String NAME = "OREMap";
//NOTE: Update this value whenever the output of this class is changed
- private static final String DATAVERSE_ORE_FORMAT_VERSION = "Dataverse OREMap Format v1.0.2";
+ private static final String DATAVERSE_ORE_FORMAT_VERSION = "Dataverse OREMap Format v1.0.3";
//v1.0.1 - added versionNote
private static final String DATAVERSE_SOFTWARE_NAME = "Dataverse";
private static final String DATAVERSE_SOFTWARE_URL = "https://github.com/iqss/dataverse";
- private Map localContext = new TreeMap();
+ private Map localContext = new TreeMap();
private DatasetVersion version;
private Boolean excludeEmail = null;
@@ -63,7 +61,7 @@ public OREMap(DatasetVersion version) {
this.version = version;
}
- //Used when the ExcludeEmailFromExport needs to be overriden, i.e. for archiving
+ //Used when the ExcludeEmailFromExport needs to be overridden, i.e. for archiving
public OREMap(DatasetVersion dv, boolean exclude) {
this.version = dv;
this.excludeEmail = exclude;
@@ -91,10 +89,10 @@ public JsonObjectBuilder getOREMapBuilder(boolean aggregationOnly) {
// Add namespaces we'll definitely use to Context
// Additional namespaces are added as needed below
- localContext.putIfAbsent(JsonLDNamespace.ore.getPrefix(), JsonLDNamespace.ore.getUrl());
- localContext.putIfAbsent(JsonLDNamespace.dcterms.getPrefix(), JsonLDNamespace.dcterms.getUrl());
- localContext.putIfAbsent(JsonLDNamespace.dvcore.getPrefix(), JsonLDNamespace.dvcore.getUrl());
- localContext.putIfAbsent(JsonLDNamespace.schema.getPrefix(), JsonLDNamespace.schema.getUrl());
+ localContext.putIfAbsent(JsonLDNamespace.ore.getPrefix(), Json.createValue(JsonLDNamespace.ore.getUrl()));
+ localContext.putIfAbsent(JsonLDNamespace.dcterms.getPrefix(), Json.createValue(JsonLDNamespace.dcterms.getUrl()));
+ localContext.putIfAbsent(JsonLDNamespace.dvcore.getPrefix(), Json.createValue(JsonLDNamespace.dvcore.getUrl()));
+ localContext.putIfAbsent(JsonLDNamespace.schema.getPrefix(), Json.createValue(JsonLDNamespace.schema.getUrl()));
Dataset dataset = version.getDataset();
String id = dataset.getGlobalId().asURL();
@@ -297,7 +295,7 @@ public JsonObjectBuilder getOREMapBuilder(boolean aggregationOnly) {
}
// Build the '@context' object for json-ld based on the localContext entries
JsonObjectBuilder contextBuilder = Json.createObjectBuilder();
- for (Entry e : localContext.entrySet()) {
+ for (Entry e : localContext.entrySet()) {
contextBuilder.add(e.getKey(), e.getValue());
}
if (aggregationOnly) {
@@ -382,7 +380,7 @@ private void addIfNotNull(JsonObjectBuilder builder, JsonLDTerm key, Long value)
private void addToContextMap(JsonLDTerm key) {
if (!key.inNamespace()) {
- localContext.putIfAbsent(key.getLabel(), key.getUrl());
+ localContext.putIfAbsent(key.getLabel(), Json.createValue(key.getUrl()));
}
}
@@ -418,7 +416,7 @@ private JsonLDTerm getTermFor(String fieldTypeName) {
}
public static JsonValue getJsonLDForField(DatasetField field, Boolean excludeEmail, Map cvocMap,
- Map localContext) {
+ Map localContext2) {
DatasetFieldType dfType = field.getDatasetFieldType();
if (excludeEmail && DatasetFieldType.FieldType.EMAIL.equals(dfType.getFieldType())) {
@@ -427,15 +425,15 @@ public static JsonValue getJsonLDForField(DatasetField field, Boolean excludeEma
JsonLDTerm fieldName = dfType.getJsonLDTerm();
if (fieldName.inNamespace()) {
- localContext.putIfAbsent(fieldName.getNamespace().getPrefix(), fieldName.getNamespace().getUrl());
+ localContext2.putIfAbsent(fieldName.getNamespace().getPrefix(), Json.createValue(fieldName.getNamespace().getUrl()));
} else {
- localContext.putIfAbsent(fieldName.getLabel(), fieldName.getUrl());
+ localContext2.putIfAbsent(fieldName.getLabel(), Json.createValue(fieldName.getUrl()));
}
JsonArrayBuilder vals = Json.createArrayBuilder();
if (!dfType.isCompound()) {
for (String val : field.getValues_nondisplay()) {
if (cvocMap.containsKey(dfType.getId())) {
- addCvocValue(val, vals, cvocMap.get(dfType.getId()), localContext);
+ addCvocValue(val, vals, cvocMap.get(dfType.getId()), localContext2);
} else {
vals.add(val);
}
@@ -451,7 +449,7 @@ public static JsonValue getJsonLDForField(DatasetField field, Boolean excludeEma
JsonLDTerm subFieldName = dsft.getJsonLDTerm();
if (dsft.isCompound()) {
- JsonValue compoundChildVals = getJsonLDForField(dsf, excludeEmail, cvocMap, localContext);
+ JsonValue compoundChildVals = getJsonLDForField(dsf, excludeEmail, cvocMap, localContext2);
child.add(subFieldName.getLabel(), compoundChildVals);
} else {
if (excludeEmail && DatasetFieldType.FieldType.EMAIL.equals(dsft.getFieldType())) {
@@ -462,10 +460,10 @@ public static JsonValue getJsonLDForField(DatasetField field, Boolean excludeEma
// Add context entry
// ToDo - also needs to recurse here?
if (subFieldName.inNamespace()) {
- localContext.putIfAbsent(subFieldName.getNamespace().getPrefix(),
- subFieldName.getNamespace().getUrl());
+ localContext2.putIfAbsent(subFieldName.getNamespace().getPrefix(),
+ Json.createValue(subFieldName.getNamespace().getUrl()));
} else {
- localContext.putIfAbsent(subFieldName.getLabel(), subFieldName.getUrl());
+ localContext2.putIfAbsent(subFieldName.getLabel(), Json.createValue(subFieldName.getUrl()));
}
List values = dsf.getValues_nondisplay();
@@ -476,7 +474,7 @@ public static JsonValue getJsonLDForField(DatasetField field, Boolean excludeEma
logger.fine("Child name: " + dsft.getName());
if (cvocMap.containsKey(dsft.getId())) {
logger.fine("Calling addcvocval for: " + dsft.getName());
- addCvocValue(val, childVals, cvocMap.get(dsft.getId()), localContext);
+ addCvocValue(val, childVals, cvocMap.get(dsft.getId()), localContext2);
} else {
childVals.add(val);
}
@@ -498,13 +496,13 @@ public static JsonValue getJsonLDForField(DatasetField field, Boolean excludeEma
}
private static void addCvocValue(String val, JsonArrayBuilder vals, JsonObject cvocEntry,
- Map localContext) {
+ Map localContext2) {
try {
if (cvocEntry.containsKey("retrieval-filtering")) {
JsonObject filtering = cvocEntry.getJsonObject("retrieval-filtering");
JsonObject context = filtering.getJsonObject("@context");
for (String prefix : context.keySet()) {
- localContext.putIfAbsent(prefix, context.getString(prefix));
+ localContext2.putIfAbsent(prefix, context.get(prefix));
}
JsonObject cachedValue = datasetFieldService.getExternalVocabularyValue(val);
if (cachedValue != null) {
diff --git a/src/main/java/edu/harvard/iq/dataverse/workflow/internalspi/COARNotifyRelationshipAnnouncementStep.java b/src/main/java/edu/harvard/iq/dataverse/workflow/internalspi/COARNotifyRelationshipAnnouncementStep.java
index c96e79f47e4..939858f9386 100644
--- a/src/main/java/edu/harvard/iq/dataverse/workflow/internalspi/COARNotifyRelationshipAnnouncementStep.java
+++ b/src/main/java/edu/harvard/iq/dataverse/workflow/internalspi/COARNotifyRelationshipAnnouncementStep.java
@@ -205,7 +205,7 @@ public void rollback(WorkflowContext context, Failure reason) {
*/
JsonArray getObjects(WorkflowContext ctxt, Map fields) {
JsonArrayBuilder jab = Json.createArrayBuilder();
- Map localContext = new HashMap();
+ Map localContext = new HashMap<>();
Map emptyCvocMap = new HashMap();
Dataset d = ctxt.getDataset();
@@ -238,7 +238,7 @@ JsonArray getObjects(WorkflowContext ctxt, Map fields) {
}
private JsonObject getRelationshipObject(DatasetFieldType dft, JsonValue jval, Dataset d,
- Map localContext) {
+ Map localContext) {
if (logger.isLoggable(Level.FINE)) {
if (jval.getValueType().equals(jakarta.json.JsonValue.ValueType.OBJECT)) {
logger.fine("Parsing : " + JsonUtil.prettyPrint(jval.asJsonObject()));