-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[DUOS-2703][risk=no] Delete study API (#2187)
- Loading branch information
Showing
13 changed files
with
573 additions
and
238 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
201 changes: 201 additions & 0 deletions
201
src/main/java/org/broadinstitute/consent/http/resources/StudyResource.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,201 @@ | ||
package org.broadinstitute.consent.http.resources; | ||
|
||
import com.google.gson.Gson; | ||
import com.google.inject.Inject; | ||
import io.dropwizard.auth.Auth; | ||
import jakarta.annotation.security.RolesAllowed; | ||
import jakarta.ws.rs.BadRequestException; | ||
import jakarta.ws.rs.Consumes; | ||
import jakarta.ws.rs.DELETE; | ||
import jakarta.ws.rs.GET; | ||
import jakarta.ws.rs.NotFoundException; | ||
import jakarta.ws.rs.PUT; | ||
import jakarta.ws.rs.Path; | ||
import jakarta.ws.rs.PathParam; | ||
import jakarta.ws.rs.Produces; | ||
import jakarta.ws.rs.core.MediaType; | ||
import jakarta.ws.rs.core.Response; | ||
import jakarta.ws.rs.core.Response.Status; | ||
import java.io.IOException; | ||
import java.util.HashMap; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.Objects; | ||
import java.util.Set; | ||
import org.broadinstitute.consent.http.enumeration.UserRoles; | ||
import org.broadinstitute.consent.http.models.AuthUser; | ||
import org.broadinstitute.consent.http.models.Dataset; | ||
import org.broadinstitute.consent.http.models.Study; | ||
import org.broadinstitute.consent.http.models.User; | ||
import org.broadinstitute.consent.http.models.dataset_registration_v1.DatasetRegistrationSchemaV1; | ||
import org.broadinstitute.consent.http.models.dataset_registration_v1.DatasetRegistrationSchemaV1UpdateValidator; | ||
import org.broadinstitute.consent.http.models.dataset_registration_v1.builder.DatasetRegistrationSchemaV1Builder; | ||
import org.broadinstitute.consent.http.service.DatasetRegistrationService; | ||
import org.broadinstitute.consent.http.service.DatasetService; | ||
import org.broadinstitute.consent.http.service.ElasticSearchService; | ||
import org.broadinstitute.consent.http.service.UserService; | ||
import org.broadinstitute.consent.http.util.gson.GsonUtil; | ||
import org.glassfish.jersey.media.multipart.FormDataBodyPart; | ||
import org.glassfish.jersey.media.multipart.FormDataMultiPart; | ||
import org.glassfish.jersey.media.multipart.FormDataParam; | ||
|
||
@Path("api/dataset/study") | ||
public class StudyResource extends Resource { | ||
|
||
private final DatasetService datasetService; | ||
private final DatasetRegistrationService datasetRegistrationService; | ||
private final UserService userService; | ||
private final ElasticSearchService elasticSearchService; | ||
|
||
|
||
@Inject | ||
public StudyResource(DatasetService datasetService, UserService userService, | ||
DatasetRegistrationService datasetRegistrationService, | ||
ElasticSearchService elasticSearchService) { | ||
this.datasetService = datasetService; | ||
this.userService = userService; | ||
this.datasetRegistrationService = datasetRegistrationService; | ||
this.elasticSearchService = elasticSearchService; | ||
} | ||
|
||
@GET | ||
@Path("/{studyId}") | ||
@Produces(MediaType.APPLICATION_JSON) | ||
@RolesAllowed({ADMIN, CHAIRPERSON, DATASUBMITTER}) | ||
public Response getStudyById(@PathParam("studyId") Integer studyId) { | ||
try { | ||
Study study = datasetService.getStudyWithDatasetsById(studyId); | ||
return Response.ok(study).build(); | ||
} catch (Exception e) { | ||
return createExceptionResponse(e); | ||
} | ||
} | ||
|
||
@DELETE | ||
@Path("/{studyId}") | ||
@Produces(MediaType.APPLICATION_JSON) | ||
@RolesAllowed({ADMIN, CHAIRPERSON, DATASUBMITTER}) | ||
public Response deleteStudyById(@Auth AuthUser authUser, @PathParam("studyId") Integer studyId) { | ||
try { | ||
User user = userService.findUserByEmail(authUser.getEmail()); | ||
Study study = datasetService.getStudyWithDatasetsById(studyId); | ||
|
||
if (Objects.isNull(study)) { | ||
throw new NotFoundException("Study not found"); | ||
} | ||
|
||
// If the user is not an admin, ensure that they are the study/dataset creator | ||
if (!user.hasUserRole(UserRoles.ADMIN) && (!Objects.equals(study.getCreateUserId(), | ||
user.getUserId()))) { | ||
throw new NotFoundException("Study not found"); | ||
} | ||
|
||
boolean deletable = study.getDatasets() | ||
.stream() | ||
.allMatch(Dataset::getDeletable); | ||
if (!deletable) { | ||
throw new BadRequestException("Study has datasets that are in use and cannot be deleted."); | ||
} | ||
Set<Integer> studyDatasetIds = study.getDatasetIds(); | ||
datasetService.deleteStudy(study, user); | ||
// Remove from ES index | ||
studyDatasetIds.forEach(id -> { | ||
try { | ||
elasticSearchService.deleteIndex(id); | ||
} catch (IOException e) { | ||
logException(e); | ||
} | ||
}); | ||
return Response.ok().build(); | ||
} catch (Exception e) { | ||
return createExceptionResponse(e); | ||
} | ||
} | ||
|
||
@GET | ||
@Path("/registration/{studyId}") | ||
@Produces(MediaType.APPLICATION_JSON) | ||
@RolesAllowed({ADMIN, CHAIRPERSON, DATASUBMITTER}) | ||
public Response getRegistrationFromStudy(@Auth AuthUser authUser, | ||
@PathParam("studyId") Integer studyId) { | ||
try { | ||
Study study = datasetService.getStudyWithDatasetsById(studyId); | ||
List<Dataset> datasets = | ||
Objects.nonNull(study.getDatasets()) ? study.getDatasets().stream().toList() : List.of(); | ||
DatasetRegistrationSchemaV1 registration = new DatasetRegistrationSchemaV1Builder().build( | ||
study, datasets); | ||
String entity = GsonUtil.buildGsonNullSerializer().toJson(registration); | ||
return Response.ok().entity(entity).build(); | ||
} catch (Exception e) { | ||
return createExceptionResponse(e); | ||
} | ||
} | ||
|
||
@PUT | ||
@Consumes({MediaType.MULTIPART_FORM_DATA}) | ||
@Produces({MediaType.APPLICATION_JSON}) | ||
@Path("/{studyId}") | ||
@RolesAllowed({ADMIN, CHAIRPERSON, DATASUBMITTER}) | ||
/* | ||
* This endpoint accepts a json instance of a dataset-registration-schema_v1.json schema. | ||
* With that object, we can fully update the study/datasets from the provided values. | ||
*/ | ||
public Response updateStudyByRegistration( | ||
@Auth AuthUser authUser, | ||
FormDataMultiPart multipart, | ||
@PathParam("studyId") Integer studyId, | ||
@FormDataParam("dataset") String json) { | ||
try { | ||
User user = userService.findUserByEmail(authUser.getEmail()); | ||
Study existingStudy = datasetRegistrationService.findStudyById(studyId); | ||
|
||
// Manually validate the schema from an editing context. Validation with the schema tools | ||
// enforces it in a creation context but doesn't work for editing purposes. | ||
DatasetRegistrationSchemaV1UpdateValidator updateValidator = new DatasetRegistrationSchemaV1UpdateValidator(); | ||
Gson gson = GsonUtil.gsonBuilderWithAdapters().create(); | ||
DatasetRegistrationSchemaV1 registration = gson.fromJson(json, | ||
DatasetRegistrationSchemaV1.class); | ||
|
||
if (updateValidator.validate(existingStudy, registration)) { | ||
// Update study from registration | ||
Map<String, FormDataBodyPart> files = extractFilesFromMultiPart(multipart); | ||
Study updatedStudy = datasetRegistrationService.updateStudyFromRegistration( | ||
studyId, | ||
registration, | ||
user, | ||
files); | ||
return Response.ok(updatedStudy).build(); | ||
} else { | ||
return Response.status(Status.BAD_REQUEST).build(); | ||
} | ||
} catch (Exception e) { | ||
return createExceptionResponse(e); | ||
} | ||
} | ||
|
||
/** | ||
* Finds and validates all the files uploaded to the multipart. | ||
* | ||
* @param multipart Form data | ||
* @return Map of file body parts, where the key is the name of the field and the value is the | ||
* body part including the file(s). | ||
*/ | ||
private Map<String, FormDataBodyPart> extractFilesFromMultiPart(FormDataMultiPart multipart) { | ||
if (Objects.isNull(multipart)) { | ||
return Map.of(); | ||
} | ||
|
||
Map<String, FormDataBodyPart> files = new HashMap<>(); | ||
for (List<FormDataBodyPart> parts : multipart.getFields().values()) { | ||
for (FormDataBodyPart part : parts) { | ||
if (Objects.nonNull(part.getContentDisposition().getFileName())) { | ||
validateFileDetails(part.getContentDisposition()); | ||
files.put(part.getName(), part); | ||
} | ||
} | ||
} | ||
|
||
return files; | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
9 changes: 9 additions & 0 deletions
9
src/main/java/org/broadinstitute/consent/http/service/dao/DatasetDeletionException.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package org.broadinstitute.consent.http.service.dao; | ||
|
||
public class DatasetDeletionException extends RuntimeException { | ||
|
||
public DatasetDeletionException(Throwable cause) { | ||
super(cause); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.