Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Adds new update dataverse general endpoint #10925

Merged
merged 18 commits into from
Nov 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
7f5b0be
Added: updateDataverse endpoint with addDataverse refactoring
GPortas Oct 15, 2024
19c8a12
Changed: limiting the information to update in a dataverse through th…
GPortas Oct 16, 2024
f4c3d2c
Removed: DataverseContact host dataverse re-set
GPortas Oct 16, 2024
8ef8cfd
Added: parseDataverseUpdates unit test
GPortas Oct 17, 2024
62df2a7
Changed: reordered logic in UpdateDataverseCommand for further refact…
GPortas Oct 17, 2024
6ccbb4a
Changed: updateDataverse return code
GPortas Oct 17, 2024
5c17039
Added: IT for updateDataverse endpoint
GPortas Oct 17, 2024
e5cdb10
Refactor: UtilIT duplication on dataverse write operations
GPortas Oct 17, 2024
8020d50
Added: pending doc comment to JsonParserTest method
GPortas Oct 17, 2024
2d10f22
Added: missing IT for updateDataverse endpoint
GPortas Oct 17, 2024
d334b68
Refactor: CreateDataverseCommand inheriting AbstractWriteDataverseCom…
GPortas Oct 21, 2024
e778239
Refactor: UpdateDataverseCommand inheriting AbstractWriteDataverseCom…
GPortas Oct 21, 2024
4e90d0c
Added: docs for #10904
GPortas Oct 21, 2024
0748b18
Merge branch 'develop' of github.com:IQSS/dataverse into 10904-edit-d…
GPortas Oct 21, 2024
6aac751
Added: release notes for #10904
GPortas Oct 21, 2024
4f98be6
Removed: unnecessary line in updateDataverse endpoint
GPortas Oct 21, 2024
0b5f9a8
Changed: handling properties update through a DTO object for updateDa…
GPortas Oct 24, 2024
b322c28
Merge branch 'develop' of github.com:IQSS/dataverse into 10904-edit-d…
GPortas Nov 4, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Adds a new endpoint (`PUT /api/dataverses/<identifier>`) for updating an existing Dataverse collection using a JSON file following the same structure as the one used in the API for the creation.
52 changes: 52 additions & 0 deletions doc/sphinx-guides/source/api/native-api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,58 @@ The request JSON supports an optional ``metadataBlocks`` object, with the follow

To obtain an example of how these objects are included in the JSON file, download :download:`dataverse-complete-optional-params.json <../_static/api/dataverse-complete-optional-params.json>` file and modify it to suit your needs.

.. _update-dataverse-api:

Update a Dataverse Collection
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Updates an existing Dataverse collection using a JSON file following the same structure as the one used in the API for the creation. (see :ref:`create-dataverse-api`).

The steps for updating a Dataverse collection are:

- Prepare a JSON file containing the fields for the properties you want to update. You do not need to include all the properties, only the ones you want to update.
- Execute a curl command or equivalent.

As an example, you can download :download:`dataverse-complete.json <../_static/api/dataverse-complete.json>` file and modify it to suit your needs. The controlled vocabulary for ``dataverseType`` is the following:

- ``DEPARTMENT``
- ``JOURNALS``
- ``LABORATORY``
- ``ORGANIZATIONS_INSTITUTIONS``
- ``RESEARCHERS``
- ``RESEARCH_GROUP``
- ``RESEARCH_PROJECTS``
- ``TEACHING_COURSES``
- ``UNCATEGORIZED``

The curl command below assumes you are using the name "dataverse-complete.json" and that this file is in your current working directory.

Next you need to figure out the alias or database id of the Dataverse collection you want to update.

.. code-block:: bash

export API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
export SERVER_URL=https://demo.dataverse.org
export DV_ALIAS=dvAlias

curl -H "X-Dataverse-key:$API_TOKEN" -X PUT "$SERVER_URL/api/dataverses/$DV_ALIAS" --upload-file dataverse-complete.json

The fully expanded example above (without environment variables) looks like this:

.. code-block:: bash

curl -H "X-Dataverse-key:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" -X PUT "https://demo.dataverse.org/api/dataverses/dvAlias" --upload-file dataverse-complete.json

You should expect an HTTP 200 response and JSON beginning with "status":"OK" followed by a representation of the updated Dataverse collection.

