diff --git a/pom.xml b/pom.xml index 95d75a0d1..674e7ffea 100644 --- a/pom.xml +++ b/pom.xml @@ -89,7 +89,7 @@ 31.0.1-jre 4.9.3 4.3.1 - 2.1-SNAPSHOT + 2.2-SNAPSHOT 2.11.0 2.2.1 diff --git a/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIObservationLevelDAO.java b/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIObservationLevelDAO.java index e37e23dbe..258ffbaf6 100644 --- a/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIObservationLevelDAO.java +++ b/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIObservationLevelDAO.java @@ -20,23 +20,38 @@ import com.google.gson.Gson; import io.micronaut.http.HttpResponse; import io.micronaut.http.HttpStatus; +import io.micronaut.http.server.exceptions.InternalServerException; import lombok.extern.slf4j.Slf4j; import okhttp3.HttpUrl; import okhttp3.MediaType; import okhttp3.Request; import okhttp3.RequestBody; +import org.brapi.client.v2.ApiResponse; import org.brapi.client.v2.JSON; import org.brapi.client.v2.model.exceptions.ApiException; +import org.brapi.client.v2.modules.phenotype.ObservationLevelNamesApi; +import org.brapi.client.v2.modules.phenotype.ObservationUnitsApi; +import org.brapi.v2.model.pheno.BrAPIObservationUnitHierarchyLevel; +import org.brapi.v2.model.pheno.BrAPIObservationUnitLevelRelationship; +import org.brapi.v2.model.pheno.response.BrAPIObservationLevelListResponse; +import org.brapi.v2.model.pheno.response.BrAPIObservationLevelListResponseResult; +import org.brapi.v2.model.pheno.response.BrAPIObservationLevelSingleResponse; +import org.breedinginsight.daos.ProgramDAO; import org.breedinginsight.model.DatasetLevel; import org.breedinginsight.model.Program; +import org.breedinginsight.services.brapi.BrAPIEndpointProvider; +import org.breedinginsight.services.exceptions.DoesNotExistException; import org.breedinginsight.utilities.BrAPIDAOUtil; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; +import org.breedinginsight.utilities.Utilities; import javax.inject.Inject; import javax.inject.Singleton; +import java.util.List; +import java.util.Optional; @Slf4j @Singleton @@ -46,12 +61,19 @@ public class BrAPIObservationLevelDAO { private final BrAPIDAOUtil brAPIDAOUtil; private final Gson gson = new JSON().getGson(); + private final BrAPIEndpointProvider brAPIEndpointProvider; + private final ProgramDAO programDAO; + @Inject - public BrAPIObservationLevelDAO(BrAPIDAOUtil brAPIDAOUtil) { + public BrAPIObservationLevelDAO(BrAPIDAOUtil brAPIDAOUtil, + BrAPIEndpointProvider brAPIEndpointProvider, + ProgramDAO programDAO) { this.brAPIDAOUtil = brAPIDAOUtil; + this.brAPIEndpointProvider = brAPIEndpointProvider; + this.programDAO = programDAO; } - public HttpResponse createObservationLevelName(Program program, String levelName, DatasetLevel levelOrder, String programDbId) throws ApiException { + public HttpResponse createObservationLevelName(Program program, String levelName, DatasetLevel levelOrder, String programDbId) { HttpUrl url = HttpUrl.parse(brAPIDAOUtil.getProgramBrAPIBaseUrl(program.getId())) .newBuilder() .addPathSegment("observationlevelnames") @@ -75,25 +97,90 @@ public HttpResponse createObservationLevelName(Program program, String l return brAPIDAOUtil.makeCall(request); } + public BrAPIObservationUnitHierarchyLevel createLevelName(Program program, + String programDbId, + String levelName, + DatasetLevel levelOrder) throws ApiException { + ObservationLevelNamesApi api = brAPIEndpointProvider.get(programDAO.getCoreClient(program.getId()), ObservationLevelNamesApi.class); + + ApiResponse response; + + + BrAPIObservationUnitHierarchyLevel level = new BrAPIObservationUnitHierarchyLevel(); + + level.setLevelName(levelName.toLowerCase()); + level.setLevelOrder(levelOrder.getValue()); + level.setProgramDbId(programDbId); + + try { + response = api.observationLevelNamesPost(List.of(level)); + } catch (ApiException e) { + log.warn(Utilities.generateApiExceptionLogMessage(e)); + throw new InternalServerException("Error making BrAPI call", e); + } + + return Optional.of(response) + .map(ApiResponse::getBody) + .map(BrAPIObservationLevelListResponse::getResult) + .map(BrAPIObservationLevelListResponseResult::getData) + .flatMap(data -> data.stream().findFirst()) + .orElseThrow(() -> new ApiException(String.format("BrAPI indicated level name [%s] was created but no levelNameDbId was returned upon its creation", levelName))); + } + + public List getObservationLevelNamesByProgramId(Program program, String programDbId) { + ObservationLevelNamesApi api = brAPIEndpointProvider.get(programDAO.getCoreClient(program.getId()), ObservationLevelNamesApi.class); + + ApiResponse response; + + int pageSize = 100; + + try { + response = api.observationLevelNamesGet(programDbId, + false, + 0, + pageSize); + } catch (ApiException e) { + log.warn(Utilities.generateApiExceptionLogMessage(e)); + throw new InternalServerException("Error making BrAPI call", e); + } + + if (response.getBody().getMetadata().getPagination().getTotalCount() > 100) { + throw new InternalServerException(String.format("More level names exist than requested [%s]", pageSize)); + } + + return response.getBody().getResult().getData(); + } + + public List getGlobalObservationLevelNames(Program program) { + return getObservationLevelNamesByProgramId(program, null); + } + public void deleteObservationLevelName(Program program, String levelDbId) { - HttpUrl url = HttpUrl.parse(brAPIDAOUtil.getProgramBrAPIBaseUrl(program.getId())) - .newBuilder() - .addPathSegment("observationlevelnames") - .addPathSegment(levelDbId) - .build(); - var request = new Request.Builder() - .url(url) - .delete() - .addHeader("Content-Type", "application/json") - .build(); + ObservationLevelNamesApi api = brAPIEndpointProvider.get(programDAO.getCoreClient(program.getId()), ObservationLevelNamesApi.class); + try { - HttpResponse response = brAPIDAOUtil.makeCall(request); - if (response.getStatus() != HttpStatus.OK && response.getStatus() != HttpStatus.NO_CONTENT && response.getStatus() != HttpStatus.ACCEPTED) { - log.warn("Observation level delete returned status {} for {}", response.getStatus(), levelDbId); - } - } catch (Exception e) { - log.warn("Failed to delete observation level {}", levelDbId, e); + api.observationLevelNameDbIdDelete(levelDbId); + } catch (ApiException e) { + log.warn(Utilities.generateApiExceptionLogMessage(e)); + throw new InternalServerException("Error making BrAPI call", e); } } + public BrAPIObservationUnitHierarchyLevel updateObservationLevelName(Program program, + String levelNameDbId, + BrAPIObservationUnitHierarchyLevel level) { + ObservationLevelNamesApi api = brAPIEndpointProvider.get(programDAO.getCoreClient(program.getId()), ObservationLevelNamesApi.class); + + ApiResponse response; + + try { + response = api.observationLevelNameDbIdPut(levelNameDbId, level); + } catch (ApiException e) { + log.warn(Utilities.generateApiExceptionLogMessage(e)); + throw new InternalServerException("Error making BrAPI call", e); + } + + return response.getBody().getResult(); + } + } diff --git a/src/main/java/org/breedinginsight/brapi/v2/services/BrAPIObservationLevelService.java b/src/main/java/org/breedinginsight/brapi/v2/services/BrAPIObservationLevelService.java new file mode 100644 index 000000000..8d48979cb --- /dev/null +++ b/src/main/java/org/breedinginsight/brapi/v2/services/BrAPIObservationLevelService.java @@ -0,0 +1,60 @@ +package org.breedinginsight.brapi.v2.services; + +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.tuple.ImmutablePair; +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.phenotype.ObservationUnitsApi; +import org.brapi.v2.model.pheno.BrAPIObservationUnitHierarchyLevel; +import org.brapi.v2.model.pheno.BrAPIObservationUnitLevelRelationship; +import org.brapi.v2.model.pheno.response.BrAPIObservationLevelListResponse; +import org.breedinginsight.brapi.v2.dao.BrAPIObservationLevelDAO; +import org.breedinginsight.model.DatasetLevel; +import org.breedinginsight.model.Program; +import org.breedinginsight.services.ProgramService; +import org.breedinginsight.services.exceptions.DoesNotExistException; + +import javax.inject.Inject; +import javax.inject.Singleton; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +@Slf4j +@Singleton +public class BrAPIObservationLevelService { + private final BrAPIObservationLevelDAO brAPIObservationLevelDAO; + private final ProgramService programService; + + + @Inject + public BrAPIObservationLevelService(BrAPIObservationLevelDAO brAPIObservationLevelDAO, + ProgramService programService) { + this.brAPIObservationLevelDAO = brAPIObservationLevelDAO; + this.programService = programService; + } + + /** + * @return Pair[GlobalLevelNames, ProgrammaticLevelNames] + */ + public Pair, List> getGlobalAndProgrammaticLevelNames(Program program, String brapiProgramDbId) { + List globalLevels = brAPIObservationLevelDAO.getGlobalObservationLevelNames(program); + List programmaticLevels = brAPIObservationLevelDAO.getObservationLevelNamesByProgramId(program, brapiProgramDbId); + + + return new ImmutablePair<>(globalLevels, programmaticLevels); + } + + public List getProgrammaticLevelNames(Program program, String brapiProgramDbId) { + return brAPIObservationLevelDAO.getObservationLevelNamesByProgramId(program, brapiProgramDbId); + } + + public BrAPIObservationUnitHierarchyLevel createObservationLevel(Program program, + String brapiProgramDbId, + String levelName, + DatasetLevel levelOrder) throws ApiException { + return brAPIObservationLevelDAO.createLevelName(program, brapiProgramDbId, levelName, levelOrder); + } + +} diff --git a/src/main/java/org/breedinginsight/brapi/v2/services/BrAPITrialService.java b/src/main/java/org/breedinginsight/brapi/v2/services/BrAPITrialService.java index 92a7ac2db..d59b0928c 100644 --- a/src/main/java/org/breedinginsight/brapi/v2/services/BrAPITrialService.java +++ b/src/main/java/org/breedinginsight/brapi/v2/services/BrAPITrialService.java @@ -6,8 +6,6 @@ import com.github.filosganga.geogson.model.positions.SinglePosition; import com.google.gson.JsonObject; import io.micronaut.context.annotation.Property; -import io.micronaut.http.HttpResponse; -import io.micronaut.http.HttpStatus; import io.micronaut.http.MediaType; import io.micronaut.http.server.exceptions.InternalServerException; import io.micronaut.http.server.types.files.StreamedFile; @@ -81,6 +79,7 @@ public class BrAPITrialService { private final DistributedLockService lockService; private static final String SHEET_NAME = "Data"; private final DatasetService datasetService; + private final BrAPIObservationLevelService observationLevelService; @Inject public BrAPITrialService(@Property(name = "brapi.server.reference-source") String referenceSource, @@ -96,7 +95,8 @@ public BrAPITrialService(@Property(name = "brapi.server.reference-source") Strin BrAPIGermplasmDAO germplasmDAO, FileMappingUtil fileMappingUtil, DistributedLockService lockService, - DatasetService datasetService) { + DatasetService datasetService, + BrAPIObservationLevelService observationLevelService) { this.referenceSource = referenceSource; this.trialDAO = trialDAO; @@ -112,6 +112,7 @@ public BrAPITrialService(@Property(name = "brapi.server.reference-source") Strin this.fileMappingUtil = fileMappingUtil; this.lockService = lockService; this.datasetService = datasetService; + this.observationLevelService = observationLevelService; } public List getExperiments(UUID programId) throws ApiException, DoesNotExistException { @@ -466,14 +467,13 @@ public Dataset createSubEntityDataset(Program program, UUID experimentId, SubEnt } String programBrapiDbId = program.getBrapiProgram() != null ? program.getBrapiProgram().getProgramDbId() : null; - HttpResponse levelResponse = observationLevelDAO.createObservationLevelName(program, datasetName, DatasetLevel.SUB_OBS_UNIT, programBrapiDbId); - // 409 and 200 are expected response codes, anything else error out - // 409 means level already exists so we just use the name in OUs - // 200 means level was created successfully and can use the name in OUs - if (levelResponse.getStatus() != HttpStatus.CONFLICT && levelResponse.getStatus() != HttpStatus.OK) { - throw new ApiException(levelResponse.getStatus().getCode(), "Unable to create observation level: " + levelResponse.getStatus().getReason()); - } + String subEntityLevelNameDbId = datasetService.getOrCreateLevelNameForDataset(program, + programBrapiDbId, + datasetName, + DatasetLevel.SUB_OBS_UNIT + ); + List expOUs = ouDAO.getObservationUnitsForDataset(topLevelDataset.getId().toString(), program); for (BrAPIObservationUnit expUnit : expOUs) { @@ -484,14 +484,14 @@ public Dataset createSubEntityDataset(Program program, UUID experimentId, SubEnt for (int i=1; i<=request.getRepeatedMeasures(); i++) { subObsUnits.add( createSubObservationUnit( - datasetName, Integer.toString(i), program, envSeqValue, expUnit, this.referenceSource, subEntityDatasetId, - UUID.randomUUID() + UUID.randomUUID(), + subEntityLevelNameDbId ) ); } @@ -529,14 +529,14 @@ public Dataset createSubEntityDataset(Program program, UUID experimentId, SubEnt } public BrAPIObservationUnit createSubObservationUnit( - String subEntityDatasetName, String subUnitId, Program program, String seqVal, BrAPIObservationUnit expUnit, String referenceSource, UUID datasetId, - UUID id + UUID id, + String subEntityLevelNameDbId ) { BrAPIObservationUnit observationUnit = new BrAPIObservationUnit(); @@ -612,42 +612,39 @@ public BrAPIObservationUnit createSubObservationUnit( // ObservationLevel entry for Sub-Obs Unit. BrAPIObservationUnitLevelRelationship level = new BrAPIObservationUnitLevelRelationship(); - level.setLevelName(subEntityDatasetName); + level.setLevelNameDbId(subEntityLevelNameDbId); level.setLevelCode(Utilities.appendProgramKey(subUnitId, program.getKey(), seqVal)); - level.setLevelOrder(DatasetLevel.SUB_OBS_UNIT.getValue()); position.setObservationLevel(level); // ObservationLevelRelationships. List levelRelationships = new ArrayList<>(); + // TODO: Figure out if we actually need to add the sub entity level to the level relationships BI-2823 levelRelationships.add(level); - // ObservationLevelRelationships for block. - BrAPIObservationUnitLevelRelationship expBlockLevel = expUnit.getObservationUnitPosition() - .getObservationLevelRelationships().stream() - .filter(x -> x.getLevelName().equals(BrAPIConstants.REPLICATE.getValue())).findFirst().orElse(null); - if (expBlockLevel != null) { - BrAPIObservationUnitLevelRelationship blockLevel = new BrAPIObservationUnitLevelRelationship(); - blockLevel.setLevelName(expBlockLevel.getLevelName()); - blockLevel.setLevelCode(expBlockLevel.getLevelCode()); - blockLevel.setLevelOrder(expBlockLevel.getLevelOrder()); - levelRelationships.add(blockLevel); - } // ObservationLevelRelationships for rep. BrAPIObservationUnitLevelRelationship expRepLevel = expUnit.getObservationUnitPosition() .getObservationLevelRelationships().stream() - .filter(x -> x.getLevelName().equals(BrAPIConstants.BLOCK.getValue())).findFirst().orElse(null); + .filter(x -> x.getLevelName().equals(BrAPIConstants.REPLICATE.getValue())).findFirst().orElse(null); if (expRepLevel != null) { BrAPIObservationUnitLevelRelationship repLevel = new BrAPIObservationUnitLevelRelationship(); - repLevel.setLevelName(expRepLevel.getLevelName()); + repLevel.setLevelNameDbId(expRepLevel.getLevelNameDbId()); repLevel.setLevelCode(expRepLevel.getLevelCode()); - repLevel.setLevelOrder(expRepLevel.getLevelOrder()); levelRelationships.add(repLevel); } + // ObservationLevelRelationships for block. + BrAPIObservationUnitLevelRelationship expBlockLevel = expUnit.getObservationUnitPosition() + .getObservationLevelRelationships().stream() + .filter(x -> x.getLevelName().equals(BrAPIConstants.BLOCK.getValue())).findFirst().orElse(null); + if (expBlockLevel != null) { + BrAPIObservationUnitLevelRelationship blockLevel = new BrAPIObservationUnitLevelRelationship(); + blockLevel.setLevelNameDbId(expBlockLevel.getLevelNameDbId()); + blockLevel.setLevelCode(expBlockLevel.getLevelCode()); + levelRelationships.add(blockLevel); + } // ObservationLevelRelationships for top-level Exp Unit linking. BrAPIObservationUnitLevelRelationship expUnitLevel = new BrAPIObservationUnitLevelRelationship(); - expUnitLevel.setLevelName(expUnit.getObservationUnitPosition().getObservationLevel().getLevelName().toLowerCase()); + expUnitLevel.setLevelNameDbId(expUnit.getObservationUnitPosition().getObservationLevel().getLevelNameDbId()); String expUnitUUID = Utilities.getExternalReference(expUnit.getExternalReferences(), referenceSource, ExternalReferenceSource.OBSERVATION_UNITS).orElseThrow().getReferenceId(); expUnitLevel.setLevelCode(Utilities.appendProgramKey(expUnitUUID, program.getKey(), seqVal)); - expUnitLevel.setLevelOrder(DatasetLevel.EXP_UNIT.getValue()); levelRelationships.add(expUnitLevel); position.setObservationLevelRelationships(levelRelationships); diff --git a/src/main/java/org/breedinginsight/brapps/importer/services/processors/experiment/service/DatasetService.java b/src/main/java/org/breedinginsight/brapps/importer/services/processors/experiment/service/DatasetService.java index fbc5186db..18157fd11 100644 --- a/src/main/java/org/breedinginsight/brapps/importer/services/processors/experiment/service/DatasetService.java +++ b/src/main/java/org/breedinginsight/brapps/importer/services/processors/experiment/service/DatasetService.java @@ -18,6 +18,7 @@ package org.breedinginsight.brapps.importer.services.processors.experiment.service; import io.micronaut.context.annotation.Property; +import org.apache.commons.lang3.StringUtils; import org.brapi.client.v2.model.exceptions.ApiException; import org.brapi.v2.model.BrAPIExternalReference; import org.brapi.v2.model.core.BrAPIListSummary; @@ -25,11 +26,15 @@ import org.brapi.v2.model.core.BrAPITrial; import org.brapi.v2.model.core.request.BrAPIListNewRequest; import org.brapi.v2.model.core.response.BrAPIListDetails; +import org.brapi.v2.model.pheno.BrAPIObservationUnitHierarchyLevel; import org.breedinginsight.brapi.v2.constants.BrAPIAdditionalInfoFields; import org.breedinginsight.brapi.v2.dao.BrAPIListDAO; +import org.breedinginsight.brapi.v2.dao.BrAPIObservationLevelDAO; +import org.breedinginsight.brapi.v2.services.BrAPIObservationLevelService; import org.breedinginsight.brapps.importer.model.response.ImportObjectState; import org.breedinginsight.brapps.importer.model.response.PendingImportObject; import org.breedinginsight.brapps.importer.services.ExternalReferenceSource; +import org.breedinginsight.model.DatasetLevel; import org.breedinginsight.model.DatasetMetadata; import org.breedinginsight.model.Program; import org.breedinginsight.utilities.Utilities; @@ -37,16 +42,20 @@ import javax.inject.Inject; import javax.inject.Singleton; import java.util.*; +import java.util.stream.Collectors; @Singleton public class DatasetService { private final BrAPIListDAO brAPIListDAO; @Property(name = "brapi.server.reference-source") private String BRAPI_REFERENCE_SOURCE; + private final BrAPIObservationLevelService observationLevelService; @Inject - public DatasetService(BrAPIListDAO brapiListDAO) { + public DatasetService(BrAPIListDAO brapiListDAO, + BrAPIObservationLevelService brAPIObservationLevelService) { this.brAPIListDAO = brapiListDAO; + this.observationLevelService = brAPIObservationLevelService; } /** * Module: Dataset Utility @@ -162,4 +171,49 @@ public BrAPIListDetails constructDatasetDetails( dataSetDetails.setExternalReferences(refs); return dataSetDetails; } + + /** + * @return brapiLevelNameDbId of found or created record + */ + public String getOrCreateLevelNameForDataset(Program program, + String brapiProgramDbId, + String levelName, + DatasetLevel levelOrder) throws ApiException { + + String existingLevelNameDbId = findLevelNameByNameAndOrder(program, brapiProgramDbId, levelName, levelOrder); + + if (StringUtils.isNotBlank(existingLevelNameDbId)) { + return existingLevelNameDbId; + } + + // Level name does not exist and needs to be created. + BrAPIObservationUnitHierarchyLevel createdLevelName = observationLevelService.createObservationLevel(program, brapiProgramDbId, levelName, levelOrder); + + return createdLevelName.getLevelNameDbId(); + } + + /** + * This method retrieves the programmatic level names and then matches the level names on the submitted + * level name and order. + * + * @return levelNameDbId of the matched level name + */ + private String findLevelNameByNameAndOrder(Program program, + String brapiProgramDbId, + String levelName, + DatasetLevel levelOrder) { + var programmaticLevelNames = observationLevelService.getProgrammaticLevelNames(program, brapiProgramDbId); + + List levelNameStreamResult + = programmaticLevelNames.stream() + .filter(ouln -> ouln.getLevelName().equals(levelName.toLowerCase()) && ouln.getLevelOrder() == levelOrder.getValue()) + .limit(1) + .collect(Collectors.toList()); + + if (levelNameStreamResult.isEmpty()) { + return null; + } + + return levelNameStreamResult.get(0).getLevelNameDbId(); + } }