diff --git a/src/main/java/org/breedinginsight/brapi/v2/BrAPIGermplasmController.java b/src/main/java/org/breedinginsight/brapi/v2/BrAPIGermplasmController.java index 948e68c20..84a6a17f0 100644 --- a/src/main/java/org/breedinginsight/brapi/v2/BrAPIGermplasmController.java +++ b/src/main/java/org/breedinginsight/brapi/v2/BrAPIGermplasmController.java @@ -1,5 +1,6 @@ package org.breedinginsight.brapi.v2; +import com.drew.lang.annotations.Nullable; import io.micronaut.context.annotation.Property; import io.micronaut.http.HttpHeaders; import io.micronaut.http.HttpResponse; @@ -19,10 +20,11 @@ import org.brapi.v2.model.BrAPIIndexPagination; import org.brapi.v2.model.BrAPIMetadata; import org.brapi.v2.model.BrAPIStatus; -import org.brapi.v2.model.core.BrAPITrial; import org.brapi.v2.model.germ.*; import org.brapi.v2.model.germ.request.BrAPIGermplasmSearchRequest; -import org.brapi.v2.model.germ.response.*; +import org.brapi.v2.model.germ.response.BrAPIGermplasmListResponse; +import org.brapi.v2.model.germ.response.BrAPIGermplasmPedigreeResponse; +import org.brapi.v2.model.germ.response.BrAPIGermplasmProgenyResponse; import org.breedinginsight.api.auth.ProgramSecured; import org.breedinginsight.api.auth.ProgramSecuredRoleGroup; import org.breedinginsight.api.model.v1.request.query.SearchRequest; @@ -33,27 +35,25 @@ import org.breedinginsight.brapi.v2.constants.BrAPIAdditionalInfoFields; import org.breedinginsight.brapi.v2.dao.BrAPIGermplasmDAO; import org.breedinginsight.brapi.v2.model.request.query.GermplasmQuery; -import org.breedinginsight.brapps.importer.services.ExternalReferenceSource; -import org.breedinginsight.model.Program; -import org.breedinginsight.services.ProgramService; -import org.breedinginsight.utilities.Utilities; -import org.breedinginsight.utilities.response.mappers.GermplasmQueryMapper; import org.breedinginsight.brapi.v2.services.BrAPIGermplasmService; import org.breedinginsight.brapps.importer.model.exports.FileType; import org.breedinginsight.daos.ProgramDAO; import org.breedinginsight.model.DownloadFile; import org.breedinginsight.model.GermplasmGenotype; +import org.breedinginsight.model.Program; +import org.breedinginsight.services.ProgramService; import org.breedinginsight.services.brapi.BrAPIEndpointProvider; import org.breedinginsight.services.exceptions.AuthorizationException; import org.breedinginsight.services.exceptions.DoesNotExistException; import org.breedinginsight.services.geno.GenotypeService; +import org.breedinginsight.utilities.Utilities; import org.breedinginsight.utilities.response.ResponseUtils; +import org.breedinginsight.utilities.response.mappers.GermplasmQueryMapper; import javax.inject.Inject; import javax.validation.Valid; import java.util.*; import java.util.regex.Pattern; -import java.util.stream.Collectors; @Slf4j @Controller("/${micronaut.bi.api.version}") @@ -209,7 +209,8 @@ public HttpResponse>>> getGermplasm( germplasmQueryMapper.setDateDisplayFormat(dateFormatParam); } - List germplasm = germplasmService.getGermplasm(programId); + // Fetch all germplasm in the program unless a list id is supplied to return only germplasm in that collection + List germplasm = queryParams.getListDbId() == null ? germplasmService.getGermplasm(programId) : germplasmService.getGermplasmByList(programId, queryParams.getListDbId()); SearchRequest searchRequest = queryParams.constructSearchRequest(); return ResponseUtils.getBrapiQueryResponse(germplasm, germplasmQueryMapper, queryParams, searchRequest); } catch (ApiException e) { @@ -221,52 +222,17 @@ public HttpResponse>>> getGermplasm( } } - @Get("/programs/{programId}/germplasm/lists/{listDbId}/records{?queryParams*}") - @Produces(MediaType.APPLICATION_JSON) - @ProgramSecured(roleGroups = {ProgramSecuredRoleGroup.PROGRAM_SCOPED_ROLES}) - public HttpResponse>>> getGermplasmListRecords( - @PathVariable("programId") UUID programId, - @PathVariable("listDbId") String listDbId, - @QueryValue @QueryValid(using = GermplasmQueryMapper.class) @Valid GermplasmQuery queryParams) { - try { - List germplasm = germplasmService.getGermplasmByList(programId, listDbId); - SearchRequest searchRequest = queryParams.constructSearchRequest(); - return ResponseUtils.getBrapiQueryResponse(germplasm, germplasmQueryMapper, queryParams, searchRequest); - } catch (Exception e) { - log.info(e.getMessage(), e); - return HttpResponse.status(HttpStatus.INTERNAL_SERVER_ERROR, "Error retrieving germplasm list records"); - } - } - - @Get("/programs/{programId}/germplasm/lists/{listDbId}/export{?fileExtension}") - @Produces(value = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") - @ProgramSecured(roleGroups = {ProgramSecuredRoleGroup.PROGRAM_SCOPED_ROLES}) - public HttpResponse germplasmListExport( - @PathVariable("programId") UUID programId, @PathVariable("listDbId") String listDbId, @QueryValue(defaultValue = "XLSX") String fileExtension) { - String downloadErrorMessage = "An error occurred while generating the download file. Contact the development team at bidevteam@cornell.edu."; - try { - FileType extension = Enum.valueOf(FileType.class, fileExtension); - DownloadFile germplasmListFile = germplasmService.exportGermplasmList(programId, listDbId, extension); - HttpResponse germplasmListExport = HttpResponse.ok(germplasmListFile.getStreamedFile()).header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename="+germplasmListFile.getFileName()+extension.getExtension()); - return germplasmListExport; - } - catch (Exception e) { - log.info(e.getMessage(), e); - e.printStackTrace(); - HttpResponse response = HttpResponse.status(HttpStatus.INTERNAL_SERVER_ERROR, downloadErrorMessage).contentType(MediaType.TEXT_PLAIN).body(downloadErrorMessage); - return response; - } - } - - @Get("/programs/{programId}/germplasm/export{?fileExtension}") + @Get("/programs/{programId}/germplasm/export{?fileExtension,list}") @Produces(value = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") @ProgramSecured(roleGroups = {ProgramSecuredRoleGroup.PROGRAM_SCOPED_ROLES}) public HttpResponse germplasmExport( - @PathVariable("programId") UUID programId, @QueryValue(defaultValue = "XLSX") String fileExtension) { + @PathVariable("programId") UUID programId, + @QueryValue(defaultValue = "XLSX") String fileExtension, + @QueryValue Optional list) { String downloadErrorMessage = "An error occurred while generating the download file. Contact the development team at bidevteam@cornell.edu."; try { FileType extension = Enum.valueOf(FileType.class, fileExtension); - DownloadFile germplasmListFile = germplasmService.exportGermplasm(programId, extension); + DownloadFile germplasmListFile = list.isEmpty() ? germplasmService.exportGermplasm(programId, extension) : germplasmService.exportGermplasmList(programId, list.get(), extension); HttpResponse germplasmExport = HttpResponse.ok(germplasmListFile.getStreamedFile()).header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename="+germplasmListFile.getFileName()+extension.getExtension()); return germplasmExport; } diff --git a/src/main/java/org/breedinginsight/brapi/v2/BrAPIListController.java b/src/main/java/org/breedinginsight/brapi/v2/BrAPIListController.java index 3dd6fd1f4..10528e298 100644 --- a/src/main/java/org/breedinginsight/brapi/v2/BrAPIListController.java +++ b/src/main/java/org/breedinginsight/brapi/v2/BrAPIListController.java @@ -1,29 +1,15 @@ -/* - * See the NOTICE file distributed with this work for additional information - * regarding copyright ownership. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - package org.breedinginsight.brapi.v2; +import io.micronaut.http.HttpRequest; import io.micronaut.http.HttpResponse; import io.micronaut.http.HttpStatus; import io.micronaut.http.MediaType; import io.micronaut.http.annotation.*; import io.micronaut.security.annotation.Secured; import io.micronaut.security.rules.SecurityRule; +import io.reactivex.rxjava3.core.Single; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.tuple.Pair; import org.brapi.client.v2.model.exceptions.ApiException; import org.brapi.v2.model.core.BrAPIListSummary; import org.brapi.v2.model.core.BrAPIListTypes; @@ -41,31 +27,35 @@ import org.breedinginsight.services.exceptions.DoesNotExistException; import org.breedinginsight.utilities.response.ResponseUtils; import org.breedinginsight.utilities.response.mappers.ListQueryMapper; +import reactor.core.publisher.Mono; +import reactor.util.function.Tuples; import javax.inject.Inject; import javax.validation.Valid; +import javax.validation.Validator; import java.util.List; +import java.util.Optional; import java.util.UUID; @Slf4j -@Controller +@Controller("/${micronaut.bi.api.version}/programs/{programId}" + BrapiVersion.BRAPI_V2) @Secured(SecurityRule.IS_AUTHENTICATED) public class BrAPIListController { - private final ProgramService programService; - private final BrAPIListService listService; + private final BrAPIListService brapiListService; private final ListQueryMapper listQueryMapper; + private final Validator validator; @Inject - public BrAPIListController(ProgramService programService, BrAPIListService listService, - ListQueryMapper listQueryMapper) { + public BrAPIListController(ProgramService programService, BrAPIListService brapiListService, + ListQueryMapper listQueryMapper, Validator validator) { this.programService = programService; - this.listService = listService; + this.brapiListService = brapiListService; this.listQueryMapper = listQueryMapper; + this.validator = validator; } - //@Get(BrapiVersion.BRAPI_V2 + "/lists") - @Get("/${micronaut.bi.api.version}/programs/{programId}" + BrapiVersion.BRAPI_V2 + "/lists{?queryParams*}") + @Get("/lists{?queryParams*}") @Produces(MediaType.APPLICATION_JSON) @ProgramSecured(roleGroups = {ProgramSecuredRoleGroup.PROGRAM_SCOPED_ROLES}) public HttpResponse>> getLists( @@ -77,7 +67,6 @@ public HttpResponse>> getLists( .getById(programId) .orElseThrow(() -> new DoesNotExistException("Program does not exist")); - // get germplasm lists by default BrAPIListTypes type = BrAPIListTypes.fromValue(queryParams.getListType()); String source = null; String id = null; @@ -93,7 +82,7 @@ public HttpResponse>> getLists( if (dateFormatParam != null) { listQueryMapper.setDateDisplayFormat(dateFormatParam); } - List brapiLists = listService.getListSummariesByTypeAndXref(type, source, id, program); + List brapiLists = brapiListService.getListSummariesByTypeAndXref(type, source, id, program); SearchRequest searchRequest = queryParams.constructSearchRequest(); return ResponseUtils.getBrapiQueryResponse(brapiLists, listQueryMapper, queryParams, searchRequest); @@ -105,4 +94,25 @@ public HttpResponse>> getLists( throw new RuntimeException(e); } } + + @Delete("/lists/{listDbId}") + @Produces(MediaType.APPLICATION_JSON) + @ProgramSecured(roleGroups = {ProgramSecuredRoleGroup.PROGRAM_SCOPED_ROLES}) + public HttpResponse deleteListById( + @PathVariable("programId") UUID programId, + @PathVariable("listDbId") String listDbId, + HttpRequest request + ) { + boolean hardDelete = false; + if (request.getParameters().contains("hardDelete")) { + String paramValue = request.getParameters().get("hardDelete"); + hardDelete = "true".equals(paramValue); + } + try { + return brapiListService.deleteBrAPIList(listDbId, programId, hardDelete); + } catch (Exception e) { + log.info(e.getMessage(), e); + return HttpResponse.status(HttpStatus.INTERNAL_SERVER_ERROR, "Error deleting germplasm list"); + } + } } diff --git a/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIListDAO.java b/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIListDAO.java index 801b9775c..e73b6836a 100644 --- a/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIListDAO.java +++ b/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIListDAO.java @@ -17,10 +17,15 @@ package org.breedinginsight.brapi.v2.dao; +import io.micronaut.http.HttpResponse; +import io.micronaut.http.HttpStatus; +import io.micronaut.http.exceptions.HttpStatusException; import lombok.extern.slf4j.Slf4j; +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; +import okhttp3.Request; import org.brapi.client.v2.ApiResponse; import org.brapi.client.v2.model.exceptions.ApiException; -import org.brapi.client.v2.model.queryParams.core.ListQueryParams; import org.brapi.client.v2.modules.core.ListsApi; import org.brapi.v2.model.BrAPIExternalReference; import org.brapi.v2.model.BrAPIResponse; @@ -29,21 +34,31 @@ import org.brapi.v2.model.core.BrAPIListTypes; import org.brapi.v2.model.core.request.BrAPIListNewRequest; import org.brapi.v2.model.core.request.BrAPIListSearchRequest; -import org.brapi.v2.model.core.response.*; -import org.brapi.v2.model.pheno.BrAPIObservation; +import org.brapi.v2.model.core.response.BrAPIListDetails; +import org.brapi.v2.model.core.response.BrAPIListsListResponse; +import org.brapi.v2.model.core.response.BrAPIListsListResponseResult; +import org.brapi.v2.model.core.response.BrAPIListsSingleResponse; +import org.breedinginsight.api.model.v1.response.DataResponse; +import org.breedinginsight.api.model.v1.response.Response; +import org.breedinginsight.brapi.v1.controller.BrapiVersion; import org.breedinginsight.brapps.importer.daos.ImportDAO; import org.breedinginsight.brapps.importer.model.ImportUpload; import org.breedinginsight.daos.ProgramDAO; +import org.breedinginsight.model.ProgramBrAPIEndpoints; +import org.breedinginsight.services.ProgramService; import org.breedinginsight.services.brapi.BrAPIEndpointProvider; +import org.breedinginsight.services.exceptions.DoesNotExistException; import org.breedinginsight.utilities.BrAPIDAOUtil; import org.breedinginsight.utilities.Utilities; import javax.inject.Inject; import javax.validation.constraints.NotNull; +import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.UUID; +import java.util.concurrent.TimeUnit; @Slf4j public class BrAPIListDAO { @@ -54,7 +69,10 @@ public class BrAPIListDAO { private final BrAPIEndpointProvider brAPIEndpointProvider; @Inject - public BrAPIListDAO(ProgramDAO programDAO, ImportDAO importDAO, BrAPIDAOUtil brAPIDAOUtil, BrAPIEndpointProvider brAPIEndpointProvider) { + public BrAPIListDAO(ProgramDAO programDAO, + ImportDAO importDAO, + BrAPIDAOUtil brAPIDAOUtil, + BrAPIEndpointProvider brAPIEndpointProvider) { this.programDAO = programDAO; this.importDAO = importDAO; this.brAPIDAOUtil = brAPIDAOUtil; @@ -196,4 +214,19 @@ public List createBrAPILists(List brapiLi throw new ApiException("No response after creating list"); } + + public HttpResponse deleteBrAPIList(String listDbId, UUID programId, boolean hardDelete) throws ApiException { + // TODO: Switch to using the ListsApi from the BrAPI client library once the delete endpoints from BI-2397 are merged. + var programBrAPIBaseUrl = brAPIDAOUtil.getProgramBrAPIBaseUrl(programId); + var requestUrl = HttpUrl.parse(programBrAPIBaseUrl + "/lists/" + listDbId).newBuilder(); + requestUrl.addQueryParameter("hardDelete", Boolean.toString(hardDelete)); + HttpUrl url = requestUrl.build(); + var brapiRequest = new Request.Builder().url(url) + .method("DELETE", null) + .addHeader("Content-Type", "application/json") + .build(); + + return brAPIDAOUtil.makeCall(brapiRequest); + } + } diff --git a/src/main/java/org/breedinginsight/brapi/v2/model/request/query/GermplasmQuery.java b/src/main/java/org/breedinginsight/brapi/v2/model/request/query/GermplasmQuery.java index 88f6d142c..81039b7ef 100644 --- a/src/main/java/org/breedinginsight/brapi/v2/model/request/query/GermplasmQuery.java +++ b/src/main/java/org/breedinginsight/brapi/v2/model/request/query/GermplasmQuery.java @@ -27,6 +27,9 @@ public class GermplasmQuery extends BrapiQuery { // This is a meta-parameter, it describes the display format of any date fields. private String dateDisplayFormat; + // The list id used to get a collection of germplasm + private String listDbId; + public SearchRequest constructSearchRequest() { List filters = new ArrayList<>(); if (!StringUtils.isBlank(getImportEntryNumber())) { diff --git a/src/main/java/org/breedinginsight/brapi/v2/services/BrAPIGermplasmService.java b/src/main/java/org/breedinginsight/brapi/v2/services/BrAPIGermplasmService.java index 8023327d7..231978368 100644 --- a/src/main/java/org/breedinginsight/brapi/v2/services/BrAPIGermplasmService.java +++ b/src/main/java/org/breedinginsight/brapi/v2/services/BrAPIGermplasmService.java @@ -82,7 +82,7 @@ public Optional getGermplasmByDBID(UUID programId, String germpl return germplasmDAO.getGermplasmByDBID(germplasmId, programId); } - public List> processListData(List germplasm, BrAPIListDetails germplasmList, Program program){ + public List> processListData(List germplasm, List listData, Program program){ Map germplasmByName = new HashMap<>(); for (BrAPIGermplasm g: germplasm) { // Use the full, unique germplasmName with programKey and accessionNumber (GID) for 2 reasons: @@ -97,14 +97,14 @@ public List> processListData(List germplasm, // This holds the BrAPI list items or all germplasm in a program if the list is null. List orderedGermplasmNames = new ArrayList<>(); - if (germplasmList == null) { + if (listData == null) { orderedGermplasmNames = germplasm.stream().sorted((left, right) -> { Integer leftAccessionNumber = Integer.parseInt(left.getAccessionNumber()); Integer rightAccessionNumber = Integer.parseInt(right.getAccessionNumber()); return leftAccessionNumber.compareTo(rightAccessionNumber); }).map(BrAPIGermplasm::getGermplasmName).collect(Collectors.toList()); } else { - orderedGermplasmNames = germplasmList.getData(); + orderedGermplasmNames = listData; } // For export, assign entry number sequentially based on BrAPI list order. @@ -124,7 +124,7 @@ public List> processListData(List germplasm, row.put("Source", source); // Use the entry number in the list map if generated - if(germplasmList == null) { + if(listData == null) { // Not downloading a real list, use GID (https://breedinginsight.atlassian.net/browse/BI-2266). row.put("Entry No", Integer.valueOf(germplasmEntry.getAccessionNumber())); } else { @@ -254,7 +254,9 @@ public DownloadFile exportGermplasm(UUID programId, FileType fileExtension) thro return new DownloadFile(fileName, downloadFile); } - public DownloadFile exportGermplasmList(UUID programId, String listId, FileType fileExtension) throws IllegalArgumentException, ApiException, IOException, DoesNotExistException { + public DownloadFile exportGermplasmList(UUID programId, + String listId, + FileType fileExtension) throws IllegalArgumentException, ApiException, IOException, DoesNotExistException { List columns = GermplasmFileColumns.getOrderedColumns(); //Retrieve germplasm list data @@ -270,7 +272,7 @@ public DownloadFile exportGermplasmList(UUID programId, String listId, FileType String fileName = createFileName(listData, listName); StreamedFile downloadFile; //Convert list data to List> data to pass into file writer - List> processedData = processListData(germplasm, listData, program); + List> processedData = processListData(germplasm, germplasmNames, program); if (fileExtension == FileType.CSV){ downloadFile = CSVWriter.writeToDownload(columns, processedData, fileExtension); diff --git a/src/main/java/org/breedinginsight/brapi/v2/services/BrAPIListService.java b/src/main/java/org/breedinginsight/brapi/v2/services/BrAPIListService.java index 3353ac17c..65be8f5cd 100644 --- a/src/main/java/org/breedinginsight/brapi/v2/services/BrAPIListService.java +++ b/src/main/java/org/breedinginsight/brapi/v2/services/BrAPIListService.java @@ -1,6 +1,7 @@ package org.breedinginsight.brapi.v2.services; import io.micronaut.context.annotation.Property; +import io.micronaut.http.HttpResponse; import lombok.extern.slf4j.Slf4j; import org.brapi.client.v2.model.exceptions.ApiException; import org.brapi.v2.model.BrAPIExternalReference; @@ -8,6 +9,8 @@ import org.brapi.v2.model.core.BrAPIListTypes; import org.brapi.v2.model.core.request.BrAPIListSearchRequest; import org.brapi.v2.model.core.response.BrAPIListsSingleResponse; +import org.breedinginsight.api.model.v1.response.DataResponse; +import org.breedinginsight.api.model.v1.response.Response; import org.breedinginsight.brapi.v2.dao.BrAPIGermplasmDAO; import org.breedinginsight.brapi.v2.dao.BrAPIListDAO; import org.breedinginsight.brapps.importer.services.ExternalReferenceSource; @@ -19,6 +22,7 @@ import javax.inject.Singleton; import java.util.List; import java.util.Optional; +import java.util.UUID; import java.util.stream.Collectors; @Slf4j @@ -88,4 +92,8 @@ public List getListSummariesByTypeAndXref( return programLists; } + + public HttpResponse deleteBrAPIList(String listDbId, UUID programId, boolean hardDelete) throws ApiException { + return listDAO.deleteBrAPIList(listDbId, programId, hardDelete); + } } diff --git a/src/main/java/org/breedinginsight/model/delta/DeltaEntity.java b/src/main/java/org/breedinginsight/model/delta/DeltaEntity.java index 11646f9f8..4f5b8ead9 100644 --- a/src/main/java/org/breedinginsight/model/delta/DeltaEntity.java +++ b/src/main/java/org/breedinginsight/model/delta/DeltaEntity.java @@ -7,7 +7,7 @@ public abstract class DeltaEntity { - protected final Gson gson; + protected static final Gson gson = new GsonBuilder().registerTypeAdapterFactory(new GeometryAdapterFactory()).create(); @NonNull protected final T entity; @@ -15,7 +15,6 @@ public abstract class DeltaEntity { // Note: do not use @Inject, DeltaEntity are always constructed by DeltaEntityFactory. protected DeltaEntity(@NonNull T entity) { this.entity = entity; - this.gson = new GsonBuilder().registerTypeAdapterFactory(new GeometryAdapterFactory()).create(); } protected T getEntity() { diff --git a/src/main/java/org/breedinginsight/model/delta/DeltaEntityFactory.java b/src/main/java/org/breedinginsight/model/delta/DeltaEntityFactory.java index 3787c5d71..059f91e66 100644 --- a/src/main/java/org/breedinginsight/model/delta/DeltaEntityFactory.java +++ b/src/main/java/org/breedinginsight/model/delta/DeltaEntityFactory.java @@ -4,14 +4,13 @@ import io.micronaut.context.annotation.Factory; import io.micronaut.context.annotation.Prototype; import lombok.NonNull; -import org.breedinginsight.brapps.importer.services.processors.experiment.service.ObservationUnitService; - import org.brapi.v2.model.core.BrAPIStudy; import org.brapi.v2.model.core.BrAPITrial; import org.brapi.v2.model.germ.BrAPIGermplasm; import org.brapi.v2.model.pheno.BrAPIObservation; import org.brapi.v2.model.pheno.BrAPIObservationUnit; import org.brapi.v2.model.pheno.BrAPIObservationVariable; +import org.breedinginsight.brapps.importer.services.processors.experiment.service.ObservationUnitService; import org.breedinginsight.model.ProgramLocation; import javax.inject.Inject; diff --git a/src/main/java/org/breedinginsight/utilities/BrAPIDAOUtil.java b/src/main/java/org/breedinginsight/utilities/BrAPIDAOUtil.java index e03a67017..15d98e1c3 100644 --- a/src/main/java/org/breedinginsight/utilities/BrAPIDAOUtil.java +++ b/src/main/java/org/breedinginsight/utilities/BrAPIDAOUtil.java @@ -18,25 +18,36 @@ 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; import io.micronaut.http.server.exceptions.InternalServerException; import io.reactivex.functions.*; import lombok.extern.slf4j.Slf4j; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; import org.brapi.client.v2.ApiResponse; import org.brapi.client.v2.model.exceptions.ApiException; -import org.brapi.client.v2.modules.germplasm.GermplasmApi; import org.brapi.v2.model.*; -import org.brapi.v2.model.germ.BrAPIGermplasm; -import org.brapi.v2.model.germ.response.BrAPIGermplasmSingleResponse; +import org.breedinginsight.api.model.v1.response.DataResponse; +import org.breedinginsight.brapi.v1.controller.BrapiVersion; import org.breedinginsight.brapps.importer.model.ImportUpload; +import org.breedinginsight.model.ProgramBrAPIEndpoints; +import org.breedinginsight.services.ProgramService; +import org.breedinginsight.services.exceptions.DoesNotExistException; import javax.inject.Inject; import javax.inject.Singleton; +import java.io.IOException; import java.time.Duration; import java.util.ArrayList; import java.util.List; import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.TimeUnit; import static org.brapi.v2.model.BrAPIWSMIMEDataTypes.APPLICATION_JSON; @@ -48,16 +59,19 @@ public class BrAPIDAOUtil { private final Duration searchTimeout; private final int pageSize; private final int postGroupSize; + private final ProgramService programService; @Inject public BrAPIDAOUtil(@Property(name = "brapi.search.wait-time") int searchWaitTime, @Property(name = "brapi.read-timeout") Duration searchTimeout, @Property(name = "brapi.page-size") int pageSize, - @Property(name = "brapi.post-group-size") int postGroupSize) { + @Property(name = "brapi.post-group-size") int postGroupSize, + ProgramService programService) { this.searchWaitTime = searchWaitTime; this.searchTimeout = searchTimeout; this.pageSize = pageSize; this.postGroupSize = postGroupSize; + this.programService = programService; } public List search(Function, Optional>>> searchMethod, @@ -366,4 +380,42 @@ public List post(List brapiObjects, return post(brapiObjects, null, postMethod, null); } + public HttpResponse makeCall(Request brapiRequest) { + // Create OkHttpClient with timeout + OkHttpClient client = new OkHttpClient.Builder() + .readTimeout(5, TimeUnit.MINUTES) + .build(); + + try (Response brapiResponse = client.newCall(brapiRequest).execute()) { + int statusCode = brapiResponse.code(); + + if (!brapiResponse.isSuccessful()) { + return HttpResponse.status(HttpStatus.valueOf(statusCode)); + } + + String responseBody = brapiResponse.body() != null ? brapiResponse.body().string() : ""; + return HttpResponse.status(HttpStatus.valueOf(statusCode), responseBody); + + } catch (IOException e) { + log.error("Error calling BrAPI Service", e); + throw new HttpStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "Error calling BrAPI Service"); + } + } + + public String getProgramBrAPIBaseUrl(UUID programId) { + ProgramBrAPIEndpoints programBrAPIEndpoints; + try { + programBrAPIEndpoints = programService.getBrapiEndpoints(programId); + } catch (DoesNotExistException e) { + throw new HttpStatusException(HttpStatus.NOT_FOUND, "Program does not exist"); + } + + if(programBrAPIEndpoints.getCoreUrl().isEmpty()) { + log.error("Program: " + programId + " is missing BrAPI URL config"); + throw new HttpStatusException(HttpStatus.INTERNAL_SERVER_ERROR, ""); + } + var programBrAPIBaseUrl = programBrAPIEndpoints.getCoreUrl().get(); + programBrAPIBaseUrl = programBrAPIBaseUrl.endsWith("/") ? programBrAPIBaseUrl.substring(0, programBrAPIBaseUrl.length() - 1) : programBrAPIBaseUrl; + return programBrAPIBaseUrl.endsWith(BrapiVersion.BRAPI_V2) ? programBrAPIBaseUrl : programBrAPIBaseUrl + BrapiVersion.BRAPI_V2; + } } diff --git a/src/main/java/org/breedinginsight/utilities/response/ResponseUtils.java b/src/main/java/org/breedinginsight/utilities/response/ResponseUtils.java index 4cee82f99..cb8791fae 100644 --- a/src/main/java/org/breedinginsight/utilities/response/ResponseUtils.java +++ b/src/main/java/org/breedinginsight/utilities/response/ResponseUtils.java @@ -18,6 +18,7 @@ package org.breedinginsight.utilities.response; import io.micronaut.http.HttpResponse; +import io.micronaut.http.HttpResponseFactory; import io.micronaut.http.HttpStatus; import io.micronaut.http.MediaType; import io.micronaut.http.exceptions.HttpStatusException; diff --git a/src/test/java/org/breedinginsight/brapi/v2/GermplasmControllerIntegrationTest.java b/src/test/java/org/breedinginsight/brapi/v2/GermplasmControllerIntegrationTest.java index 23e8f54d4..32781dd8c 100644 --- a/src/test/java/org/breedinginsight/brapi/v2/GermplasmControllerIntegrationTest.java +++ b/src/test/java/org/breedinginsight/brapi/v2/GermplasmControllerIntegrationTest.java @@ -245,7 +245,7 @@ public void getAllGermplasmByListSuccess() { String germplasmListDbId = fetchGermplasmListDbId(programId); // Build the endpoint to get germplasm by germplasm list. - String endpoint = String.format("/programs/%s/germplasm/lists/%s/records", programId, germplasmListDbId); + String endpoint = String.format("/programs/%s/brapi/v2/germplasm?listDbId=%s", programId, germplasmListDbId); // Get germplasm by list. Flowable> call = client.exchange( @@ -258,7 +258,7 @@ public void getAllGermplasmByListSuccess() { JsonObject result = JsonParser.parseString(response.body()).getAsJsonObject().getAsJsonObject("result"); JsonArray data = result.getAsJsonArray("data"); - assertEquals(data.size(), 3, "Wrong number of germplasm were returned"); + assertEquals(3, data.size(), "Wrong number of germplasm were returned"); } @Test diff --git a/src/test/java/org/breedinginsight/brapi/v2/ListControllerIntegrationTest.java b/src/test/java/org/breedinginsight/brapi/v2/ListControllerIntegrationTest.java index 9515b72a6..2d421d306 100644 --- a/src/test/java/org/breedinginsight/brapi/v2/ListControllerIntegrationTest.java +++ b/src/test/java/org/breedinginsight/brapi/v2/ListControllerIntegrationTest.java @@ -23,6 +23,7 @@ import io.micronaut.http.HttpStatus; import io.micronaut.http.client.RxHttpClient; import io.micronaut.http.client.annotation.Client; +import io.micronaut.http.client.exceptions.HttpClientResponseException; import io.micronaut.http.netty.cookies.NettyCookie; import io.micronaut.test.extensions.junit5.annotation.MicronautTest; import io.reactivex.Flowable; @@ -41,6 +42,7 @@ import org.breedinginsight.model.Program; import org.breedinginsight.services.SpeciesService; import org.jooq.DSLContext; +import org.junit.Rule; import org.junit.jupiter.api.*; import javax.inject.Inject; @@ -49,6 +51,7 @@ import java.time.OffsetDateTime; import java.util.*; +import static io.micronaut.http.HttpRequest.DELETE; import static io.micronaut.http.HttpRequest.GET; import static org.junit.jupiter.api.Assertions.*; @@ -145,13 +148,12 @@ public void setup() { newExp.put(traits.get(0).getObservationVariableName(), "1"); JsonObject result = importTestUtils.uploadAndFetchWorkflow( - importTestUtils.writeExperimentDataToFile(List.of(newExp), traits), null, true, client, program, mappingId, newExperimentWorkflowId - ); - + importTestUtils.writeExperimentDataToFile(List.of(newExp), traits), null, true, client, program, mappingId, newExperimentWorkflowId); } @Test @SneakyThrows + @Order(1) public void getAllListsSuccess() { // A GET request to the brapi/v2/lists endpoint with no query params should return all lists. Flowable> call = client.exchange( @@ -159,6 +161,12 @@ public void getAllListsSuccess() { .cookie(new NettyCookie("phylo-token", "test-registered-user")), String.class ); + // Collect all responses + List> responses = call.toList().blockingGet(); + + // Ensure we got a response + assertFalse(responses.isEmpty(), "No response received"); + // Ensure 200 OK response. HttpResponse response = call.blockingFirst(); assertEquals(HttpStatus.OK, response.getStatus()); @@ -190,4 +198,48 @@ public void getAllListsSuccess() { } } + + @Test + @SneakyThrows + @Order(2) + public void deleteListSuccess() { + // A GET request to the brapi/v2/lists endpoint with no query params should return all lists. + Flowable> getCall = client.exchange( + GET(String.format("/programs/%s/brapi/v2/lists", program.getId().toString())) + .cookie(new NettyCookie("phylo-token", "test-registered-user")), String.class + ); + + // Ensure 200 OK response for fetching lists. + HttpResponse beforeResponse = getCall.blockingFirst(); + assertEquals(HttpStatus.OK, beforeResponse.getStatus()); + + // Parse result. + JsonObject beforeResult = JsonParser.parseString(beforeResponse.body()).getAsJsonObject().getAsJsonObject("result"); + JsonArray beforeData = beforeResult.getAsJsonArray("data"); + + // A DELETE request to the brapi/v2/lists/ endpoint with no query params should delete the list. + String deleteListDbId = beforeData.get(0).getAsJsonObject().get("listDbId").getAsString(); + Flowable> deleteCall = client.exchange( + DELETE(String.format("/programs/%s/brapi/v2/lists/%s", program.getId().toString(), deleteListDbId)) + .cookie(new NettyCookie("phylo-token", "test-registered-user")), String.class + ); + + // Ensure 204 NO_CONTENT response for deleting a list. + HttpResponse deleteResponse = deleteCall.blockingFirst(); + assertEquals(HttpStatus.NO_CONTENT, deleteResponse.getStatus()); + + + // A DELETE request to the brapi/v2/lists/ endpoint with invalid dbId. + Flowable> invalidDeleteCall = client.exchange( + DELETE(String.format("/programs/%s/brapi/v2/lists/%s", program.getId().toString(), "NOT-VALID-DBID")) + .cookie(new NettyCookie("phylo-token", "test-registered-user")), String.class + ); + + // Ensure 404 NOT_FOUND response for requesting to delete a non-existant list. + try { + invalidDeleteCall.blockingFirst(); + } catch(HttpClientResponseException e) { + assertEquals(HttpStatus.NOT_FOUND, e.getStatus()); + } + } } diff --git a/src/test/java/org/breedinginsight/daos/BrAPIDAOUtilUnitTest.java b/src/test/java/org/breedinginsight/daos/BrAPIDAOUtilUnitTest.java index 0cb51387e..427ed6d88 100644 --- a/src/test/java/org/breedinginsight/daos/BrAPIDAOUtilUnitTest.java +++ b/src/test/java/org/breedinginsight/daos/BrAPIDAOUtilUnitTest.java @@ -1,6 +1,7 @@ package org.breedinginsight.daos; import com.google.gson.JsonObject; +import io.micronaut.test.annotation.MockBean; import lombok.SneakyThrows; import org.apache.commons.lang3.tuple.Pair; import org.brapi.client.v2.ApiResponse; @@ -11,6 +12,7 @@ import org.brapi.v2.model.germ.response.BrAPIGermplasmListResponseResult; import org.breedinginsight.brapps.importer.services.ExternalReferenceSource; import org.breedinginsight.model.Program; +import org.breedinginsight.services.ProgramService; import org.breedinginsight.utilities.BrAPIDAOUtil; import org.breedinginsight.utilities.Utilities; import org.junit.jupiter.api.BeforeEach; @@ -21,6 +23,8 @@ import java.time.temporal.ChronoUnit; import java.util.*; +import static org.mockito.Mockito.mock; + @TestInstance(TestInstance.Lifecycle.PER_CLASS) public class BrAPIDAOUtilUnitTest { @@ -30,6 +34,10 @@ public class BrAPIDAOUtilUnitTest { private Program testProgram; private List paginatedGermplasm; private BrAPIGermplasmSearchRequest germplasmSearch; + @MockBean(ProgramService.class) + ProgramService programService() { + return mock(ProgramService.class); + } public Integer fetchPaginatedGermplasm(int page, int pageSize) { paginatedGermplasm = new ArrayList<>(); @@ -62,7 +70,7 @@ public ApiResponse, Optional