diff --git a/doc/release-notes/6497-semantic-api.md b/doc/release-notes/6497-semantic-api.md
new file mode 100644
index 00000000000..8222892d18e
--- /dev/null
+++ b/doc/release-notes/6497-semantic-api.md
@@ -0,0 +1,7 @@
+# Release Highlights
+
+### Dataset Semantic API (Experimental)
+
+Dataset metadata can be retrieved/set/updated using a new, flatter JSON-LD format - following the format of an OAI-ORE export (RDA-conformant Bags), allowing for easier transfer of metadata to/from other systems (i.e. without needing to know Dataverse's metadata block and field storage architecture). This new API also allows for the update of terms metadata (#5899).
+
+This development was supported by the [Research Data Alliance](https://rd-alliance.org), DANS, and Sciences PO and follows the recommendations from the [Research Data Repository Interoperability Working Group](http://dx.doi.org/10.15497/RDA00025).
diff --git a/doc/sphinx-guides/source/_static/api/dataset-create.jsonld b/doc/sphinx-guides/source/_static/api/dataset-create.jsonld
new file mode 100644
index 00000000000..16861ff64ad
--- /dev/null
+++ b/doc/sphinx-guides/source/_static/api/dataset-create.jsonld
@@ -0,0 +1,15 @@
+{
+ "http://purl.org/dc/terms/title": "Darwin's Finches",
+ "http://purl.org/dc/terms/subject": "Medicine, Health and Life Sciences",
+ "http://purl.org/dc/terms/creator": {
+ "https://dataverse.org/schema/citation/author#Name": "Finch, Fiona",
+ "https://dataverse.org/schema/citation/author#Affiliation": "Birds Inc."
+ },
+ "https://dataverse.org/schema/citation/Contact": {
+ "https://dataverse.org/schema/citation/datasetContact#E-mail": "finch@mailinator.com",
+ "https://dataverse.org/schema/citation/datasetContact#Name": "Finch, Fiona"
+ },
+ "https://dataverse.org/schema/citation/Description": {
+ "https://dataverse.org/schema/citation/dsDescription#Text": "Darwin's finches (also known as the Galápagos finches) are a group of about fifteen species of passerine birds."
+ }
+}
\ No newline at end of file
diff --git a/doc/sphinx-guides/source/developers/dataset-semantic-metadata-api.rst b/doc/sphinx-guides/source/developers/dataset-semantic-metadata-api.rst
new file mode 100644
index 00000000000..da28cc60c53
--- /dev/null
+++ b/doc/sphinx-guides/source/developers/dataset-semantic-metadata-api.rst
@@ -0,0 +1,103 @@
+Dataset Semantic Metadata API
+=============================
+
+The OAI_ORE metadata export format represents Dataset metadata using json-ld (see the :doc:`/admin/metadataexport` section). As part of an RDA-supported effort to allow import of Datasets exported as Bags with an included OAI_ORE metadata file,
+an experimental API has been created that provides a json-ld alternative to the v1.0 API calls to get/set/delete Dataset metadata in the :doc:`/api/native-api`.
+
+You may prefer to work with this API if you are building a tool to import from a Bag/OAI-ORE source or already work with json-ld representations of metadata, or if you prefer the flatter json-ld representation to Dataverse software's json representation (which includes structure related to the metadata blocks involved and the type/multiplicity of the metadata fields.)
+You may not want to use this API if you need stability and backward compatibility (the 'experimental' designation for this API implies that community feedback is desired and that, in future Dataverse software versions, the API may be modified based on that feedback).
+
+Note: The examples use the 'application/ld+json' mimetype. For compatibility reasons, the APIs also be used with mimetype "application/json-ld"
+
+Get Dataset Metadata
+--------------------
+
+To get the json-ld formatted metadata for a Dataset, specify the Dataset ID (DATASET_ID) or Persistent identifier (DATASET_PID), and, for specific versions, the version number.
+
+.. code-block:: bash
+
+ export API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
+ export DATASET_ID='12345'
+ export DATASET_PID='doi:10.5072/FK2A1B2C3'
+ export VERSION='1.0'
+ export SERVER_URL=https://demo.dataverse.org
+
+ Example 1: Get metadata for version '1.0'
+
+ curl -H X-Dataverse-key:$API_TOKEN -H 'Accept: application/ld+json' "$SERVER_URL/api/datasets/$DATASET_ID/versions/$VERSION/metadata"
+
+ Example 2: Get metadata for the latest version using the DATASET PID
+
+ curl -H X-Dataverse-key:$API_TOKEN -H 'Accept: application/ld+json' "$SERVER_URL/api/datasets/:persistentId/metadata?persistentId=$DATASET_PID"
+
+You should expect a 200 ("OK") response and JSON-LD mirroring the OAI-ORE representation in the returned 'data' object.
+
+
+Add Dataset Metadata
+--------------------
+
+To add json-ld formatted metadata for a Dataset, specify the Dataset ID (DATASET_ID) or Persistent identifier (DATASET_PID). Adding '?replace=true' will overwrite an existing metadata value. The default (replace=false) will only add new metadata or add a new value to a multi-valued field.
+
+.. code-block:: bash
+
+ export API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
+ export DATASET_ID='12345'
+ export DATASET_PID='doi:10.5072/FK2A1B2C3'
+ export VERSION='1.0'
+ export SERVER_URL=https://demo.dataverse.org
+
+ Example: Change the Dataset title
+
+ curl -X PUT -H X-Dataverse-key:$API_TOKEN -H 'Content-Type: application/ld+json' -d '{"Title": "Submit menu test", "@context":{"Title": "http://purl.org/dc/terms/title"}}' "$SERVER_URL/api/datasets/$DATASET_ID/metadata?replace=true"
+
+ Example 2: Add a description using the DATASET PID
+
+ curl -X PUT -H X-Dataverse-key:$API_TOKEN -H 'Content-Type: application/ld+json' -d '{"citation:Description": {"dsDescription:Text": "New description"}, "@context":{"citation": "https://dataverse.org/schema/citation/","dsDescription": "https://dataverse.org/schema/citation/dsDescription#"}}' "$SERVER_URL/api/datasets/:persistentId/metadata?persistentId=$DATASET_PID"
+
+You should expect a 200 ("OK") response indicating whether a draft Dataset version was created or an existing draft was updated.
+
+
+Delete Dataset Metadata
+-----------------------
+
+To delete metadata for a Dataset, send a json-ld representation of the fields to delete and specify the Dataset ID (DATASET_ID) or Persistent identifier (DATASET_PID).
+
+.. code-block:: bash
+
+ export API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
+ export DATASET_ID='12345'
+ export DATASET_PID='doi:10.5072/FK2A1B2C3'
+ export VERSION='1.0'
+ export SERVER_URL=https://demo.dataverse.org
+
+ Example: Delete the TermsOfUseAndAccess 'restrictions' value 'No restrictions' for the latest version using the DATASET PID
+
+ curl -X PUT -H X-Dataverse-key:$API_TOKEN -H 'Content-Type: application/ld+json' -d '{"https://dataverse.org/schema/core#restrictions":"No restrictions"}' "$SERVER_URL/api/datasets/:persistentId/metadata/delete?persistentId=$DATASET_PID"
+
+Note, this example uses the term URI directly rather than adding an '@context' element. You can use either form in any of these API calls.
+
+You should expect a 200 ("OK") response indicating whether a draft Dataset version was created or an existing draft was updated.
+
+
+Create a Dataset
+----------------
+
+Specifying the Content-Type as application/ld+json with the existing /api/dataverses/{id}/datasets API call (see :ref:`create-dataset-command`) supports using the same metadata format when creating a Dataset.
+
+With curl, this is done by adding the following header:
+
+.. code-block:: bash
+
+ -H 'Content-Type: application/ld+json'
+
+ .. code-block:: bash
+
+ export API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
+ export SERVER_URL=https://demo.dataverse.org
+ export DATAVERSE_ID=root
+ export PERSISTENT_IDENTIFIER=doi:10.5072/FK27U7YBV
+
+ curl -H X-Dataverse-key:$API_TOKEN -H 'Content-Type: application/ld+json' -X POST $SERVER_URL/api/dataverses/$DATAVERSE_ID/datasets --upload-file dataset-create.jsonld
+
+An example jsonld file is available at :download:`dataset-create.jsonld <../_static/api/dataset-create.jsonld>`
+
diff --git a/doc/sphinx-guides/source/developers/index.rst b/doc/sphinx-guides/source/developers/index.rst
index eebfd50ba35..1e76171fc92 100755
--- a/doc/sphinx-guides/source/developers/index.rst
+++ b/doc/sphinx-guides/source/developers/index.rst
@@ -35,4 +35,5 @@ Developer Guide
big-data-support
aux-file-support
s3-direct-upload-api
+ dataset-semantic-metadata-api
workflows
diff --git a/pom.xml b/pom.xml
index d31497101bf..9f32dc9ea75 100644
--- a/pom.xml
+++ b/pom.xml
@@ -173,9 +173,21 @@
org.glassfish
javax.json
- 1.0.4
+ 1.1.4
test
+
+ org.skyscreamer
+ jsonassert
+ 1.5.0
+ test
+
+
+ com.vaadin.external.google
+ android-json
+
+
+
org.apache.httpcomponents
httpclient
@@ -218,6 +230,11 @@
aws-java-sdk-s3
+
+ com.apicatalog
+ titanium-json-ld
+ 0.8.6
+
org.apache.abdera
diff --git a/scripts/search/tests/data/dataset-finch1.jsonld b/scripts/search/tests/data/dataset-finch1.jsonld
new file mode 100644
index 00000000000..be39c9f14b2
--- /dev/null
+++ b/scripts/search/tests/data/dataset-finch1.jsonld
@@ -0,0 +1,26 @@
+
+{
+ "http://purl.org/dc/terms/title": "Darwin's Finches",
+ "http://purl.org/dc/terms/subject": "Medicine, Health and Life Sciences",
+ "http://purl.org/dc/terms/creator": {
+ "https://dataverse.org/schema/citation/author#Name": "Finch, Fiona",
+ "https://dataverse.org/schema/citation/author#Affiliation": "Birds Inc."
+ },
+ "https://dataverse.org/schema/citation/Contact": {
+ "https://dataverse.org/schema/citation/datasetContact#E-mail": "finch@mailinator.com",
+ "https://dataverse.org/schema/citation/datasetContact#Name": "Finch, Fiona"
+ },
+ "https://dataverse.org/schema/citation/Description": {
+ "https://dataverse.org/schema/citation/dsDescription#Text": "Darwin's finches (also known as the Galápagos finches) are a group of about fifteen species of passerine birds."
+ },
+ "@type": [
+ "http://www.openarchives.org/ore/terms/Aggregation",
+ "http://schema.org/Dataset"
+ ],
+ "http://schema.org/version": "DRAFT",
+ "http://schema.org/name": "Darwin's Finches",
+ "https://dataverse.org/schema/core#fileTermsOfAccess": {
+ "https://dataverse.org/schema/core#fileRequestAccess": false
+ },
+ "http://schema.org/includedInDataCatalog": "Root"
+}
\ No newline at end of file
diff --git a/src/main/java/edu/harvard/iq/dataverse/DatasetVersion.java b/src/main/java/edu/harvard/iq/dataverse/DatasetVersion.java
index 5083b778d2e..864dca45664 100644
--- a/src/main/java/edu/harvard/iq/dataverse/DatasetVersion.java
+++ b/src/main/java/edu/harvard/iq/dataverse/DatasetVersion.java
@@ -1863,7 +1863,7 @@ public String getJsonLd() {
JsonObjectBuilder license = Json.createObjectBuilder().add("@type", "Dataset");
if (TermsOfUseAndAccess.License.CC0.equals(terms.getLicense())) {
- license.add("text", "CC0").add("url", "https://creativecommons.org/publicdomain/zero/1.0/");
+ license.add("text", "CC0").add("url", TermsOfUseAndAccess.CC0_URI);
} else {
String termsOfUse = terms.getTermsOfUse();
// Terms of use can be null if you create the dataset with JSON.
diff --git a/src/main/java/edu/harvard/iq/dataverse/TermsOfUseAndAccess.java b/src/main/java/edu/harvard/iq/dataverse/TermsOfUseAndAccess.java
index ad6775d6efd..72f4ab54ee8 100644
--- a/src/main/java/edu/harvard/iq/dataverse/TermsOfUseAndAccess.java
+++ b/src/main/java/edu/harvard/iq/dataverse/TermsOfUseAndAccess.java
@@ -280,7 +280,7 @@ public enum License {
* API use? See also https://github.com/IQSS/dataverse/issues/1385
*/
public static TermsOfUseAndAccess.License defaultLicense = TermsOfUseAndAccess.License.CC0;
-
+ public static String CC0_URI = "https://creativecommons.org/publicdomain/zero/1.0/";
@Override
public int hashCode() {
int hash = 0;
diff --git a/src/main/java/edu/harvard/iq/dataverse/api/DatasetFieldServiceApi.java b/src/main/java/edu/harvard/iq/dataverse/api/DatasetFieldServiceApi.java
index 315c3de4400..a057068aa1a 100644
--- a/src/main/java/edu/harvard/iq/dataverse/api/DatasetFieldServiceApi.java
+++ b/src/main/java/edu/harvard/iq/dataverse/api/DatasetFieldServiceApi.java
@@ -137,6 +137,7 @@ public Response getByName(@PathParam("name") String name) {
String solrFieldSearchable = dsf.getSolrField().getNameSearchable();
String solrFieldFacetable = dsf.getSolrField().getNameFacetable();
String metadataBlock = dsf.getMetadataBlock().getName();
+ String uri=dsf.getUri();
boolean hasParent = dsf.isHasParent();
boolean allowsMultiples = dsf.isAllowMultiples();
boolean isRequired = dsf.isRequired();
@@ -168,7 +169,8 @@ public Response getByName(@PathParam("name") String name) {
.add("parentAllowsMultiples", parentAllowsMultiplesDisplay)
.add("solrFieldSearchable", solrFieldSearchable)
.add("solrFieldFacetable", solrFieldFacetable)
- .add("isRequired", isRequired));
+ .add("isRequired", isRequired)
+ .add("uri", uri));
} catch ( NoResultException nre ) {
return notFound(name);
@@ -356,7 +358,7 @@ public String getArrayIndexOutOfBoundMessage(HeaderType header,
int wrongIndex) {
List columns = getColumnsByHeader(header);
-
+
String column = columns.get(wrongIndex - 1);
List arguments = new ArrayList<>();
arguments.add(header.name());
diff --git a/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java b/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java
index c1359e1c366..645b3cc8355 100644
--- a/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java
+++ b/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java
@@ -6,7 +6,6 @@
import edu.harvard.iq.dataverse.Dataset;
import edu.harvard.iq.dataverse.DatasetField;
import edu.harvard.iq.dataverse.DatasetFieldCompoundValue;
-import edu.harvard.iq.dataverse.DatasetFieldServiceBean;
import edu.harvard.iq.dataverse.DatasetFieldType;
import edu.harvard.iq.dataverse.DatasetFieldValue;
import edu.harvard.iq.dataverse.DatasetLock;
@@ -78,7 +77,7 @@
import edu.harvard.iq.dataverse.ingest.IngestServiceBean;
import edu.harvard.iq.dataverse.privateurl.PrivateUrl;
import edu.harvard.iq.dataverse.S3PackageImporter;
-import static edu.harvard.iq.dataverse.api.AbstractApiBean.error;
+import edu.harvard.iq.dataverse.api.AbstractApiBean.WrappedResponse;
import edu.harvard.iq.dataverse.api.dto.RoleAssignmentDTO;
import edu.harvard.iq.dataverse.batch.util.LoggingUtil;
import edu.harvard.iq.dataverse.dataaccess.DataAccess;
@@ -103,6 +102,8 @@
import edu.harvard.iq.dataverse.util.EjbUtil;
import edu.harvard.iq.dataverse.util.FileUtil;
import edu.harvard.iq.dataverse.util.SystemConfig;
+import edu.harvard.iq.dataverse.util.bagit.OREMap;
+import edu.harvard.iq.dataverse.util.json.JSONLDUtil;
import edu.harvard.iq.dataverse.util.json.JsonParseException;
import edu.harvard.iq.dataverse.search.IndexServiceBean;
import static edu.harvard.iq.dataverse.util.json.JsonPrinter.*;
@@ -122,6 +123,7 @@
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
+import java.util.Map.Entry;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -135,6 +137,7 @@
import javax.json.JsonObject;
import javax.json.JsonObjectBuilder;
import javax.json.JsonReader;
+import javax.json.stream.JsonParsingException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.Consumes;
@@ -162,7 +165,6 @@
import com.amazonaws.services.s3.model.PartETag;
import edu.harvard.iq.dataverse.FileMetadata;
-import java.util.Map.Entry;
@Path("datasets")
public class Datasets extends AbstractApiBean {
@@ -189,9 +191,6 @@ public class Datasets extends AbstractApiBean {
@EJB
DDIExportServiceBean ddiExportService;
- @EJB
- DatasetFieldServiceBean datasetfieldService;
-
@EJB
MetadataBlockServiceBean metadataBlockService;
@@ -599,6 +598,7 @@ public Response updateDatasetPIDMetadataAll() {
@PUT
@Path("{id}/versions/{versionId}")
+ @Consumes(MediaType.APPLICATION_JSON)
public Response updateDraftVersion( String jsonBody, @PathParam("id") String id, @PathParam("versionId") String versionId ){
if ( ! ":draft".equals(versionId) ) {
@@ -651,6 +651,94 @@ public Response updateDraftVersion( String jsonBody, @PathParam("id") String id,
}
}
+
+ @GET
+ @Path("{id}/versions/{versionId}/metadata")
+ @Produces("application/ld+json, application/json-ld")
+ public Response getVersionJsonLDMetadata(@PathParam("id") String id, @PathParam("versionId") String versionId, @Context UriInfo uriInfo, @Context HttpHeaders headers) {
+ try {
+ DataverseRequest req = createDataverseRequest(findUserOrDie());
+ DatasetVersion dsv = getDatasetVersionOrDie(req, versionId, findDatasetOrDie(id), uriInfo, headers);
+ OREMap ore = new OREMap(dsv,
+ settingsService.isTrueForKey(SettingsServiceBean.Key.ExcludeEmailFromExport, false));
+ return ok(ore.getOREMapBuilder(true));
+
+ } catch (WrappedResponse ex) {
+ ex.printStackTrace();
+ return ex.getResponse();
+ } catch (Exception jpe) {
+ logger.log(Level.SEVERE, "Error getting jsonld metadata for dsv: ", jpe.getLocalizedMessage());
+ jpe.printStackTrace();
+ return error(Response.Status.INTERNAL_SERVER_ERROR, jpe.getLocalizedMessage());
+ }
+ }
+
+ @GET
+ @Path("{id}/metadata")
+ @Produces("application/ld+json, application/json-ld")
+ public Response getVersionJsonLDMetadata(@PathParam("id") String id, @Context UriInfo uriInfo, @Context HttpHeaders headers) {
+ return getVersionJsonLDMetadata(id, ":draft", uriInfo, headers);
+ }
+
+ @PUT
+ @Path("{id}/metadata")
+ @Consumes("application/ld+json, application/json-ld")
+ public Response updateVersionMetadata(String jsonLDBody, @PathParam("id") String id, @DefaultValue("false") @QueryParam("replace") boolean replaceTerms) {
+
+ try {
+ Dataset ds = findDatasetOrDie(id);
+ DataverseRequest req = createDataverseRequest(findUserOrDie());
+ DatasetVersion dsv = ds.getEditVersion();
+ boolean updateDraft = ds.getLatestVersion().isDraft();
+ dsv = JSONLDUtil.updateDatasetVersionMDFromJsonLD(dsv, jsonLDBody, metadataBlockService, datasetFieldSvc, !replaceTerms, false);
+
+ DatasetVersion managedVersion;
+ if (updateDraft) {
+ Dataset managedDataset = execCommand(new UpdateDatasetVersionCommand(ds, req));
+ managedVersion = managedDataset.getEditVersion();
+ } else {
+ managedVersion = execCommand(new CreateDatasetVersionCommand(req, ds, dsv));
+ }
+ String info = updateDraft ? "Version Updated" : "Version Created";
+ return ok(Json.createObjectBuilder().add(info, managedVersion.getVersionDate()));
+
+ } catch (WrappedResponse ex) {
+ return ex.getResponse();
+ } catch (JsonParsingException jpe) {
+ logger.log(Level.SEVERE, "Error parsing dataset json. Json: {0}", jsonLDBody);
+ return error(Status.BAD_REQUEST, "Error parsing Json: " + jpe.getMessage());
+ }
+ }
+
+ @PUT
+ @Path("{id}/metadata/delete")
+ @Consumes("application/ld+json, application/json-ld")
+ public Response deleteMetadata(String jsonLDBody, @PathParam("id") String id) {
+ try {
+ Dataset ds = findDatasetOrDie(id);
+ DataverseRequest req = createDataverseRequest(findUserOrDie());
+ DatasetVersion dsv = ds.getEditVersion();
+ boolean updateDraft = ds.getLatestVersion().isDraft();
+ dsv = JSONLDUtil.deleteDatasetVersionMDFromJsonLD(dsv, jsonLDBody, metadataBlockService, datasetFieldSvc);
+ DatasetVersion managedVersion;
+ if (updateDraft) {
+ Dataset managedDataset = execCommand(new UpdateDatasetVersionCommand(ds, req));
+ managedVersion = managedDataset.getEditVersion();
+ } else {
+ managedVersion = execCommand(new CreateDatasetVersionCommand(req, ds, dsv));
+ }
+ String info = updateDraft ? "Version Updated" : "Version Created";
+ return ok(Json.createObjectBuilder().add(info, managedVersion.getVersionDate()));
+
+ } catch (WrappedResponse ex) {
+ ex.printStackTrace();
+ return ex.getResponse();
+ } catch (JsonParsingException jpe) {
+ logger.log(Level.SEVERE, "Error parsing dataset json. Json: {0}", jsonLDBody);
+ jpe.printStackTrace();
+ return error(Status.BAD_REQUEST, "Error parsing Json: " + jpe.getMessage());
+ }
+ }
@PUT
@Path("{id}/deleteMetadata")
diff --git a/src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java b/src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java
index ebc52785637..b56a88af75f 100644
--- a/src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java
+++ b/src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java
@@ -63,6 +63,8 @@
import edu.harvard.iq.dataverse.util.BundleUtil;
import edu.harvard.iq.dataverse.util.StringUtil;
import static edu.harvard.iq.dataverse.util.StringUtil.nonEmpty;
+
+import edu.harvard.iq.dataverse.util.json.JSONLDUtil;
import edu.harvard.iq.dataverse.util.json.JsonParseException;
import static edu.harvard.iq.dataverse.util.json.JsonPrinter.brief;
import java.io.StringReader;
@@ -229,6 +231,7 @@ public Response addDataverse(String body, @PathParam("identifier") String parent
@POST
@Path("{identifier}/datasets")
+ @Consumes("application/json")
public Response createDataset(String jsonBody, @PathParam("identifier") String parentIdtf) {
try {
User u = findUserOrDie();
@@ -266,6 +269,45 @@ public Response createDataset(String jsonBody, @PathParam("identifier") String p
return ex.getResponse();
}
}
+
+ @POST
+ @Path("{identifier}/datasets")
+ @Consumes("application/ld+json, application/json-ld")
+ public Response createDatasetFromJsonLd(String jsonLDBody, @PathParam("identifier") String parentIdtf) {
+ try {
+ User u = findUserOrDie();
+ Dataverse owner = findDataverseOrDie(parentIdtf);
+ Dataset ds = new Dataset();
+
+ ds.setOwner(owner);
+ ds = JSONLDUtil.updateDatasetMDFromJsonLD(ds, jsonLDBody, metadataBlockSvc, datasetFieldSvc, false, false);
+
+ ds.setOwner(owner);
+
+
+
+ // clean possible dataset/version metadata
+ DatasetVersion version = ds.getVersions().get(0);
+ version.setMinorVersionNumber(null);
+ version.setVersionNumber(null);
+ version.setVersionState(DatasetVersion.VersionState.DRAFT);
+
+ ds.setAuthority(null);
+ ds.setIdentifier(null);
+ ds.setProtocol(null);
+ ds.setGlobalIdCreateTime(null);
+
+ Dataset managedDs = execCommand(new CreateNewDatasetCommand(ds, createDataverseRequest(u)));
+ return created("/datasets/" + managedDs.getId(),
+ Json.createObjectBuilder()
+ .add("id", managedDs.getId())
+ .add("persistentId", managedDs.getGlobalIdString())
+ );
+
+ } catch (WrappedResponse ex) {
+ return ex.getResponse();
+ }
+ }
@POST
@Path("{identifier}/datasets/:import")
diff --git a/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/LocalSubmitToArchiveCommand.java b/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/LocalSubmitToArchiveCommand.java
index 0a1de25bed0..d87c3011c15 100644
--- a/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/LocalSubmitToArchiveCommand.java
+++ b/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/LocalSubmitToArchiveCommand.java
@@ -84,6 +84,7 @@ public WorkflowStepResult performArchiveSubmission(DatasetVersion dv, ApiToken t
}
} catch (Exception e) {
logger.warning(e.getLocalizedMessage() + "here");
+ e.printStackTrace();
}
return WorkflowStepResult.OK;
}
diff --git a/src/main/java/edu/harvard/iq/dataverse/export/openaire/OpenAireExportUtil.java b/src/main/java/edu/harvard/iq/dataverse/export/openaire/OpenAireExportUtil.java
index fe0c15969ca..f972ae2a983 100644
--- a/src/main/java/edu/harvard/iq/dataverse/export/openaire/OpenAireExportUtil.java
+++ b/src/main/java/edu/harvard/iq/dataverse/export/openaire/OpenAireExportUtil.java
@@ -19,6 +19,7 @@
import edu.harvard.iq.dataverse.DatasetFieldConstant;
import edu.harvard.iq.dataverse.GlobalId;
+import edu.harvard.iq.dataverse.TermsOfUseAndAccess;
import edu.harvard.iq.dataverse.api.dto.DatasetDTO;
import edu.harvard.iq.dataverse.api.dto.DatasetVersionDTO;
import edu.harvard.iq.dataverse.api.dto.FieldDTO;
@@ -1135,7 +1136,7 @@ public static void writeAccessRightsElement(XMLStreamWriter xmlw, DatasetVersion
writeRightsHeader(xmlw, language);
if (StringUtils.isNotBlank(datasetVersionDTO.getLicense())) {
if (StringUtils.containsIgnoreCase(datasetVersionDTO.getLicense(), "cc0")) {
- xmlw.writeAttribute("rightsURI", "https://creativecommons.org/publicdomain/zero/1.0/");
+ xmlw.writeAttribute("rightsURI", TermsOfUseAndAccess.CC0_URI);
if (StringUtils.isNotBlank(datasetVersionDTO.getTermsOfUse())) {
xmlw.writeCharacters(datasetVersionDTO.getTermsOfUse());
}
diff --git a/src/main/java/edu/harvard/iq/dataverse/util/bagit/BagGenerator.java b/src/main/java/edu/harvard/iq/dataverse/util/bagit/BagGenerator.java
index 7c3db485e47..0d079f4b172 100644
--- a/src/main/java/edu/harvard/iq/dataverse/util/bagit/BagGenerator.java
+++ b/src/main/java/edu/harvard/iq/dataverse/util/bagit/BagGenerator.java
@@ -177,7 +177,11 @@ public BagGenerator(OREMap oreMap, String dataciteXml) throws JsonSyntaxExceptio
public void setIgnoreHashes(boolean val) {
ignorehashes = val;
}
-
+
+ public void setDefaultCheckSumType(ChecksumType type) {
+ hashtype=type;
+ }
+
public static void println(String s) {
System.out.println(s);
System.out.flush();
@@ -531,18 +535,22 @@ private void processContainer(JsonObject item, String currentPath) throws IOExce
if (child.has(JsonLDTerm.checksum.getLabel())) {
ChecksumType childHashType = ChecksumType.fromString(
child.getAsJsonObject(JsonLDTerm.checksum.getLabel()).get("@type").getAsString());
- if (hashtype != null && !hashtype.equals(childHashType)) {
- logger.warning("Multiple hash values in use - not supported");
- }
- if (hashtype == null)
+ if (hashtype == null) {
+ //If one wasn't set as a default, pick up what the first child with one uses
hashtype = childHashType;
- childHash = child.getAsJsonObject(JsonLDTerm.checksum.getLabel()).get("@value").getAsString();
- if (checksumMap.containsValue(childHash)) {
- // Something else has this hash
- logger.warning("Duplicate/Collision: " + child.get("@id").getAsString() + " has SHA1 Hash: "
- + childHash);
- }
- checksumMap.put(childPath, childHash);
+ }
+ if (hashtype != null && !hashtype.equals(childHashType)) {
+ logger.warning("Multiple hash values in use - will calculate " + hashtype.toString()
+ + " hashes for " + childTitle);
+ } else {
+ childHash = child.getAsJsonObject(JsonLDTerm.checksum.getLabel()).get("@value").getAsString();
+ if (checksumMap.containsValue(childHash)) {
+ // Something else has this hash
+ logger.warning("Duplicate/Collision: " + child.get("@id").getAsString() + " has SHA1 Hash: "
+ + childHash);
+ }
+ checksumMap.put(childPath, childHash);
+ }
}
if ((hashtype == null) | ignorehashes) {
// Pick sha512 when ignoring hashes or none exist
@@ -816,7 +824,7 @@ private String generateInfoFile() {
} else {
info.append(
// FixMe - handle description having subfields better
- WordUtils.wrap(getSingleValue(aggregation.getAsJsonObject(descriptionTerm.getLabel()),
+ WordUtils.wrap(getSingleValue(aggregation.get(descriptionTerm.getLabel()),
descriptionTextTerm.getLabel()), 78, CRLF + " ", true));
info.append(CRLF);
@@ -862,22 +870,24 @@ private String generateInfoFile() {
* - the key to find a value(s) for
* @return - a single string
*/
- String getSingleValue(JsonObject jsonObject, String key) {
+ String getSingleValue(JsonElement jsonElement, String key) {
String val = "";
- if (jsonObject.get(key).isJsonPrimitive()) {
+ if(jsonElement.isJsonObject()) {
+ JsonObject jsonObject=jsonElement.getAsJsonObject();
val = jsonObject.get(key).getAsString();
- } else if (jsonObject.get(key).isJsonArray()) {
- Iterator iter = jsonObject.getAsJsonArray(key).iterator();
+ } else if (jsonElement.isJsonArray()) {
+
+ Iterator iter = jsonElement.getAsJsonArray().iterator();
ArrayList stringArray = new ArrayList();
while (iter.hasNext()) {
- stringArray.add(iter.next().getAsString());
+ stringArray.add(iter.next().getAsJsonObject().getAsJsonPrimitive(key).getAsString());
}
if (stringArray.size() > 1) {
- val = StringUtils.join((String[]) stringArray.toArray(), ",");
+ val = StringUtils.join(stringArray.toArray(), ",");
} else {
val = stringArray.get(0);
}
- logger.warning("Multiple values found for: " + key + ": " + val);
+ logger.fine("Multiple values found for: " + key + ": " + val);
}
return val;
}
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 38303eb1f41..b0abc3ce9a5 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
@@ -39,7 +39,7 @@ public class OREMap {
private Map localContext = new TreeMap();
private DatasetVersion version;
private Boolean excludeEmail = null;
-
+
public OREMap(DatasetVersion version) {
this.version = version;
}
@@ -56,6 +56,14 @@ public void writeOREMap(OutputStream outputStream) throws Exception {
}
public JsonObject getOREMap() throws Exception {
+ return getOREMap(false);
+ }
+
+ public JsonObject getOREMap(boolean aggregationOnly) throws Exception {
+ return getOREMapBuilder(aggregationOnly).build();
+ }
+
+ public JsonObjectBuilder getOREMapBuilder(boolean aggregationOnly) throws Exception {
//Set this flag if it wasn't provided
if(excludeEmail==null) {
@@ -79,7 +87,7 @@ public JsonObject getOREMap() throws Exception {
for (DatasetField field : fields) {
if (!field.isEmpty()) {
DatasetFieldType dfType = field.getDatasetFieldType();
- if(excludeEmail && DatasetFieldType.FieldType.EMAIL.equals(dfType.getFieldType())) {
+ if (excludeEmail && DatasetFieldType.FieldType.EMAIL.equals(dfType.getFieldType())) {
continue;
}
JsonLDTerm fieldName = getTermFor(dfType);
@@ -101,13 +109,13 @@ public JsonObject getOREMap() throws Exception {
for (DatasetField dsf : dscv.getChildDatasetFields()) {
DatasetFieldType dsft = dsf.getDatasetFieldType();
- if(excludeEmail && DatasetFieldType.FieldType.EMAIL.equals(dsft.getFieldType())) {
+ if (excludeEmail && DatasetFieldType.FieldType.EMAIL.equals(dsft.getFieldType())) {
continue;
}
// which may have multiple values
if (!dsf.isEmpty()) {
- // Add context entry
- //ToDo - also needs to recurse here?
+ // Add context entry
+ // ToDo - also needs to recurse here?
JsonLDTerm subFieldName = getTermFor(dfType, dsft);
if (subFieldName.inNamespace()) {
localContext.putIfAbsent(subFieldName.getNamespace().getPrefix(),
@@ -143,14 +151,14 @@ public JsonObject getOREMap() throws Exception {
Json.createArrayBuilder().add(JsonLDTerm.ore("Aggregation").getLabel())
.add(JsonLDTerm.schemaOrg("Dataset").getLabel()))
.add(JsonLDTerm.schemaOrg("version").getLabel(), version.getFriendlyVersionNumber())
- .add(JsonLDTerm.schemaOrg("datePublished").getLabel(), dataset.getPublicationDateFormattedYYYYMMDD())
.add(JsonLDTerm.schemaOrg("name").getLabel(), version.getTitle())
.add(JsonLDTerm.schemaOrg("dateModified").getLabel(), version.getLastUpdateTime().toString());
+ addIfNotNull(aggBuilder, JsonLDTerm.schemaOrg("datePublished"), dataset.getPublicationDateFormattedYYYYMMDD());
TermsOfUseAndAccess terms = version.getTermsOfUseAndAccess();
if (terms.getLicense() == TermsOfUseAndAccess.License.CC0) {
aggBuilder.add(JsonLDTerm.schemaOrg("license").getLabel(),
- "https://creativecommons.org/publicdomain/zero/1.0/");
+ TermsOfUseAndAccess.CC0_URI);
} else {
addIfNotNull(aggBuilder, JsonLDTerm.termsOfUse, terms.getTermsOfUse());
}
@@ -183,96 +191,101 @@ public JsonObject getOREMap() throws Exception {
// The aggregation aggregates aggregatedresources (Datafiles) which each have
// their own entry and metadata
JsonArrayBuilder aggResArrayBuilder = Json.createArrayBuilder();
+ if (!aggregationOnly) {
- for (FileMetadata fmd : version.getFileMetadatas()) {
- DataFile df = fmd.getDataFile();
- JsonObjectBuilder aggRes = Json.createObjectBuilder();
+ for (FileMetadata fmd : version.getFileMetadatas()) {
+ DataFile df = fmd.getDataFile();
+ JsonObjectBuilder aggRes = Json.createObjectBuilder();
- if (fmd.getDescription() != null) {
- aggRes.add(JsonLDTerm.schemaOrg("description").getLabel(), fmd.getDescription());
- } else {
- addIfNotNull(aggRes, JsonLDTerm.schemaOrg("description"), df.getDescription());
- }
- addIfNotNull(aggRes, JsonLDTerm.schemaOrg("name"), fmd.getLabel()); // "label" is the filename
- addIfNotNull(aggRes, JsonLDTerm.restricted, fmd.isRestricted());
- addIfNotNull(aggRes, JsonLDTerm.directoryLabel, fmd.getDirectoryLabel());
- addIfNotNull(aggRes, JsonLDTerm.schemaOrg("version"), fmd.getVersion());
- addIfNotNull(aggRes, JsonLDTerm.datasetVersionId, fmd.getDatasetVersion().getId());
- JsonArray catArray = null;
- if (fmd != null) {
- List categories = fmd.getCategoriesByName();
- if (categories.size() > 0) {
- JsonArrayBuilder jab = Json.createArrayBuilder();
- for (String s : categories) {
- jab.add(s);
+ if (fmd.getDescription() != null) {
+ aggRes.add(JsonLDTerm.schemaOrg("description").getLabel(), fmd.getDescription());
+ } else {
+ addIfNotNull(aggRes, JsonLDTerm.schemaOrg("description"), df.getDescription());
+ }
+ addIfNotNull(aggRes, JsonLDTerm.schemaOrg("name"), fmd.getLabel()); // "label" is the filename
+ addIfNotNull(aggRes, JsonLDTerm.restricted, fmd.isRestricted());
+ addIfNotNull(aggRes, JsonLDTerm.directoryLabel, fmd.getDirectoryLabel());
+ addIfNotNull(aggRes, JsonLDTerm.schemaOrg("version"), fmd.getVersion());
+ addIfNotNull(aggRes, JsonLDTerm.datasetVersionId, fmd.getDatasetVersion().getId());
+ JsonArray catArray = null;
+ if (fmd != null) {
+ List categories = fmd.getCategoriesByName();
+ if (categories.size() > 0) {
+ JsonArrayBuilder jab = Json.createArrayBuilder();
+ for (String s : categories) {
+ jab.add(s);
+ }
+ catArray = jab.build();
}
- catArray = jab.build();
}
+ addIfNotNull(aggRes, JsonLDTerm.categories, catArray);
+ // File DOI if it exists
+ String fileId = null;
+ String fileSameAs = null;
+ if (df.getGlobalId().asString().length() != 0) {
+ fileId = df.getGlobalId().asString();
+ fileSameAs = SystemConfig.getDataverseSiteUrlStatic()
+ + "/api/access/datafile/:persistentId?persistentId=" + fileId;
+ } else {
+ fileId = SystemConfig.getDataverseSiteUrlStatic() + "/file.xhtml?fileId=" + df.getId();
+ fileSameAs = SystemConfig.getDataverseSiteUrlStatic() + "/api/access/datafile/" + df.getId();
+ }
+ aggRes.add("@id", fileId);
+ aggRes.add(JsonLDTerm.schemaOrg("sameAs").getLabel(), fileSameAs);
+ fileArray.add(fileId);
+
+ aggRes.add("@type", JsonLDTerm.ore("AggregatedResource").getLabel());
+ addIfNotNull(aggRes, JsonLDTerm.schemaOrg("fileFormat"), df.getContentType());
+ addIfNotNull(aggRes, JsonLDTerm.filesize, df.getFilesize());
+ addIfNotNull(aggRes, JsonLDTerm.storageIdentifier, df.getStorageIdentifier());
+ addIfNotNull(aggRes, JsonLDTerm.originalFileFormat, df.getOriginalFileFormat());
+ addIfNotNull(aggRes, JsonLDTerm.originalFormatLabel, df.getOriginalFormatLabel());
+ addIfNotNull(aggRes, JsonLDTerm.UNF, df.getUnf());
+ addIfNotNull(aggRes, JsonLDTerm.rootDataFileId, df.getRootDataFileId());
+ addIfNotNull(aggRes, JsonLDTerm.previousDataFileId, df.getPreviousDataFileId());
+ JsonObject checksum = null;
+ // Add checksum. RDA recommends SHA-512
+ if (df.getChecksumType() != null && df.getChecksumValue() != null) {
+ checksum = Json.createObjectBuilder().add("@type", df.getChecksumType().toString())
+ .add("@value", df.getChecksumValue()).build();
+ aggRes.add(JsonLDTerm.checksum.getLabel(), checksum);
+ }
+ JsonArray tabTags = null;
+ JsonArrayBuilder jab = JsonPrinter.getTabularFileTags(df);
+ if (jab != null) {
+ tabTags = jab.build();
+ }
+ addIfNotNull(aggRes, JsonLDTerm.tabularTags, tabTags);
+ // Add latest resource to the array
+ aggResArrayBuilder.add(aggRes.build());
}
- addIfNotNull(aggRes, JsonLDTerm.categories, catArray);
- // File DOI if it exists
- String fileId = null;
- String fileSameAs = null;
- if (df.getGlobalId().asString().length() != 0) {
- fileId = df.getGlobalId().asString();
- fileSameAs = SystemConfig.getDataverseSiteUrlStatic()
- + "/api/access/datafile/:persistentId?persistentId=" + fileId;
- } else {
- fileId = SystemConfig.getDataverseSiteUrlStatic() + "/file.xhtml?fileId=" + df.getId();
- fileSameAs = SystemConfig.getDataverseSiteUrlStatic() + "/api/access/datafile/" + df.getId();
- }
- aggRes.add("@id", fileId);
- aggRes.add(JsonLDTerm.schemaOrg("sameAs").getLabel(), fileSameAs);
- fileArray.add(fileId);
-
- aggRes.add("@type", JsonLDTerm.ore("AggregatedResource").getLabel());
- addIfNotNull(aggRes, JsonLDTerm.schemaOrg("fileFormat"), df.getContentType());
- addIfNotNull(aggRes, JsonLDTerm.filesize, df.getFilesize());
- addIfNotNull(aggRes, JsonLDTerm.storageIdentifier, df.getStorageIdentifier());
- addIfNotNull(aggRes, JsonLDTerm.originalFileFormat, df.getOriginalFileFormat());
- addIfNotNull(aggRes, JsonLDTerm.originalFormatLabel, df.getOriginalFormatLabel());
- addIfNotNull(aggRes, JsonLDTerm.UNF, df.getUnf());
- addIfNotNull(aggRes, JsonLDTerm.rootDataFileId, df.getRootDataFileId());
- addIfNotNull(aggRes, JsonLDTerm.previousDataFileId, df.getPreviousDataFileId());
- JsonObject checksum = null;
- // Add checksum. RDA recommends SHA-512
- if (df.getChecksumType() != null && df.getChecksumValue() != null) {
- checksum = Json.createObjectBuilder().add("@type", df.getChecksumType().toString())
- .add("@value", df.getChecksumValue()).build();
- aggRes.add(JsonLDTerm.checksum.getLabel(), checksum);
- }
- JsonArray tabTags = null;
- JsonArrayBuilder jab = JsonPrinter.getTabularFileTags(df);
- if (jab != null) {
- tabTags = jab.build();
- }
- addIfNotNull(aggRes, JsonLDTerm.tabularTags, tabTags);
- //Add latest resource to the array
- aggResArrayBuilder.add(aggRes.build());
}
// Build the '@context' object for json-ld based on the localContext entries
JsonObjectBuilder contextBuilder = Json.createObjectBuilder();
for (Entry e : localContext.entrySet()) {
contextBuilder.add(e.getKey(), e.getValue());
}
- // Now create the overall map object with it's metadata
- JsonObject oremap = Json.createObjectBuilder()
- .add(JsonLDTerm.dcTerms("modified").getLabel(), LocalDate.now().toString())
- .add(JsonLDTerm.dcTerms("creator").getLabel(),
- BrandingUtil.getInstallationBrandName())
- .add("@type", JsonLDTerm.ore("ResourceMap").getLabel())
- // Define an id for the map itself (separate from the @id of the dataset being
- // described
- .add("@id",
- SystemConfig.getDataverseSiteUrlStatic() + "/api/datasets/export?exporter="
- + OAI_OREExporter.NAME + "&persistentId=" + id)
- // Add the aggregation (Dataset) itself to the map.
- .add(JsonLDTerm.ore("describes").getLabel(),
- aggBuilder.add(JsonLDTerm.ore("aggregates").getLabel(), aggResArrayBuilder.build())
- .add(JsonLDTerm.schemaOrg("hasPart").getLabel(), fileArray.build()).build())
- // and finally add the context
- .add("@context", contextBuilder.build()).build();
- return oremap;
+ if (aggregationOnly) {
+ return aggBuilder.add("@context", contextBuilder.build());
+ } else {
+ // Now create the overall map object with it's metadata
+ JsonObjectBuilder oremapBuilder = Json.createObjectBuilder()
+ .add(JsonLDTerm.dcTerms("modified").getLabel(), LocalDate.now().toString())
+ .add(JsonLDTerm.dcTerms("creator").getLabel(), BrandingUtil.getInstallationBrandName())
+ .add("@type", JsonLDTerm.ore("ResourceMap").getLabel())
+ // Define an id for the map itself (separate from the @id of the dataset being
+ // described
+ .add("@id",
+ SystemConfig.getDataverseSiteUrlStatic() + "/api/datasets/export?exporter="
+ + OAI_OREExporter.NAME + "&persistentId=" + id)
+ // Add the aggregation (Dataset) itself to the map.
+ .add(JsonLDTerm.ore("describes").getLabel(),
+ aggBuilder.add(JsonLDTerm.ore("aggregates").getLabel(), aggResArrayBuilder.build())
+ .add(JsonLDTerm.schemaOrg("hasPart").getLabel(), fileArray.build()).build())
+ // and finally add the context
+ .add("@context", contextBuilder.build());
+ return oremapBuilder;
+ }
}
/*
@@ -353,7 +366,7 @@ private JsonLDTerm getTermFor(DatasetFieldType dsft) {
namespaceUri = SystemConfig.getDataverseSiteUrlStatic() + "/schema/" + dsft.getMetadataBlock().getName()
+ "#";
}
- JsonLDNamespace blockNamespace = new JsonLDNamespace(dsft.getMetadataBlock().getName(), namespaceUri);
+ JsonLDNamespace blockNamespace = JsonLDNamespace.defineNamespace(dsft.getMetadataBlock().getName(), namespaceUri);
return new JsonLDTerm(blockNamespace, dsft.getTitle());
}
}
@@ -369,7 +382,7 @@ private JsonLDTerm getTermFor(DatasetFieldType dfType, DatasetFieldType dsft) {
+ dfType.getMetadataBlock().getName() + "/";
}
subFieldNamespaceUri = subFieldNamespaceUri + dfType.getName() + "#";
- JsonLDNamespace fieldNamespace = new JsonLDNamespace(dfType.getName(), subFieldNamespaceUri);
+ JsonLDNamespace fieldNamespace = JsonLDNamespace.defineNamespace(dfType.getName(), subFieldNamespaceUri);
return new JsonLDTerm(fieldNamespace, dsft.getTitle());
}
}
diff --git a/src/main/java/edu/harvard/iq/dataverse/util/json/JSONLDUtil.java b/src/main/java/edu/harvard/iq/dataverse/util/json/JSONLDUtil.java
new file mode 100644
index 00000000000..965251e7124
--- /dev/null
+++ b/src/main/java/edu/harvard/iq/dataverse/util/json/JSONLDUtil.java
@@ -0,0 +1,914 @@
+package edu.harvard.iq.dataverse.util.json;
+
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.sql.Timestamp;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Map.Entry;
+import java.util.TreeMap;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.json.Json;
+import javax.json.JsonArray;
+import javax.json.JsonObject;
+import javax.json.JsonObjectBuilder;
+import javax.json.JsonString;
+import javax.json.JsonValue;
+import javax.json.JsonWriter;
+import javax.json.JsonWriterFactory;
+import javax.json.JsonValue.ValueType;
+import javax.json.stream.JsonGenerator;
+import javax.ws.rs.BadRequestException;
+
+import org.apache.commons.lang3.StringUtils;
+
+import com.apicatalog.jsonld.JsonLd;
+import com.apicatalog.jsonld.api.JsonLdError;
+import com.apicatalog.jsonld.document.JsonDocument;
+
+import edu.harvard.iq.dataverse.ControlledVocabularyValue;
+import edu.harvard.iq.dataverse.Dataset;
+import edu.harvard.iq.dataverse.DatasetField;
+import edu.harvard.iq.dataverse.DatasetFieldCompoundValue;
+import edu.harvard.iq.dataverse.DatasetFieldServiceBean;
+import edu.harvard.iq.dataverse.DatasetFieldType;
+import edu.harvard.iq.dataverse.DatasetFieldValue;
+import edu.harvard.iq.dataverse.DatasetVersion;
+import edu.harvard.iq.dataverse.GlobalId;
+import edu.harvard.iq.dataverse.MetadataBlock;
+import edu.harvard.iq.dataverse.MetadataBlockServiceBean;
+import edu.harvard.iq.dataverse.TermsOfUseAndAccess;
+import edu.harvard.iq.dataverse.TermsOfUseAndAccess.License;
+import edu.harvard.iq.dataverse.DatasetVersion.VersionState;
+import edu.harvard.iq.dataverse.util.bagit.OREMap;
+
+public class JSONLDUtil {
+
+ private static final Logger logger = Logger.getLogger(JSONLDUtil.class.getCanonicalName());
+
+ /*
+ * private static Map populateContext(JsonValue json) {
+ * Map context = new TreeMap(); if (json
+ * instanceof JsonArray) { logger.warning("Array @context not yet supported"); }
+ * else { for (String key : ((JsonObject) json).keySet()) {
+ * context.putIfAbsent(key, ((JsonObject) json).getString(key)); } } return
+ * context; }
+ */
+
+ public static JsonObject getContext(Map contextMap) {
+ JsonObjectBuilder contextBuilder = Json.createObjectBuilder();
+ for (Entry e : contextMap.entrySet()) {
+ contextBuilder.add(e.getKey(), e.getValue());
+ }
+ return contextBuilder.build();
+ }
+
+ public static Dataset updateDatasetMDFromJsonLD(Dataset ds, String jsonLDBody,
+ MetadataBlockServiceBean metadataBlockSvc, DatasetFieldServiceBean datasetFieldSvc, boolean append, boolean migrating) {
+
+ DatasetVersion dsv = new DatasetVersion();
+
+ JsonObject jsonld = decontextualizeJsonLD(jsonLDBody);
+ String id = null;
+ try {
+ id=jsonld.getString("@id");
+ } catch (NullPointerException npe) {
+ //Do nothing - a null value and other invalid values will be caught in parsing
+ }
+ Optional maybePid = GlobalId.parse(id);
+ if (maybePid.isPresent()) {
+ ds.setGlobalId(maybePid.get());
+ } else {
+ if(migrating) {
+ // unparsable PID passed. Terminate.
+ throw new BadRequestException("Cannot parse the @id. Make sure it is in valid form - see Dataverse Native API documentation.");
+ }
+ }
+
+ dsv = updateDatasetVersionMDFromJsonLD(dsv, jsonld, metadataBlockSvc, datasetFieldSvc, append, migrating);
+ dsv.setDataset(ds);
+
+ List versions = new ArrayList<>(1);
+ versions.add(dsv);
+
+ ds.setVersions(versions);
+ if (jsonld.containsKey(JsonLDTerm.schemaOrg("dateModified").getUrl())) {
+ String dateString = jsonld.getString(JsonLDTerm.schemaOrg("dateModified").getUrl());
+ LocalDateTime dateTime = getDateTimeFrom(dateString);
+ ds.setModificationTime(Timestamp.valueOf(dateTime));
+ }
+ try {
+ if (logger.isLoggable(Level.FINE)) {
+ if (ds.getModificationTime() == null) {
+ // Create (migrating==false case - modification time will be set in the create
+ // call, but we need a non-null value to reuse the OREMap method for logging
+ // here
+ ds.setModificationTime(Timestamp.from(Instant.now()));
+ }
+ logger.fine("Output dsv: " + new OREMap(dsv, false).getOREMap().toString());
+ }
+ } catch (Exception e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ return ds;
+ }
+
+ public static DatasetVersion updateDatasetVersionMDFromJsonLD(DatasetVersion dsv, String jsonLDBody,
+ MetadataBlockServiceBean metadataBlockSvc, DatasetFieldServiceBean datasetFieldSvc, boolean append, boolean migrating) {
+ JsonObject jsonld = decontextualizeJsonLD(jsonLDBody);
+ return updateDatasetVersionMDFromJsonLD(dsv, jsonld, metadataBlockSvc, datasetFieldSvc, append, migrating);
+ }
+
+ /**
+ *
+ * @param dsv
+ * @param jsonld
+ * @param metadataBlockSvc
+ * @param datasetFieldSvc
+ * @param append - if append, will add new top level field values for
+ * multi-valued fields, if true and field type isn't
+ * multiple, will fail. if false will replace all
+ * value(s) for fields found in the json-ld.
+ * @return
+ */
+ public static DatasetVersion updateDatasetVersionMDFromJsonLD(DatasetVersion dsv, JsonObject jsonld,
+ MetadataBlockServiceBean metadataBlockSvc, DatasetFieldServiceBean datasetFieldSvc, boolean append, boolean migrating) {
+
+ //Assume draft to start
+ dsv.setVersionState(VersionState.DRAFT);
+
+ populateFieldTypeMap(metadataBlockSvc);
+
+ // get existing ones?
+ List dsfl = dsv.getDatasetFields();
+ Map fieldByTypeMap = new HashMap();
+ for (DatasetField dsf : dsfl) {
+ if (fieldByTypeMap.containsKey(dsf.getDatasetFieldType())) {
+ // May have multiple values per field, but not multiple fields of one type?
+ logger.warning("Multiple fields of type " + dsf.getDatasetFieldType().getName());
+ }
+ fieldByTypeMap.put(dsf.getDatasetFieldType(), dsf);
+ }
+
+ TermsOfUseAndAccess terms = (dsv.getTermsOfUseAndAccess()!=null) ? dsv.getTermsOfUseAndAccess().copyTermsOfUseAndAccess() : new TermsOfUseAndAccess();
+
+ for (String key : jsonld.keySet()) {
+ if (!key.equals("@context")) {
+ if (dsftMap.containsKey(key)) {
+
+ DatasetFieldType dsft = dsftMap.get(key);
+ DatasetField dsf = null;
+ if (fieldByTypeMap.containsKey(dsft)) {
+ dsf = fieldByTypeMap.get(dsft);
+ // If there's an existing field, we use it with append and remove it for !append
+ // unless it's multiple
+ if (!append && !dsft.isAllowMultiples()) {
+ dsfl.remove(dsf);
+ dsf=null;
+ }
+ }
+ if (dsf == null) {
+ dsf = new DatasetField();
+ dsfl.add(dsf);
+ dsf.setDatasetFieldType(dsft);
+ }
+
+ // Todo - normalize object vs. array
+ JsonArray valArray = getValues(jsonld.get(key), dsft.isAllowMultiples(), dsft.getName());
+
+ addField(dsf, valArray, dsft, datasetFieldSvc, append);
+
+ } else {
+
+ if (key.equals(JsonLDTerm.schemaOrg("datePublished").getUrl())&& migrating && !append) {
+ dsv.setVersionState(VersionState.RELEASED);
+ } else if (key.equals(JsonLDTerm.schemaOrg("version").getUrl())&& migrating && !append) {
+ String friendlyVersion = jsonld.getString(JsonLDTerm.schemaOrg("version").getUrl());
+ int index = friendlyVersion.indexOf(".");
+ if (index > 0) {
+ dsv.setVersionNumber(Long.parseLong(friendlyVersion.substring(0, index)));
+ dsv.setMinorVersionNumber(Long.parseLong(friendlyVersion.substring(index + 1)));
+ }
+ } else if (key.equals(JsonLDTerm.schemaOrg("license").getUrl())) {
+ //Special handling for license
+ if (!append || !isSet(terms, key)) {
+ // Mirror rules from SwordServiceBean
+ if (jsonld.containsKey(JsonLDTerm.termsOfUse.getUrl())) {
+ throw new BadRequestException(
+ "Cannot specify " + JsonLDTerm.schemaOrg("license").getUrl() + " and "
+ + JsonLDTerm.termsOfUse.getUrl());
+ }
+ setSemTerm(terms, key, TermsOfUseAndAccess.defaultLicense);
+ } else {
+ throw new BadRequestException(
+ "Can't append to a single-value field that already has a value: "
+ + JsonLDTerm.schemaOrg("license").getUrl());
+ }
+
+ } else if (datasetTerms.contains(key)) {
+ // Other Dataset-level TermsOfUseAndAccess
+ if (!append || !isSet(terms, key)) {
+ setSemTerm(terms, key, jsonld.getString(key));
+ } else {
+ throw new BadRequestException(
+ "Can't append to a single-value field that already has a value: " + key);
+ }
+ } else if (key.equals(JsonLDTerm.fileTermsOfAccess.getUrl())) {
+ // Other DataFile-level TermsOfUseAndAccess
+ JsonObject fAccessObject = jsonld.getJsonObject(JsonLDTerm.fileTermsOfAccess.getUrl());
+ for (String fileKey : fAccessObject.keySet()) {
+ if (datafileTerms.contains(fileKey)) {
+ if (!append || !isSet(terms, fileKey)) {
+ if (fileKey.equals(JsonLDTerm.fileRequestAccess.getUrl())) {
+ setSemTerm(terms, fileKey, fAccessObject.getBoolean(fileKey));
+ } else {
+ setSemTerm(terms, fileKey, fAccessObject.getString(fileKey));
+ }
+ } else {
+ throw new BadRequestException(
+ "Can't append to a single-value field that already has a value: "
+ + fileKey);
+ }
+ }
+ }
+ }
+ dsv.setTermsOfUseAndAccess(terms);
+ // ToDo: support Dataverse location metadata? e.g. move to new dataverse?
+ // re: JsonLDTerm.schemaOrg("includedInDataCatalog")
+ }
+
+ }
+ }
+
+ dsv.setDatasetFields(dsfl);
+
+ return dsv;
+ }
+ /**
+ *
+ * @param dsv
+ * @param jsonLDBody
+ * @param metadataBlockService
+ * @param datasetFieldSvc
+ * @param b
+ * @param c
+ * @return
+ */
+ public static DatasetVersion deleteDatasetVersionMDFromJsonLD(DatasetVersion dsv, String jsonLDBody,
+ MetadataBlockServiceBean metadataBlockSvc, DatasetFieldServiceBean datasetFieldSvc) {
+ logger.fine("deleteDatasetVersionMD");
+ JsonObject jsonld = decontextualizeJsonLD(jsonLDBody);
+ //All terms are now URIs
+ //Setup dsftMap - URI to datasetFieldType map
+ populateFieldTypeMap(metadataBlockSvc);
+
+
+ //Another map - from datasetFieldType to an existing field in the dataset
+ List dsfl = dsv.getDatasetFields();
+ Map fieldByTypeMap = new HashMap();
+ for (DatasetField dsf : dsfl) {
+ if (fieldByTypeMap.containsKey(dsf.getDatasetFieldType())) {
+ // May have multiple values per field, but not multiple fields of one type?
+ logger.warning("Multiple fields of type " + dsf.getDatasetFieldType().getName());
+ }
+ fieldByTypeMap.put(dsf.getDatasetFieldType(), dsf);
+ }
+
+ TermsOfUseAndAccess terms = dsv.getTermsOfUseAndAccess().copyTermsOfUseAndAccess();
+
+ //Iterate through input json
+ for (String key : jsonld.keySet()) {
+ //Skip context (shouldn't be present with decontextualize used above)
+ if (!key.equals("@context")) {
+ if (dsftMap.containsKey(key)) {
+ //THere's a field type with theis URI
+ DatasetFieldType dsft = dsftMap.get(key);
+ DatasetField dsf = null;
+ if (fieldByTypeMap.containsKey(dsft)) {
+ //There's a field of this type
+ dsf = fieldByTypeMap.get(dsft);
+
+ // Todo - normalize object vs. array
+ JsonArray valArray = getValues(jsonld.get(key), dsft.isAllowMultiples(), dsft.getName());
+ logger.fine("Deleting: " + key + " : " + valArray.toString());
+ DatasetField dsf2 = getReplacementField(dsf, valArray);
+ if(dsf2 == null) {
+ //Exact match - remove the field
+ dsfl.remove(dsf);
+ } else {
+ //Partial match - some values of a multivalue field match, so keep the remaining values
+ dsfl.remove(dsf);
+ dsfl.add(dsf2);
+ }
+ }
+ } else {
+ // Internal/non-metadatablock terms
+ boolean found=false;
+ if (key.equals(JsonLDTerm.schemaOrg("license").getUrl())) {
+ if(jsonld.getString(key).equals(TermsOfUseAndAccess.CC0_URI)) {
+ setSemTerm(terms, key, TermsOfUseAndAccess.License.NONE);
+ } else {
+ throw new BadRequestException(
+ "Term: " + key + " with value: " + jsonld.getString(key) + " not found.");
+ }
+ found=true;
+ } else if (datasetTerms.contains(key)) {
+ if(!deleteIfSemTermMatches(terms, key, jsonld.get(key))) {
+ throw new BadRequestException(
+ "Term: " + key + " with value: " + jsonld.getString(key) + " not found.");
+ }
+ found=true;
+ } else if (key.equals(JsonLDTerm.fileTermsOfAccess.getUrl())) {
+ JsonObject fAccessObject = jsonld.getJsonObject(JsonLDTerm.fileTermsOfAccess.getUrl());
+ for (String fileKey : fAccessObject.keySet()) {
+ if (datafileTerms.contains(fileKey)) {
+ if(!deleteIfSemTermMatches(terms, key, jsonld.get(key))) {
+ throw new BadRequestException(
+ "Term: " + key + " with value: " + jsonld.getString(key) + " not found.");
+ }
+ found=true;
+ }
+ }
+ } else if(!found) {
+ throw new BadRequestException(
+ "Term: " + key + " not found.");
+ }
+
+ dsv.setTermsOfUseAndAccess(terms);
+ }
+ }
+ }
+ dsv.setDatasetFields(dsfl);
+ return dsv;
+ }
+
+ /**
+ *
+ * @param dsf
+ * @param valArray
+ * @return null if exact match, otherwise return a field without the value to be deleted
+ */
+ private static DatasetField getReplacementField(DatasetField dsf, JsonArray valArray) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ private static void addField(DatasetField dsf, JsonArray valArray, DatasetFieldType dsft,
+ DatasetFieldServiceBean datasetFieldSvc, boolean append) {
+
+ if (append && !dsft.isAllowMultiples()) {
+ if ((dsft.isCompound() && !dsf.getDatasetFieldCompoundValues().isEmpty())
+ || (dsft.isAllowControlledVocabulary() && !dsf.getControlledVocabularyValues().isEmpty())
+ || !dsf.getDatasetFieldValues().isEmpty()) {
+ throw new BadRequestException(
+ "Can't append to a single-value field that already has a value: " + dsft.getName());
+ }
+ }
+ logger.fine("Name: " + dsft.getName());
+ logger.fine("val: " + valArray.toString());
+ logger.fine("Compound: " + dsft.isCompound());
+ logger.fine("CV: " + dsft.isAllowControlledVocabulary());
+
+ if (dsft.isCompound()) {
+ /*
+ * List vals = parseCompoundValue(type,
+ * jsonld.get(key),testType); for (DatasetFieldCompoundValue dsfcv : vals) {
+ * dsfcv.setParentDatasetField(ret); } dsf.setDatasetFieldCompoundValues(vals);
+ */
+ List cvList = dsf.getDatasetFieldCompoundValues();
+ if (!cvList.isEmpty()) {
+ if (!append) {
+ cvList.clear();
+ } else if (!dsft.isAllowMultiples() && cvList.size() == 1) {
+ // Trying to append but only a single value is allowed (and there already is
+ // one)
+ // (and we don't currently support appending new fields within a compound value)
+ throw new BadRequestException(
+ "Append with compound field with single value not yet supported: " + dsft.getDisplayName());
+ }
+ }
+
+ List vals = new LinkedList<>();
+ for (JsonValue val : valArray) {
+ if (!(val instanceof JsonObject)) {
+ throw new BadRequestException(
+ "Compound field values must be JSON objects, field: " + dsft.getName());
+ }
+ DatasetFieldCompoundValue cv = null;
+
+ cv = new DatasetFieldCompoundValue();
+ cv.setDisplayOrder(cvList.size());
+ cvList.add(cv);
+ cv.setParentDatasetField(dsf);
+
+ JsonObject obj = (JsonObject) val;
+ for (String childKey : obj.keySet()) {
+ if (dsftMap.containsKey(childKey)) {
+ DatasetFieldType childft = dsftMap.get(childKey);
+ if (!dsft.getChildDatasetFieldTypes().contains(childft)) {
+ throw new BadRequestException(
+ "Compound field " + dsft.getName() + "can't include term " + childKey);
+ }
+ DatasetField childDsf = new DatasetField();
+ cv.getChildDatasetFields().add(childDsf);
+ childDsf.setDatasetFieldType(childft);
+ childDsf.setParentDatasetFieldCompoundValue(cv);
+
+ JsonArray childValArray = getValues(obj.get(childKey), childft.isAllowMultiples(),
+ childft.getName());
+ addField(childDsf, childValArray, childft, datasetFieldSvc, append);
+ }
+ }
+ }
+
+ } else if (dsft.isControlledVocabulary()) {
+
+ List vals = dsf.getControlledVocabularyValues();
+ for (JsonString strVal : valArray.getValuesAs(JsonString.class)) {
+ String strValue = strVal.getString();
+ ControlledVocabularyValue cvv = datasetFieldSvc
+ .findControlledVocabularyValueByDatasetFieldTypeAndStrValue(dsft, strValue, true);
+ if (cvv == null) {
+ throw new BadRequestException(
+ "Unknown value for Controlled Vocab Field: " + dsft.getName() + " : " + strValue);
+ }
+ // Only add value to the list if it is not a duplicate
+ if (strValue.equals("Other")) {
+ System.out.println("vals = " + vals + ", contains: " + vals.contains(cvv));
+ }
+ if (!vals.contains(cvv)) {
+ if (vals.size() > 0) {
+ cvv.setDisplayOrder(vals.size());
+ }
+ vals.add(cvv);
+ cvv.setDatasetFieldType(dsft);
+ }
+ }
+ dsf.setControlledVocabularyValues(vals);
+
+ } else {
+ List vals = dsf.getDatasetFieldValues();
+
+ for (JsonString strVal : valArray.getValuesAs(JsonString.class)) {
+ String strValue = strVal.getString();
+ DatasetFieldValue datasetFieldValue = new DatasetFieldValue();
+
+ datasetFieldValue.setDisplayOrder(vals.size());
+ datasetFieldValue.setValue(strValue.trim());
+ vals.add(datasetFieldValue);
+ datasetFieldValue.setDatasetField(dsf);
+
+ }
+ dsf.setDatasetFieldValues(vals);
+ }
+ }
+
+ private static JsonArray getValues(JsonValue val, boolean allowMultiples, String name) {
+ JsonArray valArray = null;
+ if (val instanceof JsonArray) {
+ if ((((JsonArray) val).size() > 1) && !allowMultiples) {
+ throw new BadRequestException("Array for single value notsupported: " + name);
+ } else {
+ valArray = (JsonArray) val;
+ }
+ } else {
+ valArray = Json.createArrayBuilder().add(val).build();
+ }
+ return valArray;
+ }
+
+ static Map localContext = new TreeMap();
+ static Map dsftMap = new TreeMap();
+
+ private static void populateFieldTypeMap(MetadataBlockServiceBean metadataBlockSvc) {
+ if (dsftMap.isEmpty()) {
+
+ List mdbList = metadataBlockSvc.listMetadataBlocks();
+
+ for (MetadataBlock mdb : mdbList) {
+ boolean blockHasUri = mdb.getNamespaceUri() != null;
+ for (DatasetFieldType dsft : mdb.getDatasetFieldTypes()) {
+ if (dsft.getUri() != null) {
+ dsftMap.put(dsft.getUri(), dsft);
+ }
+ if (blockHasUri) {
+ if (dsft.getParentDatasetFieldType() != null) {
+ // ToDo - why not getName for child type? Would have to fix in ORE generation
+ // code and handle legacy bags
+ dsftMap.put(mdb.getNamespaceUri() + dsft.getParentDatasetFieldType().getName() + "#"
+ + dsft.getTitle(), dsft);
+ } else {
+ dsftMap.put(mdb.getNamespaceUri() + dsft.getTitle(), dsft);
+ }
+ }
+ }
+ }
+ logger.fine("DSFT Map: " + String.join(", ", dsftMap.keySet()));
+ }
+ }
+
+ public static void populateContext(MetadataBlockServiceBean metadataBlockSvc) {
+ if (localContext.isEmpty()) {
+
+ List mdbList = metadataBlockSvc.listMetadataBlocks();
+
+ for (MetadataBlock mdb : mdbList) {
+ boolean blockHasUri = mdb.getNamespaceUri() != null;
+ if (blockHasUri) {
+ JsonLDNamespace.defineNamespace(mdb.getName(), mdb.getNamespaceUri());
+ }
+ for (DatasetFieldType dsft : mdb.getDatasetFieldTypes()) {
+ if ((dsft.getUri() != null) && !JsonLDNamespace.isInNamespace(dsft.getUri())) {
+ //Add term if uri exists and it's not in one of the namespaces already defined
+ localContext.putIfAbsent(dsft.getName(), dsft.getUri());
+ }
+ }
+ }
+ JsonLDNamespace.addNamespacesToContext(localContext);
+ logger.fine("LocalContext keys: " + String.join(", ", localContext.keySet()));
+ }
+ }
+
+ public static JsonObject decontextualizeJsonLD(String jsonLDString) {
+ logger.fine(jsonLDString);
+ try (StringReader rdr = new StringReader(jsonLDString)) {
+
+ // Use JsonLd to expand/compact to localContext
+ JsonObject jsonld = Json.createReader(rdr).readObject();
+ JsonDocument doc = JsonDocument.of(jsonld);
+ JsonArray array = null;
+ try {
+ array = JsonLd.expand(doc).get();
+ jsonld = JsonLd.compact(JsonDocument.of(array), JsonDocument.of(Json.createObjectBuilder().build()))
+ .get();
+ // jsonld = array.getJsonObject(0);
+ logger.fine("Decontextualized object: " + jsonld);
+ return jsonld;
+ } catch (JsonLdError e) {
+ System.out.println(e.getMessage());
+ return null;
+ }
+ }
+ }
+
+ private static JsonObject recontextualizeJsonLD(JsonObject jsonldObj, MetadataBlockServiceBean metadataBlockSvc) {
+
+ populateContext(metadataBlockSvc);
+
+ // Use JsonLd to expand/compact to localContext
+ JsonDocument doc = JsonDocument.of(jsonldObj);
+ JsonArray array = null;
+ try {
+ array = JsonLd.expand(doc).get();
+
+ jsonldObj = JsonLd.compact(JsonDocument.of(array), JsonDocument.of(JSONLDUtil.getContext(localContext)))
+ .get();
+ logger.fine("Compacted: " + jsonldObj.toString());
+ return jsonldObj;
+ } catch (JsonLdError e) {
+ System.out.println(e.getMessage());
+ return null;
+ }
+ }
+
+ public static String prettyPrint(JsonValue val) {
+ StringWriter sw = new StringWriter();
+ Map properties = new HashMap<>(1);
+ properties.put(JsonGenerator.PRETTY_PRINTING, true);
+ JsonWriterFactory writerFactory = Json.createWriterFactory(properties);
+ JsonWriter jsonWriter = writerFactory.createWriter(sw);
+ jsonWriter.write(val);
+ jsonWriter.close();
+ return sw.toString();
+ }
+
+//Modified from https://stackoverflow.com/questions/3389348/parse-any-date-in-java
+
+ private static final Map DATE_FORMAT_REGEXPS = new HashMap() {
+ {
+ put("^\\d{8}$", "yyyyMMdd");
+ put("^\\d{1,2}-\\d{1,2}-\\d{4}$", "dd-MM-yyyy");
+ put("^\\d{4}-\\d{1,2}-\\d{1,2}$", "yyyy-MM-dd");
+ put("^\\d{1,2}/\\d{1,2}/\\d{4}$", "MM/dd/yyyy");
+ put("^\\d{4}/\\d{1,2}/\\d{1,2}$", "yyyy/MM/dd");
+ put("^\\d{1,2}\\s[a-z]{3}\\s\\d{4}$", "dd MMM yyyy");
+ put("^\\d{1,2}\\s[a-z]{4,}\\s\\d{4}$", "dd MMMM yyyy");
+ }
+ };
+
+ private static final Map DATETIME_FORMAT_REGEXPS = new HashMap() {
+ {
+ put("^\\d{12}$", "yyyyMMddHHmm");
+ put("^\\d{8}\\s\\d{4}$", "yyyyMMdd HHmm");
+ put("^\\d{1,2}-\\d{1,2}-\\d{4}\\s\\d{1,2}:\\d{2}$", "dd-MM-yyyy HH:mm");
+ put("^\\d{4}-\\d{1,2}-\\d{1,2}\\s\\d{1,2}:\\d{2}$", "yyyy-MM-dd HH:mm");
+ put("^\\d{1,2}/\\d{1,2}/\\d{4}\\s\\d{1,2}:\\d{2}$", "MM/dd/yyyy HH:mm");
+ put("^\\d{4}/\\d{1,2}/\\d{1,2}\\s\\d{1,2}:\\d{2}$", "yyyy/MM/dd HH:mm");
+ put("^\\d{1,2}\\s[a-z]{3}\\s\\d{4}\\s\\d{1,2}:\\d{2}$", "dd MMM yyyy HH:mm");
+ put("^\\d{1,2}\\s[a-z]{4,}\\s\\d{4}\\s\\d{1,2}:\\d{2}$", "dd MMMM yyyy HH:mm");
+ put("^\\d{14}$", "yyyyMMddHHmmss");
+ put("^\\d{8}\\s\\d{6}$", "yyyyMMdd HHmmss");
+ put("^\\d{1,2}-\\d{1,2}-\\d{4}\\s\\d{1,2}:\\d{2}:\\d{2}$", "dd-MM-yyyy HH:mm:ss");
+ put("^\\d{4}-\\d{1,2}-\\d{1,2}\\s\\d{1,2}:\\d{2}:\\d{2}$", "yyyy-MM-dd HH:mm:ss");
+ put("^\\d{1,2}/\\d{1,2}/\\d{4}\\s\\d{1,2}:\\d{2}:\\d{2}$", "MM/dd/yyyy HH:mm:ss");
+ put("^\\d{4}/\\d{1,2}/\\d{1,2}\\s\\d{1,2}:\\d{2}:\\d{2}$", "yyyy/MM/dd HH:mm:ss");
+ put("^\\d{1,2}\\s[a-z]{3}\\s\\d{4}\\s\\d{1,2}:\\d{2}:\\d{2}$", "dd MMM yyyy HH:mm:ss");
+ put("^\\d{1,2}\\s[a-z]{4,}\\s\\d{4}\\s\\d{1,2}:\\d{2}:\\d{2}$", "dd MMMM yyyy HH:mm:ss");
+ put("^\\d{4}-\\d{1,2}-\\d{1,2}\\s\\d{1,2}:\\d{2}:\\d{2}\\.\\d{3}$", "yyyy-MM-dd HH:mm:ss.SSS");
+ put("^[a-z,A-Z]{3}\\s[a-z,A-Z]{3}\\s\\d{1,2}\\s\\d{1,2}:\\d{2}:\\d{2}\\s[a-z,A-Z]{3}\\s\\d{4}$",
+ "EEE MMM dd HH:mm:ss zzz yyyy"); // Wed Sep 23 19:33:46 UTC 2020
+
+ }
+ };
+
+ /**
+ * Determine DateTimeFormatter pattern matching with the given date string.
+ * Returns null if format is unknown. You can simply extend DateUtil with more
+ * formats if needed.
+ *
+ * @param dateString The date string to determine the SimpleDateFormat pattern
+ * for.
+ * @return The matching SimpleDateFormat pattern, or null if format is unknown.
+ * @see SimpleDateFormat
+ */
+ public static DateTimeFormatter determineDateTimeFormat(String dateString) {
+ for (String regexp : DATETIME_FORMAT_REGEXPS.keySet()) {
+ if (dateString.toLowerCase().matches(regexp)) {
+ return DateTimeFormatter.ofPattern(DATETIME_FORMAT_REGEXPS.get(regexp));
+ }
+ }
+ logger.warning("Unknown datetime format: " + dateString);
+ return null; // Unknown format.
+ }
+
+ public static DateTimeFormatter determineDateFormat(String dateString) {
+ for (String regexp : DATE_FORMAT_REGEXPS.keySet()) {
+ if (dateString.toLowerCase().matches(regexp)) {
+ return DateTimeFormatter.ofPattern(DATE_FORMAT_REGEXPS.get(regexp));
+ }
+ }
+ logger.warning("Unknown date format: " + dateString);
+ return null; // Unknown format.
+ }
+
+ public static LocalDateTime getDateTimeFrom(String dateString) {
+ DateTimeFormatter dtf = determineDateTimeFormat(dateString);
+ if (dtf != null) {
+ return LocalDateTime.parse(dateString, dtf);
+ } else {
+ dtf = determineDateFormat(dateString);
+ if (dtf != null) {
+ return LocalDate.parse(dateString, dtf).atStartOfDay();
+ }
+ }
+
+ return null;
+ }
+
+ // Convenience methods for TermsOfUseAndAccess
+
+ public static final List datasetTerms = new ArrayList(Arrays.asList(
+ "https://dataverse.org/schema/core#termsOfUse",
+ "https://dataverse.org/schema/core#confidentialityDeclaration",
+ "https://dataverse.org/schema/core#specialPermissions", "https://dataverse.org/schema/core#restrictions",
+ "https://dataverse.org/schema/core#citationRequirements",
+ "https://dataverse.org/schema/core#depositorRequirements", "https://dataverse.org/schema/core#conditions",
+ "https://dataverse.org/schema/core#disclaimer"));
+ public static final List datafileTerms = new ArrayList(Arrays.asList(
+ "https://dataverse.org/schema/core#termsOfAccess", "https://dataverse.org/schema/core#fileRequestAccess",
+ "https://dataverse.org/schema/core#dataAccessPlace", "https://dataverse.org/schema/core#originalArchive",
+ "https://dataverse.org/schema/core#availabilityStatus",
+ "https://dataverse.org/schema/core#contactForAccess", "https://dataverse.org/schema/core#sizeOfCollection",
+ "https://dataverse.org/schema/core#studyCompletion"));
+
+ public static boolean isSet(TermsOfUseAndAccess terms, String semterm) {
+ switch (semterm) {
+ case "http://schema.org/license":
+ return !terms.getLicense().equals(TermsOfUseAndAccess.License.NONE);
+ case "https://dataverse.org/schema/core#termsOfUse":
+ return !StringUtils.isBlank(terms.getTermsOfUse());
+ case "https://dataverse.org/schema/core#confidentialityDeclaration":
+ return !StringUtils.isBlank(terms.getConfidentialityDeclaration());
+ case "https://dataverse.org/schema/core#specialPermissions":
+ return !StringUtils.isBlank(terms.getSpecialPermissions());
+ case "https://dataverse.org/schema/core#restrictions":
+ return !StringUtils.isBlank(terms.getRestrictions());
+ case "https://dataverse.org/schema/core#citationRequirements":
+ return !StringUtils.isBlank(terms.getCitationRequirements());
+ case "https://dataverse.org/schema/core#depositorRequirements":
+ return !StringUtils.isBlank(terms.getDepositorRequirements());
+ case "https://dataverse.org/schema/core#conditions":
+ return !StringUtils.isBlank(terms.getConditions());
+ case "https://dataverse.org/schema/core#disclaimer":
+ return !StringUtils.isBlank(terms.getDisclaimer());
+ case "https://dataverse.org/schema/core#termsOfAccess":
+ return !StringUtils.isBlank(terms.getTermsOfAccess());
+ case "https://dataverse.org/schema/core#fileRequestAccess":
+ return !terms.isFileAccessRequest();
+ case "https://dataverse.org/schema/core#dataAccessPlace":
+ return !StringUtils.isBlank(terms.getDataAccessPlace());
+ case "https://dataverse.org/schema/core#originalArchive":
+ return !StringUtils.isBlank(terms.getOriginalArchive());
+ case "https://dataverse.org/schema/core#availabilityStatus":
+ return !StringUtils.isBlank(terms.getAvailabilityStatus());
+ case "https://dataverse.org/schema/core#contactForAccess":
+ return !StringUtils.isBlank(terms.getContactForAccess());
+ case "https://dataverse.org/schema/core#sizeOfCollection":
+ return !StringUtils.isBlank(terms.getSizeOfCollection());
+ case "https://dataverse.org/schema/core#studyCompletion":
+ return !StringUtils.isBlank(terms.getStudyCompletion());
+ default:
+ logger.warning("isSet called for " + semterm);
+ return false;
+ }
+ }
+
+ public static void setSemTerm(TermsOfUseAndAccess terms, String semterm, Object value) {
+ switch (semterm) {
+ case "http://schema.org/license":
+ // Mirror rules from SwordServiceBean
+ if (((License) value).equals(TermsOfUseAndAccess.defaultLicense)) {
+ terms.setLicense(TermsOfUseAndAccess.defaultLicense);
+ } else {
+ throw new BadRequestException("The only allowed value for " + JsonLDTerm.schemaOrg("license").getUrl()
+ + " is " + TermsOfUseAndAccess.CC0_URI);
+ }
+ break;
+ case "https://dataverse.org/schema/core#termsOfUse":
+ terms.setTermsOfUse((String) value);
+ break;
+ case "https://dataverse.org/schema/core#confidentialityDeclaration":
+ terms.setConfidentialityDeclaration((String) value);
+ break;
+ case "https://dataverse.org/schema/core#specialPermissions":
+ terms.setSpecialPermissions((String) value);
+ break;
+ case "https://dataverse.org/schema/core#restrictions":
+ terms.setRestrictions((String) value);
+ break;
+ case "https://dataverse.org/schema/core#citationRequirements":
+ terms.setCitationRequirements((String) value);
+ break;
+ case "https://dataverse.org/schema/core#depositorRequirements":
+ terms.setDepositorRequirements((String) value);
+ break;
+ case "https://dataverse.org/schema/core#conditions":
+ terms.setConditions((String) value);
+ break;
+ case "https://dataverse.org/schema/core#disclaimer":
+ terms.setDisclaimer((String) value);
+ break;
+ case "https://dataverse.org/schema/core#termsOfAccess":
+ terms.setTermsOfAccess((String) value);
+ break;
+ case "https://dataverse.org/schema/core#fileRequestAccess":
+ terms.setFileAccessRequest((boolean) value);
+ break;
+ case "https://dataverse.org/schema/core#dataAccessPlace":
+ terms.setDataAccessPlace((String) value);
+ break;
+ case "https://dataverse.org/schema/core#originalArchive":
+ terms.setOriginalArchive((String) value);
+ break;
+ case "https://dataverse.org/schema/core#availabilityStatus":
+ terms.setAvailabilityStatus((String) value);
+ break;
+ case "https://dataverse.org/schema/core#contactForAccess":
+ terms.setContactForAccess((String) value);
+ break;
+ case "https://dataverse.org/schema/core#sizeOfCollection":
+ terms.setSizeOfCollection((String) value);
+ break;
+ case "https://dataverse.org/schema/core#studyCompletion":
+ terms.setStudyCompletion((String) value);
+ break;
+ default:
+ logger.warning("setSemTerm called for " + semterm);
+ break;
+ }
+ }
+
+ private static boolean deleteIfSemTermMatches(TermsOfUseAndAccess terms, String semterm, JsonValue jsonValue) {
+ boolean foundTerm=false;
+ String val = null;
+ if(jsonValue.getValueType().equals(ValueType.STRING)) {
+ val = ((JsonString)jsonValue).getString();
+ }
+ switch (semterm) {
+
+ case "https://dataverse.org/schema/core#termsOfUse":
+ if(terms.getTermsOfUse().equals(val)) {
+ terms.setTermsOfUse(null);
+ foundTerm=true;
+ }
+ break;
+ case "https://dataverse.org/schema/core#confidentialityDeclaration":
+ if(terms.getConfidentialityDeclaration().equals(val)) {
+ terms.setConfidentialityDeclaration(null);
+ foundTerm=true;
+ }
+ break;
+ case "https://dataverse.org/schema/core#specialPermissions":
+ if(terms.getSpecialPermissions().equals(val)) {
+ terms.setSpecialPermissions(null);
+ foundTerm=true;
+ }
+ break;
+ case "https://dataverse.org/schema/core#restrictions":
+ if(terms.getRestrictions().equals(val)) {
+ terms.setRestrictions(null);
+ foundTerm=true;
+ }
+ break;
+ case "https://dataverse.org/schema/core#citationRequirements":
+ if(terms.getCitationRequirements().equals(val)) {
+ terms.setCitationRequirements(null);
+ foundTerm=true;
+ }
+ break;
+ case "https://dataverse.org/schema/core#depositorRequirements":
+ if(terms.getDepositorRequirements().equals(val)) {
+ terms.setDepositorRequirements(null);
+ foundTerm=true;
+ }
+ break;
+ case "https://dataverse.org/schema/core#conditions":
+ if(terms.getConditions().equals(val)) {
+ terms.setConditions(null);
+ foundTerm=true;
+ }
+ break;
+ case "https://dataverse.org/schema/core#disclaimer":
+ if(terms.getDisclaimer().equals(val)) {
+ terms.setDisclaimer(null);
+ foundTerm=true;
+ }
+ break;
+ case "https://dataverse.org/schema/core#termsOfAccess":
+ if(terms.getTermsOfAccess().equals(val)) {
+ terms.setTermsOfAccess(null);
+ foundTerm=true;
+ }
+ break;
+ case "https://dataverse.org/schema/core#fileRequestAccess":
+ if(terms.isFileAccessRequest() && (jsonValue.equals(JsonValue.TRUE))) {
+ terms.setFileAccessRequest(false);
+ foundTerm=true;
+ }
+ break;
+ case "https://dataverse.org/schema/core#dataAccessPlace":
+ if(terms.getDataAccessPlace().equals(val)) {
+ terms.setDataAccessPlace(null);
+ foundTerm=true;
+ }
+ break;
+ case "https://dataverse.org/schema/core#originalArchive":
+ if(terms.getOriginalArchive().equals(val)) {
+ terms.setOriginalArchive(null);
+ foundTerm=true;
+ }
+ break;
+ case "https://dataverse.org/schema/core#availabilityStatus":
+ if(terms.getAvailabilityStatus().equals(val)) {
+ terms.setAvailabilityStatus(null);
+ foundTerm=true;
+ }
+ break;
+ case "https://dataverse.org/schema/core#contactForAccess":
+ if(terms.getContactForAccess().equals(val)) {
+ terms.setContactForAccess(null);
+ foundTerm=true;
+ }
+ break;
+ case "https://dataverse.org/schema/core#sizeOfCollection":
+ if(terms.getSizeOfCollection().equals(val)) {
+ terms.setSizeOfCollection(null);
+ foundTerm=true;
+ }
+ break;
+ case "https://dataverse.org/schema/core#studyCompletion":
+ if(terms.getStudyCompletion().equals(val)) {
+ terms.setStudyCompletion(null);
+ foundTerm=true;
+ }
+ break;
+ default:
+ logger.warning("deleteIfSemTermMatches called for " + semterm);
+ break;
+ }
+ return foundTerm;
+ }
+
+}
diff --git a/src/main/java/edu/harvard/iq/dataverse/util/json/JsonLDNamespace.java b/src/main/java/edu/harvard/iq/dataverse/util/json/JsonLDNamespace.java
index bda4a55d623..904419775c9 100644
--- a/src/main/java/edu/harvard/iq/dataverse/util/json/JsonLDNamespace.java
+++ b/src/main/java/edu/harvard/iq/dataverse/util/json/JsonLDNamespace.java
@@ -1,5 +1,13 @@
package edu.harvard.iq.dataverse.util.json;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+import edu.harvard.iq.dataverse.DataFile;
+
public class JsonLDNamespace {
String prefix;
@@ -12,7 +20,40 @@ public class JsonLDNamespace {
public static JsonLDNamespace ore = new JsonLDNamespace("ore","http://www.openarchives.org/ore/terms/");
public static JsonLDNamespace schema = new JsonLDNamespace("schema","http://schema.org/");
- public JsonLDNamespace(String prefix, String url) {
+ private static List namespaces = new ArrayList(Arrays.asList(dvcore, dcterms, ore, schema));
+
+ public static JsonLDNamespace defineNamespace(String prefix, String url) {
+
+ JsonLDNamespace ns = new JsonLDNamespace(prefix, url);
+ int i = namespaces.indexOf(ns);
+ if(i>=0) {
+ return namespaces.get(i);
+ } else {
+ namespaces.add(ns);
+ return ns;
+ }
+ }
+
+ public static void deleteNamespace(JsonLDNamespace ns) {
+ namespaces.remove(ns);
+ }
+
+ public static boolean isInNamespace(String url) {
+ for(JsonLDNamespace ns: namespaces) {
+ if(url.startsWith(ns.getUrl())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static void addNamespacesToContext(Map context) {
+ for(JsonLDNamespace ns: namespaces) {
+ context.putIfAbsent(ns.getPrefix(), ns.getUrl());
+ };
+ }
+
+ private JsonLDNamespace(String prefix, String url) {
this.prefix = prefix;
this.url = url;
}
@@ -24,5 +65,14 @@ public String getPrefix() {
public String getUrl() {
return url;
}
+
+ @Override
+ public boolean equals(Object object) {
+ if (!(object instanceof JsonLDNamespace)) {
+ return false;
+ }
+ JsonLDNamespace other = (JsonLDNamespace) object;
+ return (other.getPrefix().equals(getPrefix()) && other.getUrl().equals(getUrl()));
+ }
}
diff --git a/src/main/java/edu/harvard/iq/dataverse/util/json/JsonLDTerm.java b/src/main/java/edu/harvard/iq/dataverse/util/json/JsonLDTerm.java
index 5acb0c437ae..20aeceda7de 100644
--- a/src/main/java/edu/harvard/iq/dataverse/util/json/JsonLDTerm.java
+++ b/src/main/java/edu/harvard/iq/dataverse/util/json/JsonLDTerm.java
@@ -1,8 +1,5 @@
package edu.harvard.iq.dataverse.util.json;
-import java.util.HashMap;
-import java.util.Map;
-
public class JsonLDTerm {
JsonLDNamespace namespace = null;
diff --git a/src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java b/src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java
index a7dc41d9c74..e8f1e71e25e 100644
--- a/src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java
+++ b/src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java
@@ -7,6 +7,7 @@
import java.util.logging.Logger;
import org.junit.BeforeClass;
import org.junit.Test;
+import org.skyscreamer.jsonassert.JSONAssert;
import org.junit.Ignore;
import com.jayway.restassured.path.json.JsonPath;
@@ -27,17 +28,20 @@
import edu.harvard.iq.dataverse.settings.SettingsServiceBean;
import java.util.UUID;
import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.exception.ExceptionUtils;
+
import com.jayway.restassured.parsing.Parser;
import static com.jayway.restassured.path.json.JsonPath.with;
import com.jayway.restassured.path.xml.XmlPath;
-import edu.harvard.iq.dataverse.Dataset;
import static edu.harvard.iq.dataverse.api.UtilIT.equalToCI;
-import static edu.harvard.iq.dataverse.authorization.AuthenticationResponse.Status.ERROR;
import edu.harvard.iq.dataverse.authorization.groups.impl.builtin.AuthenticatedUsers;
import edu.harvard.iq.dataverse.util.BundleUtil;
import edu.harvard.iq.dataverse.util.SystemConfig;
+import edu.harvard.iq.dataverse.util.json.JSONLDUtil;
+
import java.io.File;
import java.io.IOException;
+import java.io.StringReader;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
@@ -45,26 +49,15 @@
import javax.json.Json;
import javax.json.JsonArray;
import javax.json.JsonObjectBuilder;
-import javax.persistence.EntityManager;
-import javax.persistence.EntityManagerFactory;
-import javax.persistence.Persistence;
-import javax.persistence.PersistenceContext;
-import static javax.ws.rs.core.Response.Status.BAD_REQUEST;
-import static javax.ws.rs.core.Response.Status.CREATED;
-import static javax.ws.rs.core.Response.Status.FORBIDDEN;
import static javax.ws.rs.core.Response.Status.NO_CONTENT;
-import static javax.ws.rs.core.Response.Status.OK;
-import static javax.ws.rs.core.Response.Status.UNAUTHORIZED;
-import static junit.framework.Assert.assertEquals;
+import static org.junit.Assert.assertEquals;
import org.hamcrest.CoreMatchers;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.nullValue;
-import org.junit.After;
import org.junit.AfterClass;
import org.junit.Assert;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
-import org.junit.Before;
import static org.junit.matchers.JUnitMatchers.containsString;
public class DatasetsIT {
@@ -2164,4 +2157,111 @@ public void testRestrictFileExportDdi() throws IOException {
.body("message", equalTo("You do not have permission to download this file."));
}
+ @Test
+ public void testSemanticMetadataAPIs() {
+ Response createUser = UtilIT.createRandomUser();
+ createUser.prettyPrint();
+ String username = UtilIT.getUsernameFromResponse(createUser);
+ String apiToken = UtilIT.getApiTokenFromResponse(createUser);
+
+ Response createDataverseResponse = UtilIT.createRandomDataverse(apiToken);
+ createDataverseResponse.prettyPrint();
+ String dataverseAlias = UtilIT.getAliasFromResponse(createDataverseResponse);
+
+ // Create a dataset using native api
+ Response createDatasetResponse = UtilIT.createRandomDatasetViaNativeApi(dataverseAlias, apiToken);
+ createDatasetResponse.prettyPrint();
+ Integer datasetId = UtilIT.getDatasetIdFromResponse(createDatasetResponse);
+
+ // Get the metadata with the semantic api
+ Response response = UtilIT.getDatasetJsonLDMetadata(datasetId, apiToken);
+ response.then().assertThat().statusCode(OK.getStatusCode());
+ // Compare the metadata with an expected value - the metadatablock entries
+ // should be the same but there will be additional fields with values related to
+ // the dataset's creation (e.g. new id)
+ String jsonLDString = getData(response.getBody().asString());
+ JsonObject jo = null;
+ try {
+ jo = JSONLDUtil.decontextualizeJsonLD(jsonLDString);
+ } catch (NoSuchMethodError e) {
+ logger.info(ExceptionUtils.getStackTrace(e));
+ }
+
+
+ String expectedJsonLD = UtilIT.getDatasetJson("scripts/search/tests/data/dataset-finch1.jsonld");
+ jo = Json.createObjectBuilder(jo).remove("@id").remove("http://schema.org/dateModified").build();
+ String jsonLD = jo.toString();
+
+ // ToDo: Are the static pars as expected
+ JSONAssert.assertEquals(expectedJsonLD, jsonLD, false);
+ // Now change the title
+ response = UtilIT.updateDatasetJsonLDMetadata(datasetId, apiToken,
+ "{\"Title\": \"New Title\", \"@context\":{\"Title\": \"http://purl.org/dc/terms/title\"}}", true);
+ response.then().assertThat().statusCode(OK.getStatusCode());
+
+ response = UtilIT.getDatasetJsonLDMetadata(datasetId, apiToken);
+ response.then().assertThat().statusCode(OK.getStatusCode());
+
+ // Check that the semantic api returns the new title
+ jsonLDString = getData(response.getBody().asString());
+ JsonObject jsonLDObject = JSONLDUtil.decontextualizeJsonLD(jsonLDString);
+ assertEquals("New Title", jsonLDObject.getString("http://purl.org/dc/terms/title"));
+
+ // Add an additional description (which is multi-valued and compound)
+ // Also add new terms of use (single value so would fail with replace false if a
+ // value existed)
+ String newDescription = "{\"citation:Description\": {\"dsDescription:Text\": \"New description\"}, \"https://dataverse.org/schema/core#termsOfUse\": \"New terms\", \"@context\":{\"citation\": \"https://dataverse.org/schema/citation/\",\"dsDescription\": \"https://dataverse.org/schema/citation/dsDescription#\"}}";
+ response = UtilIT.updateDatasetJsonLDMetadata(datasetId, apiToken, newDescription, false);
+ response.then().assertThat().statusCode(OK.getStatusCode());
+
+ response = UtilIT.getDatasetJsonLDMetadata(datasetId, apiToken);
+ response.then().assertThat().statusCode(OK.getStatusCode());
+
+ // Look for a second description
+ jsonLDString = getData(response.getBody().asString());
+ jsonLDObject = JSONLDUtil.decontextualizeJsonLD(jsonLDString);
+ assertEquals("New description",
+ ((JsonObject) jsonLDObject.getJsonArray("https://dataverse.org/schema/citation/Description").get(1))
+ .getString("https://dataverse.org/schema/citation/dsDescription#Text"));
+
+ // Can't add terms of use with replace=false and a value already set (single
+ // valued field)
+ String badTerms = "{\"https://dataverse.org/schema/core#termsOfUse\": \"Bad terms\"}}";
+ response = UtilIT.updateDatasetJsonLDMetadata(datasetId, apiToken, badTerms, false);
+ response.then().assertThat().statusCode(BAD_REQUEST.getStatusCode());
+
+ // Delete the terms of use
+ response = UtilIT.deleteDatasetJsonLDMetadata(datasetId, apiToken,
+ "{\"https://dataverse.org/schema/core#termsOfUse\": \"New terms\"}");
+ response.then().assertThat().statusCode(OK.getStatusCode());
+
+ response = UtilIT.getDatasetJsonLDMetadata(datasetId, apiToken);
+ response.then().assertThat().statusCode(OK.getStatusCode());
+
+ // Verify that they're gone
+ jsonLDString = getData(response.getBody().asString());
+ jsonLDObject = JSONLDUtil.decontextualizeJsonLD(jsonLDString);
+ assertTrue(!jsonLDObject.containsKey("https://dataverse.org/schema/core#termsOfUse"));
+
+ // Cleanup - delete dataset, dataverse, user...
+ Response deleteDatasetResponse = UtilIT.deleteDatasetViaNativeApi(datasetId, apiToken);
+ deleteDatasetResponse.prettyPrint();
+ assertEquals(200, deleteDatasetResponse.getStatusCode());
+
+ Response deleteDataverseResponse = UtilIT.deleteDataverse(dataverseAlias, apiToken);
+ deleteDataverseResponse.prettyPrint();
+ assertEquals(200, deleteDataverseResponse.getStatusCode());
+
+ Response deleteUserResponse = UtilIT.deleteUser(username);
+ deleteUserResponse.prettyPrint();
+ assertEquals(200, deleteUserResponse.getStatusCode());
+
+ }
+
+ private String getData(String body) {
+ try (StringReader rdr = new StringReader(body)) {
+ return Json.createReader(rdr).readObject().getJsonObject("data").toString();
+ }
+ }
+
}
diff --git a/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java b/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java
index bb4d3fe3c5a..7019dde608c 100644
--- a/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java
+++ b/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java
@@ -11,6 +11,7 @@
import javax.json.JsonObject;
import java.io.File;
import java.io.IOException;
+import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.logging.Level;
@@ -38,7 +39,6 @@
import static com.jayway.restassured.RestAssured.given;
import edu.harvard.iq.dataverse.util.StringUtil;
import java.io.StringReader;
-import javax.json.JsonArray;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
@@ -397,7 +397,7 @@ static Response createDatasetViaNativeApi(String dataverseAlias, String pathToJs
return createDatasetResponse;
}
- private static String getDatasetJson(String pathToJsonFile) {
+ static String getDatasetJson(String pathToJsonFile) {
File datasetVersionJson = new File(pathToJsonFile);
try {
String datasetVersionAsJson = new String(Files.readAllBytes(Paths.get(datasetVersionJson.getAbsolutePath())));
@@ -2633,5 +2633,29 @@ static String getBannerMessageIdFromResponse(String getBannerMessagesResponse) {
return "0";
}
+ static Response getDatasetJsonLDMetadata(Integer datasetId, String apiToken) {
+ Response response = given()
+ .header(API_TOKEN_HTTP_HEADER, apiToken)
+ .accept("application/ld+json")
+ .get("/api/datasets/" + datasetId + "/metadata");
+ return response;
+ }
+ static Response updateDatasetJsonLDMetadata(Integer datasetId, String apiToken, String jsonLDBody, boolean replace) {
+ Response response = given()
+ .header(API_TOKEN_HTTP_HEADER, apiToken)
+ .contentType("application/ld+json")
+ .body(jsonLDBody.getBytes(StandardCharsets.UTF_8))
+ .put("/api/datasets/" + datasetId + "/metadata?replace=" + replace);
+ return response;
+ }
+
+ static Response deleteDatasetJsonLDMetadata(Integer datasetId, String apiToken, String jsonLDBody) {
+ Response response = given()
+ .header(API_TOKEN_HTTP_HEADER, apiToken)
+ .contentType("application/ld+json")
+ .body(jsonLDBody.getBytes(StandardCharsets.UTF_8))
+ .put("/api/datasets/" + datasetId + "/metadata/delete");
+ return response;
+ }
}
diff --git a/src/test/java/edu/harvard/iq/dataverse/dataaccess/StorageIOTest.java b/src/test/java/edu/harvard/iq/dataverse/dataaccess/StorageIOTest.java
index 47164a5c035..83cb0c72786 100644
--- a/src/test/java/edu/harvard/iq/dataverse/dataaccess/StorageIOTest.java
+++ b/src/test/java/edu/harvard/iq/dataverse/dataaccess/StorageIOTest.java
@@ -65,7 +65,9 @@ public void testGetDvObject() {
Dataset d= new Dataset();
instance.setDvObject(d);
//assertSame uses == rather than the .equals() method which would (currently) be true for any two Datasets
- assertSame(d, instance.getDataset()); try {
+ assertSame(d, instance.getDataset());
+
+ try {
instance.getDataFile();
fail("This should have thrown");
} catch (ClassCastException ex) {