Same as in :ref:`create-dataverse-api`, the request JSON supports an optional ``metadataBlocks`` object, with the following supported sub-objects:

- ``metadataBlockNames``: The names of the metadata blocks you want to add to the Dataverse collection.
- ``inputLevels``: The names of the fields in each metadata block for which you want to add a custom configuration regarding their inclusion or requirement when creating and editing datasets in the new Dataverse collection. Note that if the corresponding metadata blocks names are not specified in the ``metadataBlockNames``` field, they will be added automatically to the Dataverse collection.
- ``facetIds``: The names of the fields to use as facets for browsing datasets and collections in the new Dataverse collection. Note that the order of the facets is defined by their order in the provided JSON array.

To obtain an example of how these objects are included in the JSON file, download :download:`dataverse-complete-optional-params.json <../_static/api/dataverse-complete-optional-params.json>` file and modify it to suit your needs.

.. _view-dataverse:

View a Dataverse Collection
Expand Down
176 changes: 128 additions & 48 deletions src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,9 @@
import edu.harvard.iq.dataverse.*;
import edu.harvard.iq.dataverse.api.auth.AuthRequired;
import edu.harvard.iq.dataverse.api.datadeposit.SwordServiceBean;
import edu.harvard.iq.dataverse.api.dto.DataverseMetadataBlockFacetDTO;
import edu.harvard.iq.dataverse.api.dto.*;
import edu.harvard.iq.dataverse.authorization.DataverseRole;

import edu.harvard.iq.dataverse.api.dto.ExplicitGroupDTO;
import edu.harvard.iq.dataverse.api.dto.RoleAssignmentDTO;
import edu.harvard.iq.dataverse.api.dto.RoleDTO;
import edu.harvard.iq.dataverse.api.imports.ImportException;
import edu.harvard.iq.dataverse.api.imports.ImportServiceBean;
import edu.harvard.iq.dataverse.authorization.Permission;
Expand Down Expand Up @@ -127,73 +124,156 @@ public Response addRoot(@Context ContainerRequestContext crc, String body) {
@Path("{identifier}")
public Response addDataverse(@Context ContainerRequestContext crc, String body, @PathParam("identifier") String parentIdtf) {
Dataverse newDataverse;
JsonObject newDataverseJson;
try {
newDataverseJson = JsonUtil.getJsonObject(body);
newDataverse = jsonParser().parseDataverse(newDataverseJson);
newDataverse = parseAndValidateAddDataverseRequestBody(body);
} catch (JsonParsingException jpe) {
logger.log(Level.SEVERE, "Json: {0}", body);
return error(Status.BAD_REQUEST, MessageFormat.format(BundleUtil.getStringFromBundle("dataverse.create.error.jsonparse"), jpe.getMessage()));
} catch (JsonParseException ex) {
logger.log(Level.SEVERE, "Error parsing dataverse from json: " + ex.getMessage(), ex);
return error(Status.BAD_REQUEST, MessageFormat.format(BundleUtil.getStringFromBundle("dataverse.create.error.jsonparsetodataverse"), ex.getMessage()));
}

try {
JsonObject metadataBlocksJson = newDataverseJson.getJsonObject("metadataBlocks");
List<DataverseFieldTypeInputLevel> inputLevels = null;
List<MetadataBlock> metadataBlocks = null;
List<DatasetFieldType> facetList = null;
if (metadataBlocksJson != null) {
JsonArray inputLevelsArray = metadataBlocksJson.getJsonArray("inputLevels");
inputLevels = inputLevelsArray != null ? parseInputLevels(inputLevelsArray, newDataverse) : null;

JsonArray metadataBlockNamesArray = metadataBlocksJson.getJsonArray("metadataBlockNames");
metadataBlocks = metadataBlockNamesArray != null ? parseNewDataverseMetadataBlocks(metadataBlockNamesArray) : null;

JsonArray facetIdsArray = metadataBlocksJson.getJsonArray("facetIds");
facetList = facetIdsArray != null ? parseFacets(facetIdsArray) : null;
}
List<DataverseFieldTypeInputLevel> inputLevels = parseInputLevels(body, newDataverse);
List<MetadataBlock> metadataBlocks = parseMetadataBlocks(body);
List<DatasetFieldType> facets = parseFacets(body);

if (!parentIdtf.isEmpty()) {
Dataverse owner = findDataverseOrDie(parentIdtf);
newDataverse.setOwner(owner);
}

// set the dataverse - contact relationship in the contacts
for (DataverseContact dc : newDataverse.getDataverseContacts()) {
dc.setDataverse(newDataverse);
}

AuthenticatedUser u = getRequestAuthenticatedUserOrDie(crc);
newDataverse = execCommand(new CreateDataverseCommand(newDataverse, createDataverseRequest(u), facetList, inputLevels, metadataBlocks));
newDataverse = execCommand(new CreateDataverseCommand(newDataverse, createDataverseRequest(u), facets, inputLevels, metadataBlocks));
return created("/dataverses/" + newDataverse.getAlias(), json(newDataverse));
} catch (WrappedResponse ww) {

String error = ConstraintViolationUtil.getErrorStringForConstraintViolations(ww.getCause());
if (!error.isEmpty()) {
logger.log(Level.INFO, error);
return ww.refineResponse(error);
}
return ww.getResponse();

} catch (WrappedResponse ww) {
return handleWrappedResponse(ww);
} catch (EJBException ex) {
Throwable cause = ex;
StringBuilder sb = new StringBuilder();
sb.append("Error creating dataverse.");
while (cause.getCause() != null) {
cause = cause.getCause();
if (cause instanceof ConstraintViolationException) {
sb.append(ConstraintViolationUtil.getErrorStringForConstraintViolations(cause));
}
}
logger.log(Level.SEVERE, sb.toString());
return error(Response.Status.INTERNAL_SERVER_ERROR, "Error creating dataverse: " + sb.toString());
return handleEJBException(ex, "Error creating dataverse.");
} catch (Exception ex) {
logger.log(Level.SEVERE, "Error creating dataverse", ex);
return error(Response.Status.INTERNAL_SERVER_ERROR, "Error creating dataverse: " + ex.getMessage());
}
}

private Dataverse parseAndValidateAddDataverseRequestBody(String body) throws JsonParsingException, JsonParseException {
try {
JsonObject addDataverseJson = JsonUtil.getJsonObject(body);
return jsonParser().parseDataverse(addDataverseJson);
} catch (JsonParsingException jpe) {
logger.log(Level.SEVERE, "Json: {0}", body);
throw jpe;
} catch (JsonParseException ex) {
logger.log(Level.SEVERE, "Error parsing dataverse from json: " + ex.getMessage(), ex);
throw ex;
}
}

@PUT
@AuthRequired
@Path("{identifier}")
public Response updateDataverse(@Context ContainerRequestContext crc, String body, @PathParam("identifier") String identifier) {
Dataverse dataverse;
try {
dataverse = findDataverseOrDie(identifier);
} catch (WrappedResponse e) {
return e.getResponse();
}

DataverseDTO updatedDataverseDTO;
try {
updatedDataverseDTO = parseAndValidateUpdateDataverseRequestBody(body);
} catch (JsonParsingException jpe) {
return error(Status.BAD_REQUEST, MessageFormat.format(BundleUtil.getStringFromBundle("dataverse.create.error.jsonparse"), jpe.getMessage()));
} catch (JsonParseException ex) {
return error(Status.BAD_REQUEST, MessageFormat.format(BundleUtil.getStringFromBundle("dataverse.create.error.jsonparsetodataverse"), ex.getMessage()));
}

try {
List<DataverseFieldTypeInputLevel> inputLevels = parseInputLevels(body, dataverse);
List<MetadataBlock> metadataBlocks = parseMetadataBlocks(body);
List<DatasetFieldType> facets = parseFacets(body);

AuthenticatedUser u = getRequestAuthenticatedUserOrDie(crc);
dataverse = execCommand(new UpdateDataverseCommand(dataverse, facets, null, createDataverseRequest(u), inputLevels, metadataBlocks, updatedDataverseDTO));
return ok(json(dataverse));

} catch (WrappedResponse ww) {
return handleWrappedResponse(ww);
} catch (EJBException ex) {
return handleEJBException(ex, "Error updating dataverse.");
} catch (Exception ex) {
logger.log(Level.SEVERE, "Error updating dataverse", ex);
return error(Response.Status.INTERNAL_SERVER_ERROR, "Error updating dataverse: " + ex.getMessage());
}
}

private DataverseDTO parseAndValidateUpdateDataverseRequestBody(String body) throws JsonParsingException, JsonParseException {
try {
JsonObject updateDataverseJson = JsonUtil.getJsonObject(body);
return jsonParser().parseDataverseDTO(updateDataverseJson);
} catch (JsonParsingException jpe) {
logger.log(Level.SEVERE, "Json: {0}", body);
throw jpe;
} catch (JsonParseException ex) {
logger.log(Level.SEVERE, "Error parsing DataverseDTO from json: " + ex.getMessage(), ex);
throw ex;
}
}

private List<DataverseFieldTypeInputLevel> parseInputLevels(String body, Dataverse dataverse) throws WrappedResponse {
JsonObject metadataBlocksJson = getMetadataBlocksJson(body);
if (metadataBlocksJson == null) {
return null;
}
JsonArray inputLevelsArray = metadataBlocksJson.getJsonArray("inputLevels");
return inputLevelsArray != null ? parseInputLevels(inputLevelsArray, dataverse) : null;
}

private List<MetadataBlock> parseMetadataBlocks(String body) throws WrappedResponse {
JsonObject metadataBlocksJson = getMetadataBlocksJson(body);
if (metadataBlocksJson == null) {
return null;
}
JsonArray metadataBlocksArray = metadataBlocksJson.getJsonArray("metadataBlockNames");
return metadataBlocksArray != null ? parseNewDataverseMetadataBlocks(metadataBlocksArray) : null;
}

private List<DatasetFieldType> parseFacets(String body) throws WrappedResponse {
JsonObject metadataBlocksJson = getMetadataBlocksJson(body);
if (metadataBlocksJson == null) {
return null;
}
JsonArray facetsArray = metadataBlocksJson.getJsonArray("facetIds");
return facetsArray != null ? parseFacets(facetsArray) : null;
}

private JsonObject getMetadataBlocksJson(String body) {
JsonObject dataverseJson = JsonUtil.getJsonObject(body);
return dataverseJson.getJsonObject("metadataBlocks");
}

private Response handleWrappedResponse(WrappedResponse ww) {
String error = ConstraintViolationUtil.getErrorStringForConstraintViolations(ww.getCause());
if (!error.isEmpty()) {
logger.log(Level.INFO, error);
return ww.refineResponse(error);
}
return ww.getResponse();
}

private Response handleEJBException(EJBException ex, String action) {
Throwable cause = ex;
StringBuilder sb = new StringBuilder();
sb.append(action);
while (cause.getCause() != null) {
cause = cause.getCause();
if (cause instanceof ConstraintViolationException) {
sb.append(ConstraintViolationUtil.getErrorStringForConstraintViolations(cause));
}
}
logger.log(Level.SEVERE, sb.toString());
return error(Response.Status.INTERNAL_SERVER_ERROR, sb.toString());
}

private List<MetadataBlock> parseNewDataverseMetadataBlocks(JsonArray metadataBlockNamesArray) throws WrappedResponse {
Expand Down
63 changes: 63 additions & 0 deletions src/main/java/edu/harvard/iq/dataverse/api/dto/DataverseDTO.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package edu.harvard.iq.dataverse.api.dto;

import edu.harvard.iq.dataverse.Dataverse;
import edu.harvard.iq.dataverse.DataverseContact;

import java.util.List;

public class DataverseDTO {
private String alias;
private String name;
private String description;
private String affiliation;
private List<DataverseContact> dataverseContacts;
private Dataverse.DataverseType dataverseType;

public String getAlias() {
return alias;
}

public void setAlias(String alias) {
this.alias = alias;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getDescription() {
return description;
}

public void setDescription(String description) {
this.description = description;
}

public String getAffiliation() {
return affiliation;
}

public void setAffiliation(String affiliation) {
this.affiliation = affiliation;
}

public List<DataverseContact> getDataverseContacts() {
return dataverseContacts;
}

public void setDataverseContacts(List<DataverseContact> dataverseContacts) {
this.dataverseContacts = dataverseContacts;
}

public Dataverse.DataverseType getDataverseType() {
return dataverseType;
}

public void setDataverseType(Dataverse.DataverseType dataverseType) {
this.dataverseType = dataverseType;
}
}
Loading
Loading