Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -281,4 +281,42 @@ public HttpResponse<Response<SampleSubmission>> checkVendorStatus(@PathVariable
return HttpResponse.serverError();
}
}

/**
* Delete sample submission.
* Deletes the bidb submission record and BrAPI samples & plates
* @param programId bi-api id of program
* @param submissionId bi-api id of submission
* @return HttpResponse
* @throws ApiException
*/
@Delete("programs/{programId}/submissions/{submissionId}")
@Produces(MediaType.APPLICATION_JSON)
// sys admin and program admin roles to match file import permissions
@ProgramSecured(roles = {ProgramSecuredRole.SYSTEM_ADMIN, ProgramSecuredRole.PROGRAM_ADMIN})
public HttpResponse deleteSubmissionById(@PathVariable UUID programId, @PathVariable UUID submissionId) throws ApiException {

// program validation
Optional<Program> program = programService.getById(programId);
if(program.isEmpty()) {
log.info(String.format("programId not found: %s", programId.toString()));
return HttpResponse.notFound();
}

// sample status validation
Optional<SampleSubmission> submissionOpt = sampleSubmissionService.getSampleSubmission(program.get(), submissionId, false);

if(submissionOpt.isEmpty()) {
return HttpResponse.notFound();
}
SampleSubmission submission = submissionOpt.get();
if (!submission.isDeletable()) {
return HttpResponse.notAllowed();
}

sampleSubmissionService.deleteSampleSubmission(program.get(), submissionId);

return HttpResponse.ok();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,15 @@

package org.breedinginsight.brapps.importer.daos;

import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import io.micronaut.context.annotation.Property;
import io.micronaut.http.server.exceptions.InternalServerException;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
import org.brapi.client.v2.JSON;
import org.brapi.client.v2.model.exceptions.ApiException;
import org.brapi.client.v2.modules.genotype.SamplesApi;
import org.brapi.v2.model.geno.BrAPISample;
Expand All @@ -33,9 +40,9 @@

import javax.inject.Inject;
import javax.inject.Singleton;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.UUID;

@Slf4j
@Singleton
Expand All @@ -47,6 +54,7 @@ public class BrAPISampleDAO {
private final ImportDAO importDAO;
private final BrAPIDAOUtil brAPIDAOUtil;
private final BrAPIEndpointProvider brAPIEndpointProvider;
private final Gson gson = new JSON().getGson();

@Inject
public BrAPISampleDAO(ProgramDAO programDAO,
Expand Down Expand Up @@ -102,4 +110,149 @@ public List<BrAPISample> readSamplesBySubmissionIds(Program program, List<String
SamplesApi samplesApi = brAPIEndpointProvider.get(programDAO.getSampleClient(program.getId()), SamplesApi.class);
return brAPIDAOUtil.search(samplesApi::searchSamplesPost, samplesApi::searchSamplesSearchResultsDbIdGet, searchRequest);
}

/**
* Deletes all samples specified in the brapi server
* @param program
* @param sampleDbIds
* @throws ApiException
*/
public void deleteSamples(Program program, List<String> sampleDbIds) throws ApiException {
// create batch of samples, not yet included in brapi client TODO: switch to brapi client when available
String programBrAPIBaseUrl = brAPIDAOUtil.getProgramBrAPIBaseUrl(program.getId());
String batchDbId = postSamplesBatch(programBrAPIBaseUrl, sampleDbIds);

// delete samples specified in batch
deleteBatch(programBrAPIBaseUrl, batchDbId);
}

/**
* Deletes all plates specified in the brapi server
* @param program
* @param plateDbIds
* @throws ApiException
*/
public void deletePlates(Program program, List<String> plateDbIds) throws ApiException {
// create batch of plates, not yet included in brapi client TODO: switch to brapi client when available
String programBrAPIBaseUrl = brAPIDAOUtil.getProgramBrAPIBaseUrl(program.getId());
String batchDbId = postPlatesBatch(programBrAPIBaseUrl, plateDbIds);

// delete plates specified in batch
deleteBatch(programBrAPIBaseUrl, batchDbId);
}


private String postSamplesBatch(String programBrAPIBaseUrl, List<String> sampleDbIds) throws ApiException {
HttpUrl.Builder requestUrl = HttpUrl.parse(programBrAPIBaseUrl + "/batchDeletes").newBuilder();
SampleBatchDeleteRequest requestBody = new SampleBatchDeleteRequest(sampleDbIds);
String json = gson.toJson(requestBody);
RequestBody body = RequestBody.create(json, MediaType.get("application/json"));
HttpUrl url = requestUrl.build();
return postBatch(url, body, programBrAPIBaseUrl);
}

private String postPlatesBatch(String programBrAPIBaseUrl, List<String> plateDbIds) throws ApiException {
HttpUrl.Builder requestUrl = HttpUrl.parse(programBrAPIBaseUrl + "/batchDeletes").newBuilder();
PlateBatchDeleteRequest requestBody = new PlateBatchDeleteRequest(plateDbIds);
String json = gson.toJson(requestBody);
RequestBody body = RequestBody.create(json, MediaType.get("application/json"));
HttpUrl url = requestUrl.build();
return postBatch(url, body, programBrAPIBaseUrl);
}

private String postBatch(HttpUrl url, RequestBody body, String programBrAPIBaseUrl) throws ApiException {

Request brapiRequest = new Request.Builder()
.url(url)
.post(body)
.addHeader("Content-Type", "application/json")
.build();

String jsonResponse = brAPIDAOUtil.makeCallWithResponse(brapiRequest);
JsonElement rootElement = JsonParser.parseString(jsonResponse);
JsonObject rootObject = rootElement.getAsJsonObject();
JsonObject resultObject = rootObject.getAsJsonObject("result");

// check to see if immediate response or searchResultId
if(resultObject.has("batchDeleteDbId")) {
return resultObject.get("batchDeleteDbId").getAsString();
} else if (resultObject.has("searchResultsDbId")) {
// TODO: once api stuff is in client use BrAPIDAOUtil::search to handle retries, for now just request once
// brapi server only returns immediate response for batchDeletes so this case won't happen
return getBatchDeleteDbIdFromSearchResult(programBrAPIBaseUrl, resultObject.get("searchResultsDbId").getAsString());
} else {
throw new InternalServerException("Expected batchDeleteDbId or searchResultsDbId but got " + resultObject);
}
}

private String getBatchDeleteDbIdFromSearchResult(String programBrAPIBaseUrl, String searchResultDbId) throws ApiException {
HttpUrl.Builder requestUrl = HttpUrl.parse(programBrAPIBaseUrl + "/search/batchDeletes/" + searchResultDbId).newBuilder();

HttpUrl url = requestUrl.build();
Request brapiRequest = new Request.Builder()
.url(url)
.method("GET", null)
.addHeader("Content-Type", "application/json")
.build();

String jsonResponse = brAPIDAOUtil.makeCallWithResponse(brapiRequest);
JsonElement rootElement = JsonParser.parseString(jsonResponse);
JsonObject rootObject = rootElement.getAsJsonObject();
JsonObject resultObject = rootObject.getAsJsonObject("result");
return resultObject.get("batchDeleteDbId").getAsString();
}

private void deleteBatch(String programBrAPIBaseUrl, String batchDbId) throws ApiException {
HttpUrl.Builder requestUrl = HttpUrl.parse(programBrAPIBaseUrl + "/batchDeletes/" + batchDbId).newBuilder();
requestUrl.addQueryParameter("hardDelete", "true");

HttpUrl url = requestUrl.build();
Request brapiRequest = new Request.Builder()
.url(url)
.method("DELETE", null)
.addHeader("Content-Type", "application/json")
.build();

brAPIDAOUtil.makeCall(brapiRequest);
}

/**
* TODO: temporary minimal model here until brapi client is updated with delete models
*/
public class SampleBatchDeleteRequest {
private String batchDeleteType;
private Search search;

public SampleBatchDeleteRequest(List<String> sampleDbIds) {
this.batchDeleteType = "samples";
this.search = new Search(sampleDbIds);
}

private class Search {
private List<String> sampleDbIds;

public Search(List<String> sampleDbIds) {
this.sampleDbIds = sampleDbIds;
}
}
}

public class PlateBatchDeleteRequest {
private String batchDeleteType;
private Search search;

public PlateBatchDeleteRequest(List<String> plateDbIds) {
this.batchDeleteType = "plates";
this.search = new Search(plateDbIds);
}

private class Search {
private List<String> plateDbIds;

public Search(List<String> plateDbIds) {
this.plateDbIds = plateDbIds;
}
}
}

}
10 changes: 10 additions & 0 deletions src/main/java/org/breedinginsight/model/SampleSubmission.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

package org.breedinginsight.model;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
Expand Down Expand Up @@ -79,6 +80,15 @@ private void parseShipmentForms(JSONB shipmentforms) {
}
}

/**
* Should only be deleted when status is not submitted and has no vendor status
*/
@JsonIgnore
public boolean isDeletable() {
return (this.getSubmitted() == null || (this.getSubmitted() != null && !this.getSubmitted()))
&& this.getVendorStatus() == null;
}

public enum Status {
NOT_SUBMITTED("NOT SUBMITTED"),
SUBMITTED("SUBMITTED"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -427,4 +427,28 @@ public Optional<SampleSubmission> updateSubmissionStatus(Program program, UUID s

return submissionOptional;
}

/**
* Deletes BrAPI plates and submission objects as well as sample submission record in bidb
* We do not currently cache plates or samples so don't need to worry about that
* @param submissionId sample submission UUID to delete
* @exception ApiException if a BrAPI call fails
*/
public void deleteSampleSubmission(Program program, UUID submissionId) throws ApiException {
// create a batch of sampleIds and plateIds to delete
// get samples with the sample submission xref
List<BrAPISample> samples = sampleDAO.readSamplesBySubmissionIds(program, List.of(submissionId.toString()));

// extract sampleDbIds and plateDbIds to include in batches
List<String> sampleDbIds = samples.stream().map(BrAPISample::getSampleDbId).distinct().collect(Collectors.toList());
List<String> platesDbIds = samples.stream().map(BrAPISample::getPlateDbId).distinct().collect(Collectors.toList());

// delete samples and plates BrAPI objects in brapi server
sampleDAO.deleteSamples(program, sampleDbIds);
sampleDAO.deletePlates(program, platesDbIds);

// delete sample submission record from bidb
submissionDAO.deleteById(submissionId);
}

}
23 changes: 23 additions & 0 deletions src/main/java/org/breedinginsight/utilities/BrAPIDAOUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
package org.breedinginsight.utilities;

import io.micronaut.context.annotation.Property;

import io.micronaut.http.HttpResponse;
import io.micronaut.http.HttpStatus;
import io.micronaut.http.exceptions.HttpStatusException;
Expand Down Expand Up @@ -380,6 +381,28 @@ public <T> List<T> post(List<T> brapiObjects,
return post(brapiObjects, null, postMethod, null);
}

/**
* TODO: replace with brapi client methods when available, will do timeout spec from config at that point
* @param brapiRequest
* @return
* @throws ApiException
*/
public String makeCallWithResponse(Request brapiRequest) throws ApiException {
OkHttpClient client = new OkHttpClient.Builder()
.readTimeout(5, TimeUnit.MINUTES)
.build();

// autoclose Response
try (Response response = client.newCall(brapiRequest).execute()) {
if (!response.isSuccessful()) {
throw new ApiException("Request failed with status code: " + response.code());
}
return response.body().string();
} catch (IOException e) {
throw new ApiException(e);
}
}

public HttpResponse<String> makeCall(Request brapiRequest) {
// Create OkHttpClient with timeout
OkHttpClient client = new OkHttpClient.Builder()
Expand Down
4 changes: 2 additions & 2 deletions src/main/resources/version.properties
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@
#


version=v1.1.0+899
versionInfo=https://github.com/Breeding-Insight/bi-api/commit/1d19a829223e99b09324a69b51fa47c6b965de64
version=v1.1.0+901
versionInfo=https://github.com/Breeding-Insight/bi-api/commit/9c0fdb5b160215a40e5f2df57a7e922ec0036052
Loading