diff --git a/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIGermplasmDAO.java b/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIGermplasmDAO.java index 998662370..cc203255a 100644 --- a/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIGermplasmDAO.java +++ b/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIGermplasmDAO.java @@ -17,6 +17,8 @@ package org.breedinginsight.brapi.v2.dao; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; import com.google.gson.JsonObject; import io.micronaut.context.annotation.Property; import io.micronaut.http.HttpStatus; @@ -27,7 +29,9 @@ import org.brapi.client.v2.model.queryParams.germplasm.GermplasmQueryParams; import org.brapi.client.v2.modules.germplasm.GermplasmApi; import org.brapi.v2.model.BrAPIExternalReference; +import org.brapi.v2.model.core.BrAPIListItemWithFullItem; import org.brapi.v2.model.core.BrAPIProgram; +import org.brapi.v2.model.core.response.BrAPIListItemsListResponse; import org.brapi.v2.model.germ.BrAPIGermplasm; import org.brapi.v2.model.germ.BrAPIGermplasmSynonyms; import org.brapi.v2.model.germ.request.BrAPIGermplasmSearchRequest; @@ -45,6 +49,7 @@ import org.breedinginsight.utilities.Utilities; import javax.inject.Inject; +import javax.inject.Named; import javax.inject.Singleton; import java.util.*; import java.util.stream.Collectors; @@ -56,6 +61,8 @@ public class BrAPIGermplasmDAO { private final ProgramDAO programDAO; private final ImportDAO importDAO; private final BrAPIDAOUtil brAPIDAOUtil; + private final ObjectMapper objectMapper; + private final BrAPIListDAO brAPIListDAO; @Property(name = "brapi.server.reference-source") private String referenceSource; @@ -72,12 +79,16 @@ public BrAPIGermplasmDAO(ProgramDAO programDAO, ImportDAO importDAO, BrAPIDAOUtil brAPIDAOUtil, BrAPIEndpointProvider brAPIEndpointProvider, - @Property(name = "brapi.cache.fetch-page-size") int brapiFetchPageSize) { + @Property(name = "brapi.cache.fetch-page-size") int brapiFetchPageSize, + ObjectMapper objectMapper, + BrAPIListDAO brAPIListDAO) { this.programDAO = programDAO; this.importDAO = importDAO; this.brAPIDAOUtil = brAPIDAOUtil; this.brAPIEndpointProvider = brAPIEndpointProvider; this.brapiMaxPageSize = brapiFetchPageSize; + this.objectMapper = objectMapper; + this.brAPIListDAO = brAPIListDAO; } /** @@ -427,13 +438,20 @@ public List getGermplasmByRawName(List germplasmNames, U .collect(Collectors.toList()); } - public BrAPIGermplasmListResponse searchGermplasmByRawName(List germplasmNames, UUID programId, GermplasmQuery germplasmQuery) throws ApiException { + public BrAPIListItemsListResponse searchGermplasmByList(String listDbId, UUID programId, GermplasmQuery germplasmQuery) throws ApiException { Program program = new Program(programDAO.fetchOneById(programId)); - BrAPIGermplasmSearchRequest searchRequest = new BrAPIGermplasmSearchRequest(); - searchRequest.setGermplasmNames(germplasmNames); + BrAPIGermplasmSearchRequest searchRequest = buildSearchRequest(program, null, germplasmQuery); + + // Because the list item search endpoint is generalized, a typed BrAPISearchRequest won't work, so convert it to JsonNode. + // The search request type is derived by the client/prod server based off of the type of the submitted list dbId. + JsonNode listRequestBody = objectMapper.valueToTree(searchRequest); + + BrAPIListItemsListResponse response = brAPIListDAO.searchBrAPIListItemsByList(listDbId, programId, listRequestBody); + + processGermplasmForDisplay(response.getResult().getData().stream().map(BrAPIListItemWithFullItem::getItem).collect(Collectors.toList()), program); - return brapiGermplasmSearchReturnResponse(program, searchRequest, germplasmQuery); + return response; } public List brapiGermplasmSearchReturnList(Program program, 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 4f6cfdec7..be9b3fcd3 100644 --- a/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIListDAO.java +++ b/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIListDAO.java @@ -17,6 +17,7 @@ package org.breedinginsight.brapi.v2.dao; +import com.fasterxml.jackson.databind.JsonNode; import io.micronaut.http.HttpResponse; import lombok.extern.slf4j.Slf4j; import okhttp3.HttpUrl; @@ -27,14 +28,13 @@ import org.brapi.v2.model.BrAPIExternalReference; import org.brapi.v2.model.BrAPIResponse; import org.brapi.v2.model.BrAPIResponseResult; +import org.brapi.v2.model.core.BrAPIListItemWithFullItem; import org.brapi.v2.model.core.BrAPIListSummary; 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.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.brapi.v2.model.core.response.*; +import org.brapi.v2.model.germ.BrAPIGermplasm; import org.breedinginsight.brapps.importer.daos.ImportDAO; import org.breedinginsight.brapps.importer.model.ImportUpload; import org.breedinginsight.daos.ProgramDAO; @@ -71,7 +71,7 @@ public BrAPIListDAO(ProgramDAO programDAO, } public List getListsByName(List listNames, UUID programId) throws ApiException { - if(listNames.isEmpty()) { + if (listNames.isEmpty()) { return Collections.emptyList(); } @@ -122,11 +122,11 @@ private List processListsForProgram(List pro // check that all lists were created via the BI UI in case the BrAPI service silently ignores the search by // externalReference source and ID List filteredLists = new ArrayList<>(); - for (BrAPIListSummary list: programLists) { + for (BrAPIListSummary list : programLists) { if (list.getExternalReferences() == null || list.getExternalReferences().size() == 0) { continue; } else { - for (BrAPIExternalReference ref: list.getExternalReferences()) { + for (BrAPIExternalReference ref : list.getExternalReferences()) { if (ref.getReferenceID().equals(externalReferenceId.toString()) && ref.getReferenceSource().equals(externalReferenceSource)) { filteredLists.add(list); @@ -136,6 +136,7 @@ private List processListsForProgram(List pro } return filteredLists; } + public List updateBrAPIList(String brAPIListDbId, BrAPIListDetails mutatedList, UUID programId) throws ApiException { ListsApi api = brAPIEndpointProvider.get(programDAO.getCoreClient(programId), ListsApi.class); BrAPIListNewRequest request = new BrAPIListNewRequest(); @@ -160,7 +161,7 @@ public List updateBrAPIList(String brAPIListDbId, BrAPIListDetails mutat log.error(Utilities.generateApiExceptionLogMessage(e)); throw e; } - if(response != null) { + if (response != null) { BrAPIListsSingleResponse body = response.getBody(); if (body == null) { throw new ApiException("Response is missing body", 0, response.getHeaders(), null); @@ -188,7 +189,7 @@ public List createBrAPILists(List brapiLi log.warn(Utilities.generateApiExceptionLogMessage(e)); throw e; } - if(response != null) { + if (response != null) { BrAPIResponse body = response.getBody(); if (body == null) { throw new ApiException("Response is missing body", 0, response.getHeaders(), null); @@ -220,4 +221,32 @@ public HttpResponse deleteBrAPIList(String listDbId, UUID programId, boo return brAPIDAOUtil.makeCall(brapiRequest); } + public BrAPIListItemsListResponse searchBrAPIListItemsByList(String listDbId, UUID programId, JsonNode requestBody) throws ApiException { + ListsApi api = brAPIEndpointProvider.get(programDAO.getCoreClient(programId), ListsApi.class); + // Do manually, it doesn't like List to List for some reason + ApiResponse> response; + try { + response = api.searchListItemsByList(listDbId, requestBody); + } catch (ApiException e) { + log.warn(Utilities.generateApiExceptionLogMessage(e)); + throw e; + } + + if (response != null) { + BrAPIResponse> body = response.getBody(); + if (body == null) { + throw new ApiException("Response is missing body", 0, response.getHeaders(), null); + } + BrAPIResponseResult> result = body.getResult(); + if (result == null) { + throw new ApiException("Response body is missing result", 0, response.getHeaders(), response.getBody().toString()); + } + if (result.getData() == null) { + throw new ApiException("Response result is missing data", 0, response.getHeaders(), response.getBody().toString()); + } + return response.getBody(); + } + + throw new ApiException("No response for BrAPI list item search"); + } } 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 333d9910f..03d7ddfc5 100644 --- a/src/main/java/org/breedinginsight/brapi/v2/services/BrAPIGermplasmService.java +++ b/src/main/java/org/breedinginsight/brapi/v2/services/BrAPIGermplasmService.java @@ -7,12 +7,15 @@ import org.brapi.client.v2.model.exceptions.ApiException; import org.brapi.v2.model.BrAPIExternalReference; import lombok.extern.slf4j.Slf4j; +import org.brapi.v2.model.core.BrAPIListItemWithFullItem; import org.brapi.v2.model.core.request.BrAPIListNewRequest; import org.brapi.v2.model.core.response.BrAPIListDetails; +import org.brapi.v2.model.core.response.BrAPIListItemsListResponse; import org.brapi.v2.model.core.response.BrAPIListsSingleResponse; import org.brapi.v2.model.germ.BrAPIGermplasm; import org.brapi.v2.model.germ.BrAPIGermplasmSynonyms; import org.brapi.v2.model.germ.response.BrAPIGermplasmListResponse; +import org.brapi.v2.model.germ.response.BrAPIGermplasmListResponseResult; import org.breedinginsight.brapi.v2.constants.BrAPIAdditionalInfoFields; import org.breedinginsight.brapi.v2.dao.BrAPIListDAO; import org.breedinginsight.brapi.v2.model.request.query.GermplasmQuery; @@ -172,96 +175,40 @@ public BrAPIGermplasmListResponse getGermplasmByList(UUID programId, GermplasmQu if(Objects.nonNull(listResponse) && Objects.nonNull(listResponse.getResult())) { // get the list ID stored in the list external references - UUID listId = getGermplasmListId(listResponse.getResult()); + // TODO: See if this can be removed/replaced with actual BrAPI list db id + UUID biGermplasmListId = getGermplasmListId(listResponse.getResult()); - // get list BrAPI germplasm variables. - // The order of these germplasm names are the exact order/entry number in the list that was imported. - List germplasmNamesFromBrapiList = listResponse.getResult().getData(); + // Required for the actual data search in BrAPI + String brapiListDbId = listResponse.getResult().getListDbId(); - BrAPIGermplasmListResponse germplasmSearchResponse = germplasmDAO.searchGermplasmByRawName(germplasmNamesFromBrapiList, programId, germplasmQuery); + BrAPIListItemsListResponse listSearchResponse = germplasmDAO.searchGermplasmByList(brapiListDbId, programId, germplasmQuery); - List germplasm = germplasmSearchResponse.getResult().getData(); - Map germplasmByGid = new HashMap<>(); + List> listItemsWithFullItem = listSearchResponse.getResult().getData(); - if (germplasm == null || germplasm.isEmpty()) { - return germplasmSearchResponse; - } - - for (BrAPIGermplasm g : germplasm) { - // set the list ID in the germplasm additional info - g.putAdditionalInfoItem(BrAPIAdditionalInfoFields.GERMPLASM_LIST_ID, listId); - // Add to map. - germplasmByGid.put(g.getAccessionNumber(), g); - } - - // Extract gids from list names - List gids = germplasmNamesFromBrapiList.stream().map(Utilities::extractGid).collect(Collectors.toList()); - - // Build list from BrAPI list that preserves ordering and duplicates and assigns sequential entry numbers. - List orderByEntryNumber = new ArrayList<>(); - - Program program = programService.getById(programId).orElseThrow(() -> new DoesNotExistException("Could not find program: " + programId)); - - for (String gid : gids) { - BrAPIGermplasm germ = germplasmByGid.get(gid); + listItemsWithFullItem.forEach(li -> { + li.getItem().putAdditionalInfoItem(BrAPIAdditionalInfoFields.GERMPLASM_LIST_ID, biGermplasmListId); + // listItem.position start of index is 0 in BrAPI, but in DeltaBreed, it starts at 1. increment result data to account for this. + // The actual sort/filter performs the search on a formula column created on the prod server, so no need to worry about that in the request above. + li.getItem().putAdditionalInfoItem(BrAPIAdditionalInfoFields.GERMPLASM_IMPORT_ENTRY_NUMBER, li.getListItem().getPosition() + 1); + }); - if (germ == null) { - continue; - } - - // We can't simply use germ.getGermplasmName here because the BrAPIGermplasmDAO code strips the key and gid via BrAPIGermplasmDAO.processGermplasmForDisplay() - // Rebuild the unique name so we can get the entry number via the order on the germplasmNamesFromBrapiList list which was derived from the list response. - String fullGermplasmName = Utilities.appendProgramKey(germ.getDefaultDisplayName(), program.getKey(), germ.getAccessionNumber()); + List germplasm = listItemsWithFullItem.stream().map(BrAPIListItemWithFullItem::getItem).collect(Collectors.toList()); - BrAPIGermplasm listEntry = cloneBrAPIGermplasm(germ); + BrAPIGermplasmListResponse germplasmSearchResponse = new BrAPIGermplasmListResponse(); + BrAPIGermplasmListResponseResult germplasmListResponseResult = new BrAPIGermplasmListResponseResult(); - int entryNumber = germplasmNamesFromBrapiList.indexOf(fullGermplasmName) + 1; - - // Set entry number. - listEntry.putAdditionalInfoItem(BrAPIAdditionalInfoFields.GERMPLASM_IMPORT_ENTRY_NUMBER, entryNumber); - germ.putAdditionalInfoItem(BrAPIAdditionalInfoFields.GERMPLASM_IMPORT_ENTRY_NUMBER, entryNumber); - orderByEntryNumber.add(listEntry); - } + germplasmSearchResponse.setMetadata(listSearchResponse.getMetadata()); + germplasmListResponseResult.setData(germplasm); + germplasmSearchResponse.setResult(germplasmListResponseResult); - if (germplasmQuery.getSortField() == null || germplasmQuery.getSortField().equals("importEntryNumber")) { - // This should order by entry number when germplasm list details page displays - germplasmSearchResponse.getResult().setData(orderByEntryNumber); - } else { - // In all other cases it will order by the other selected data - germplasmSearchResponse.getResult().setData(germplasm); + if (germplasm.isEmpty()) { + return germplasmSearchResponse; } return germplasmSearchResponse; } else throw new ApiException(); } -// private BrAPIGermplasmListResponse orderByListImportEntryNumber() { -// for (String gid : gids) { -// BrAPIGermplasm germ = germplasmByGid.get(gid); -// -// if (germ == null) { -// continue; -// } -// -// // We can't simply use germ.getGermplasmName here because the BrAPIGermplasmDAO code strips the key and gid via BrAPIGermplasmDAO.processGermplasmForDisplay() -// // Rebuild the unique name so we can get the entry number via the order on the germplasmNamesFromBrapiList list which was derived from the list response. -// String fullGermplasmName = Utilities.appendProgramKey(germ.getDefaultDisplayName(), program.getKey(), germ.getAccessionNumber()); -// -// BrAPIGermplasm listEntry = cloneBrAPIGermplasm(germ); -// -// int entryNumber = germplasmNamesFromBrapiList.indexOf(fullGermplasmName) + 1; -// -// // Set entry number. -// listEntry.putAdditionalInfoItem(BrAPIAdditionalInfoFields.GERMPLASM_IMPORT_ENTRY_NUMBER, entryNumber); -// orderByEntryNumber.add(listEntry); -// } -// } - - private BrAPIGermplasm cloneBrAPIGermplasm(BrAPIGermplasm germplasm) { - // Serialize then deserialize to deep copy. - return (BrAPIGermplasm) gson.fromJson(gson.toJson(germplasm), BrAPIGermplasm.class); - } - public DownloadFile exportGermplasm(UUID programId, FileType fileExtension) throws IllegalArgumentException, ApiException, IOException, DoesNotExistException { List columns = GermplasmFileColumns.getOrderedColumns(); diff --git a/src/test/java/org/breedinginsight/services/BrAPIGermplasmServiceUnitTest.java b/src/test/java/org/breedinginsight/services/BrAPIGermplasmServiceUnitTest.java index 7251b02e9..6795eb0bf 100644 --- a/src/test/java/org/breedinginsight/services/BrAPIGermplasmServiceUnitTest.java +++ b/src/test/java/org/breedinginsight/services/BrAPIGermplasmServiceUnitTest.java @@ -1,5 +1,6 @@ package org.breedinginsight.services; +import com.fasterxml.jackson.databind.ObjectMapper; import com.google.gson.JsonObject; import io.reactivex.functions.Function; import io.reactivex.functions.Function3; @@ -60,7 +61,7 @@ void setup() { listDAO = mock(BrAPIListDAO.class); programDAO = mock(ProgramDAO.class); brAPIDAOUtil = mock(BrAPIDAOUtil.class); - germplasmDAO = new BrAPIGermplasmDAO(programDAO, mock(ImportDAO.class), brAPIDAOUtil, new BrAPIEndpointProvider(), 65000); + germplasmDAO = new BrAPIGermplasmDAO(programDAO, mock(ImportDAO.class), brAPIDAOUtil, new BrAPIEndpointProvider(), 65000, new ObjectMapper(), listDAO); programService = mock(ProgramService.class); Field externalReferenceSource = BrAPIGermplasmDAO.class.getDeclaredField("referenceSource");