Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
@@ -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.

1 change: 1 addition & 0 deletions doc/sphinx-guides/source/installation/localcontexts.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -1287,9 +1286,124 @@ private void writeAccessRights(XMLStreamWriter xmlw, DvObject dvObject) throws X
;
}
xmlw.writeEndElement(); // </rights>
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"); // <rights>
xmlw.writeAttribute("rightsURI", projectUrl); // repeated in @id in evv
xmlw.writeAttribute("rightsIdentifierScheme", "Local Contexts");
xmlw.writeAttribute("schemeURI", "https://localcontexts.org");
xmlw.writeEndElement(); // </rights>
}

}

}
}

}
xmlw.writeEndElement(); // </rightsList>
}

private void writeLocalContextNoticeRightsElement(XMLStreamWriter xmlw, String projectUrl, JsonObject noticeObject) throws XMLStreamException {
xmlw.writeStartElement("rights"); // <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(); // </rights>
}

private void writeLocalContextLabelRightsElement(XMLStreamWriter xmlw, String projectUrl, JsonObject labelObject) throws XMLStreamException {
xmlw.writeStartElement("rights"); // <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(); // </rights>
}


private void writeDescriptions(XMLStreamWriter xmlw, DvObject dvObject, boolean deaccessioned) throws XMLStreamException {
// descriptions -> description with descriptionType attribute
boolean descriptionsWritten = false;
Expand Down
42 changes: 20 additions & 22 deletions src/main/java/edu/harvard/iq/dataverse/util/bagit/OREMap.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -26,7 +25,6 @@
import jakarta.json.JsonObject;
import jakarta.json.JsonObjectBuilder;
import jakarta.json.JsonValue;

import org.apache.commons.lang3.exception.ExceptionUtils;

/**
Expand All @@ -49,21 +47,21 @@ 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<String, String> localContext = new TreeMap<String, String>();
private Map<String, JsonValue> localContext = new TreeMap<String, JsonValue>();
private DatasetVersion version;
private Boolean excludeEmail = null;

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;
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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<String, String> e : localContext.entrySet()) {
for (Entry<String, JsonValue> e : localContext.entrySet()) {
contextBuilder.add(e.getKey(), e.getValue());
}
if (aggregationOnly) {
Expand Down Expand Up @@ -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()));
}
}

Expand Down Expand Up @@ -418,7 +416,7 @@ private JsonLDTerm getTermFor(String fieldTypeName) {
}

public static JsonValue getJsonLDForField(DatasetField field, Boolean excludeEmail, Map<Long, JsonObject> cvocMap,
Map<String, String> localContext) {
Map<String, JsonValue> localContext2) {

DatasetFieldType dfType = field.getDatasetFieldType();
if (excludeEmail && DatasetFieldType.FieldType.EMAIL.equals(dfType.getFieldType())) {
Expand All @@ -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);
}
Expand All @@ -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())) {
Expand All @@ -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<String> values = dsf.getValues_nondisplay();
Expand All @@ -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);
}
Expand All @@ -498,13 +496,13 @@ public static JsonValue getJsonLDForField(DatasetField field, Boolean excludeEma
}

private static void addCvocValue(String val, JsonArrayBuilder vals, JsonObject cvocEntry,
Map<String, String> localContext) {
Map<String, JsonValue> 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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ public void rollback(WorkflowContext context, Failure reason) {
*/
JsonArray getObjects(WorkflowContext ctxt, Map<String, DatasetField> fields) {
JsonArrayBuilder jab = Json.createArrayBuilder();
Map<String, String> localContext = new HashMap<String, String>();
Map<String, JsonValue> localContext = new HashMap<>();
Map<Long, JsonObject> emptyCvocMap = new HashMap<Long, JsonObject>();

Dataset d = ctxt.getDataset();
Expand Down Expand Up @@ -238,7 +238,7 @@ JsonArray getObjects(WorkflowContext ctxt, Map<String, DatasetField> fields) {
}

private JsonObject getRelationshipObject(DatasetFieldType dft, JsonValue jval, Dataset d,
Map<String, String> localContext) {
Map<String, JsonValue> localContext) {
if (logger.isLoggable(Level.FINE)) {
if (jval.getValueType().equals(jakarta.json.JsonValue.ValueType.OBJECT)) {
logger.fine("Parsing : " + JsonUtil.prettyPrint(jval.asJsonObject()));
Expand Down
Loading