diff --git a/docker-compose.yml b/docker-compose.yml index 9ee284274..f26e4d503 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -69,7 +69,7 @@ services: aliases: - dbserver brapi-server: - image: breedinginsight/brapi-java-server:v2.0-3 + image: breedinginsight/brapi-java-server:v2.0-4 container_name: brapi-server depends_on: - bidb diff --git a/src/main/java/org/breedinginsight/brapps/importer/daos/BrAPIGermplasmDAO.java b/src/main/java/org/breedinginsight/brapps/importer/daos/BrAPIGermplasmDAO.java index e3627cb24..48c35a5b8 100644 --- a/src/main/java/org/breedinginsight/brapps/importer/daos/BrAPIGermplasmDAO.java +++ b/src/main/java/org/breedinginsight/brapps/importer/daos/BrAPIGermplasmDAO.java @@ -48,8 +48,6 @@ public BrAPIGermplasmDAO(BrAPIProvider brAPIProvider) { public List getGermplasmByName(List germplasmNames, BrAPIProgram brAPIProgram) throws ApiException { BrAPIGermplasmSearchRequest germplasmSearch = new BrAPIGermplasmSearchRequest(); germplasmSearch.germplasmNames(germplasmNames); - // Germplasm doesn't have program attached. Do species as next best thing - germplasmSearch.setCommonCropNames(List.of(brAPIProgram.getCommonCropName())); germplasmSearch.setPageSize(BrAPIDAOUtil.RESULTS_PER_QUERY); GermplasmApi api = brAPIProvider.getGermplasmApi(BrAPIClientType.CORE); return BrAPIDAOUtil.search( diff --git a/src/main/java/org/breedinginsight/brapps/importer/model/base/Germplasm.java b/src/main/java/org/breedinginsight/brapps/importer/model/base/Germplasm.java index dc9053c86..0252c3f58 100644 --- a/src/main/java/org/breedinginsight/brapps/importer/model/base/Germplasm.java +++ b/src/main/java/org/breedinginsight/brapps/importer/model/base/Germplasm.java @@ -21,12 +21,11 @@ import lombok.NoArgsConstructor; import lombok.Setter; import org.brapi.v2.model.BrAPIExternalReference; +import org.brapi.v2.model.core.BrAPIProgram; import org.brapi.v2.model.germ.BrAPIGermplasm; -import org.breedinginsight.brapps.importer.model.config.ImportFieldMetadata; -import org.breedinginsight.brapps.importer.model.config.ImportMappingRequired; -import org.breedinginsight.brapps.importer.model.config.ImportFieldTypeEnum; -import org.breedinginsight.brapps.importer.model.config.ImportFieldType; +import org.breedinginsight.brapps.importer.model.config.*; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -38,11 +37,27 @@ description = "A germplasm object corresponds to a non-physical entity and is used to track a unique genetic composition. This is commonly used for populations.") public class Germplasm implements BrAPIObject { + public static final String GERMPLASM_NAME_TARGET = "germplasmName"; + @ImportFieldType(type= ImportFieldTypeEnum.TEXT) @ImportMappingRequired @ImportFieldMetadata(id="germplasmName", name="Germplasm Name", description = "Name of germplasm") private String germplasmName; + @ImportFieldType(type= ImportFieldTypeEnum.RELATIONSHIP) + @ImportFieldRelations(relations = { + @ImportFieldRelation(type = ImportRelationType.DB_LOOKUP, importFields = {GERMPLASM_NAME_TARGET}), + }) + @ImportFieldMetadata(id="femaleParent", name="Female Parent", description = "The female parent of the germplasm.") + private MappedImportRelation femaleParent; + + @ImportFieldType(type= ImportFieldTypeEnum.RELATIONSHIP) + @ImportFieldRelations(relations = { + @ImportFieldRelation(type = ImportRelationType.DB_LOOKUP, importFields = {GERMPLASM_NAME_TARGET}), + }) + @ImportFieldMetadata(id="maleParent", name="Male Parent", description = "The male parent of the germplasm. Can be left blank for self crosses.") + private MappedImportRelation maleParent; + @ImportFieldType(type= ImportFieldTypeEnum.TEXT) @ImportFieldMetadata(id="germplasmPUI", name="Germplasm Permanent Unique Identifier", description = "The Permanent Unique Identifier which represents a germplasm from the source or donor.") private String germplasmPUI; @@ -78,7 +93,6 @@ public BrAPIGermplasm constructBrAPIGermplasm() { germplasm.setGermplasmName(getGermplasmName()); germplasm.setGermplasmPUI(getGermplasmPUI()); germplasm.setAccessionNumber(getAccessionNumber()); - germplasm.setAccessionNumber(getAccessionNumber()); germplasm.setCollection(getCollection()); //TODO: Need to check that the acquisition date it in date format //brAPIGermplasm.setAcquisitionDate(pedigreeImport.getGermplasm().getAcquisitionDate()); @@ -99,10 +113,15 @@ public BrAPIGermplasm constructBrAPIGermplasm() { return germplasm; } - public BrAPIGermplasm constructBrAPIGermplasm(String species) { + public BrAPIGermplasm constructBrAPIGermplasm(BrAPIProgram brAPIProgram) { BrAPIGermplasm germplasm = constructBrAPIGermplasm(); - germplasm.setSpecies(species); - germplasm.setCommonCropName(species); + germplasm.setCommonCropName(brAPIProgram.getCommonCropName()); + + // Set programId in additionalInfo + Map additionalInfo = germplasm.getAdditionalInfo() != null ? germplasm.getAdditionalInfo() : new HashMap<>(); + additionalInfo.put("programId", brAPIProgram.getProgramDbId()); + germplasm.setAdditionalInfo(additionalInfo); + return germplasm; } diff --git a/src/main/java/org/breedinginsight/brapps/importer/model/imports/PedigreeImportService.java b/src/main/java/org/breedinginsight/brapps/importer/model/imports/PedigreeImportService.java deleted file mode 100644 index 7af73fabb..000000000 --- a/src/main/java/org/breedinginsight/brapps/importer/model/imports/PedigreeImportService.java +++ /dev/null @@ -1,318 +0,0 @@ -/* - * 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.brapps.importer.model.imports; - -import io.micronaut.http.HttpStatus; -import io.micronaut.http.exceptions.HttpStatusException; -import io.micronaut.http.server.exceptions.InternalServerException; -import org.brapi.client.v2.model.exceptions.ApiException; -import org.brapi.v2.model.core.BrAPIProgram; -import org.brapi.v2.model.germ.*; -import org.breedinginsight.brapps.importer.daos.BrAPICrossDAO; -import org.breedinginsight.brapps.importer.daos.BrAPIGermplasmDAO; -import org.breedinginsight.brapps.importer.daos.BrAPIProgramDAO; -import org.breedinginsight.brapps.importer.model.base.*; -import org.breedinginsight.brapps.importer.model.config.ImportRelationType; -import org.breedinginsight.brapps.importer.model.response.ImportPreviewResponse; -import org.breedinginsight.brapps.importer.model.response.ImportPreviewStatistics; -import org.breedinginsight.brapps.importer.model.response.PendingImportObject; -import org.breedinginsight.brapps.importer.model.response.ImportObjectState; -import org.breedinginsight.brapps.importer.services.FileMappingUtil; -import org.breedinginsight.model.Program; -import org.breedinginsight.services.exceptions.UnprocessableEntityException; -import tech.tablesaw.api.Table; - -import javax.inject.Inject; -import javax.inject.Singleton; -import java.util.*; -import java.util.stream.Collectors; - -@Singleton -public class PedigreeImportService extends BrAPIImportService { - - private String IMPORT_TYPE_ID = "PedigreeImport"; - - private BrAPIGermplasmDAO brAPIGermplasmDAO; - private BrAPIProgramDAO brAPIProgramDAO; - private BrAPICrossDAO brAPICrossDAO; - private FileMappingUtil fileMappingUtil; - - @Inject - public PedigreeImportService(FileMappingUtil fileMappingUtil, - BrAPIProgramDAO brAPIProgramDAO, BrAPIGermplasmDAO brAPIGermplasmDAO, BrAPICrossDAO brAPICrossDAO) - { - this.fileMappingUtil = fileMappingUtil; - this.brAPIGermplasmDAO = brAPIGermplasmDAO; - this.brAPIProgramDAO = brAPIProgramDAO; - this.brAPICrossDAO = brAPICrossDAO; - } - - @Override - public PedigreeImport getImportClass() { - return new PedigreeImport(); - } - - @Override - public String getImportTypeId() { - return IMPORT_TYPE_ID; - } - - @Override - public ImportPreviewResponse process(List brAPIImports, Table data, Program program, Boolean commit) throws UnprocessableEntityException { - - //BrAPI Objects per row - List pedigreeImports = (List)(List) brAPIImports; - - // Get BrAPI Program - BrAPIProgram brAPIProgram; - try { - Optional optionalBrAPIProgram = brAPIProgramDAO.getProgram(program.getId()); - if (optionalBrAPIProgram.isEmpty()) throw new ApiException("Program was not found in the brapi service"); - brAPIProgram = optionalBrAPIProgram.get(); - } catch (ApiException e) { - // Our program should be set up already - throw new InternalServerException(e.toString(), e); - } - - // Get all of our objects specified in the data file by their unique attributes - Set germplasmNames = new HashSet<>(); - for (int i = 0; i < pedigreeImports.size(); i++) { - PedigreeImport pedigreeImport = pedigreeImports.get(i); - if (pedigreeImport.getGermplasm() != null && pedigreeImport.getGermplasm().getGermplasmName() != null){ - germplasmNames.add(pedigreeImport.getGermplasm().getGermplasmName()); - } - } - - // Add our values for the db lookup to our list - // Get the first row with a cross. Not ideal, but it works - Cross importCross = null; - Iterator iterator = pedigreeImports.iterator(); - while (importCross == null && iterator.hasNext()) { - PedigreeImport pedigreeImport = iterator.next(); - importCross = pedigreeImport.getCross(); - } - if (importCross != null) { - // Check the lookup for the female parent - if (importCross.getFemaleParent() != null && importCross.getFemaleParent().getType() == ImportRelationType.DB_LOOKUP) { - - List names = pedigreeImports.stream() - .filter(pedigreeImport -> pedigreeImport.getCross() != null && pedigreeImport.getCross().getFemaleParent() != null) - .map(pedigreeImport -> pedigreeImport.getCross().getFemaleParent().getReferenceValue()) - .collect(Collectors.toList()); - - if (importCross.getFemaleParent().getTargetColumn().equals(Cross.GERMPLASM_NAME_TARGET)) { - germplasmNames.addAll(names); - } else { - throw new HttpStatusException(HttpStatus.UNPROCESSABLE_ENTITY, - String.format("Target field, %s, not supported", importCross.getFemaleParent().getTargetColumn())); - } - } - - // Check the lookup for the male parent - if (importCross.getMaleParent() != null && importCross.getMaleParent().getType() == ImportRelationType.DB_LOOKUP) { - - List names = pedigreeImports.stream() - .filter(pedigreeImport -> pedigreeImport.getCross() != null && pedigreeImport.getCross().getMaleParent() != null) - .map(pedigreeImport -> pedigreeImport.getCross().getMaleParent().getReferenceValue()) - .collect(Collectors.toList()); - - if (importCross.getMaleParent().getTargetColumn().equals(Cross.GERMPLASM_NAME_TARGET)) { - germplasmNames.addAll(names); - } else { - throw new HttpStatusException(HttpStatus.UNPROCESSABLE_ENTITY, - String.format("Target field, %s, not supported", importCross.getFemaleParent().getTargetColumn())); - } - } - } - - // Setting up our data elements - List mappedBrAPIImport = pedigreeImports.stream() - .map(pedigreeImport -> new PedigreeImportPending()).collect(Collectors.toList()); - Map> germplasmByName = new HashMap<>(); - Map> crossByGermplasmName = new HashMap<>(); - - // Get existing objects - try { - List existingGermplasms = brAPIGermplasmDAO.getGermplasmByName(new ArrayList<>(germplasmNames), brAPIProgram); - existingGermplasms.forEach(existingGermplasm -> { - germplasmByName.put(existingGermplasm.getGermplasmName(), new PendingImportObject<>(ImportObjectState.EXISTING, existingGermplasm)); - }); - } catch (ApiException e) { - // We shouldn't get an error back from our services. If we do, nothing the user can do about it - throw new InternalServerException(e.toString(), e); - } - - // Create new objects - for (int i = 0; i < pedigreeImports.size(); i++) { - PedigreeImport pedigreeImport = pedigreeImports.get(i); - PedigreeImportPending mappedImportRow = mappedBrAPIImport.get(i); - Germplasm germplasm = pedigreeImport.getGermplasm(); - Cross cross = pedigreeImport.getCross(); - - // Germplasm - if (germplasm != null && germplasm.getGermplasmName() != null) { - if (!germplasmByName.containsKey(germplasm.getGermplasmName())) { - BrAPIGermplasm newGermplasm = germplasm.constructBrAPIGermplasm(brAPIProgram.getCommonCropName()); - germplasmByName.put(newGermplasm.getGermplasmName(), new PendingImportObject<>(ImportObjectState.NEW, newGermplasm)); - } - mappedImportRow.setGermplasm(germplasmByName.get(germplasm.getGermplasmName())); - } - - // Crosses - // TODO: Fix once search crosses is added to brapi. Right now only creates crosses for new germplasm - // Crosses produce germplasm. If no germplasm for this row, skip it - if (cross != null && mappedImportRow.getGermplasm() != null) { - PendingImportObject germplasmPreview = germplasmByName.get(germplasm.getGermplasmName()); - - BrAPICross newCross = cross.getBrAPICross(); - Boolean addCross = true; - - // Add the mother - if (cross.getFemaleParent() != null) { - BrAPICrossParent mother = new BrAPICrossParent(); - if (cross.getFemaleParent().getTargetColumn().equals(Cross.GERMPLASM_NAME_TARGET)) { - if (germplasmByName.containsKey(cross.getFemaleParent().getReferenceValue())) { - mother.setGermplasmName(cross.getFemaleParent().getReferenceValue()); - } else { - //TODO: Throw error if the user wants an error - addCross = false; - } - } - newCross.setParent1(mother); - } - - // Add father - if (cross.getMaleParent() != null) { - BrAPICrossParent father = new BrAPICrossParent(); - if (cross.getFemaleParent().getTargetColumn().equals(Cross.GERMPLASM_NAME_TARGET)) { - if (germplasmByName.containsKey(cross.getMaleParent().getReferenceValue())) { - father.setGermplasmName(cross.getMaleParent().getReferenceValue()); - } else { - //TODO: Throw error if the user wants an error - addCross = false; - } - } - newCross.setParent2(father); - } - - if (cross.getFemaleParent() == null && cross.getMaleParent() == null) { - addCross = false; - } - - // We don't add the cross if the parents weren't found - if (addCross) { - if (!crossByGermplasmName.containsKey(germplasm.getGermplasmName()) && - germplasmPreview.getState() == ImportObjectState.NEW - ) { - crossByGermplasmName.put(germplasm.getGermplasmName(), new PendingImportObject<>(ImportObjectState.NEW, newCross)); - } else if (!crossByGermplasmName.containsKey(germplasm.getGermplasmName())) { - //TODO: Need to search crosses. This is just a placeholder for now - crossByGermplasmName.put(germplasm.getGermplasmName(), new PendingImportObject<>(ImportObjectState.EXISTING, newCross)); - } - - mappedImportRow.setCross(crossByGermplasmName.get(germplasm.getGermplasmName())); - } - - } - } - - - // Get our new objects to create - List newGermplasmList = germplasmByName.values().stream() - .filter(preview -> preview != null && preview.getState() == ImportObjectState.NEW) - .map(preview -> preview.getBrAPIObject()) - .collect(Collectors.toList()); - List newCrosses = crossByGermplasmName.values().stream() - .filter(preview -> preview != null && preview.getState() == ImportObjectState.NEW) - .map(preview -> preview.getBrAPIObject()) - .collect(Collectors.toList()); - - - // Construct our response object - ImportPreviewResponse response = new ImportPreviewResponse(); - ImportPreviewStatistics germplasmStats = new ImportPreviewStatistics(); - germplasmStats.setNewObjectCount(newGermplasmList.size()); - ImportPreviewStatistics ouStats = new ImportPreviewStatistics(); - ImportPreviewStatistics crossStats = new ImportPreviewStatistics(); - crossStats.setNewObjectCount(newCrosses.size()); - response.setStatistics(Map.of( - "Germplasm", germplasmStats, - "Crosses", crossStats - )); - response.setRows((List)(List) mappedBrAPIImport); - // Preview Class - // statistics - // Germplasm - // new - // ObservationUnits - // new - // rows - // germplasm BrAPIPreview - // ou BrAPIPReview - if (!commit) { - return response; - } else { - - // POST Germplasm - List createdGermplasm = new ArrayList<>(); - if (newGermplasmList.size() > 0) { - try { - createdGermplasm = brAPIGermplasmDAO.createBrAPIGermplasm(newGermplasmList); - } catch (ApiException e) { - throw new InternalServerException(e.toString(), e); - } - } - // Update our records - createdGermplasm.forEach(germplasm -> { - PendingImportObject preview = germplasmByName.get(germplasm.getGermplasmName()); - preview.setBrAPIObject(germplasm); - }); - - // POST Crosses - // Update the germplasm and observation unit ids in the new crosses - for (int k = 0; k < mappedBrAPIImport.size(); k++) { - PedigreeImportPending mappedImportRow = mappedBrAPIImport.get(k); - if (mappedImportRow.getCross() != null){ - PendingImportObject crossPreview = mappedImportRow.getCross(); - if (crossPreview.getState() == ImportObjectState.NEW) { - BrAPICross cross = crossPreview.getBrAPIObject(); - if (cross.getParent1() != null){ - BrAPIGermplasm targetGermplasmParent = germplasmByName.get(cross.getParent1().getGermplasmName()).getBrAPIObject(); - cross.getParent1().setGermplasmDbId(targetGermplasmParent.getGermplasmDbId()); - } - - if (cross.getParent2() != null) { - BrAPIGermplasm targetGermplasmParent = germplasmByName.get(cross.getParent2().getGermplasmName()).getBrAPIObject(); - cross.getParent2().setGermplasmDbId(targetGermplasmParent.getGermplasmDbId()); - } - } - - } - } - - try { - brAPICrossDAO.createBrAPICrosses(newCrosses); - } catch (ApiException e) { - throw new InternalServerException(e.toString(), e); - } - } - - //DONE! - return response; - } -} diff --git a/src/main/java/org/breedinginsight/brapps/importer/model/imports/PedigreeImport.java b/src/main/java/org/breedinginsight/brapps/importer/model/imports/germplasm/GermplasmImport.java similarity index 77% rename from src/main/java/org/breedinginsight/brapps/importer/model/imports/PedigreeImport.java rename to src/main/java/org/breedinginsight/brapps/importer/model/imports/germplasm/GermplasmImport.java index 3dc0a1cf2..e0fe0f84e 100644 --- a/src/main/java/org/breedinginsight/brapps/importer/model/imports/PedigreeImport.java +++ b/src/main/java/org/breedinginsight/brapps/importer/model/imports/germplasm/GermplasmImport.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.breedinginsight.brapps.importer.model.imports; +package org.breedinginsight.brapps.importer.model.imports.germplasm; import lombok.Getter; import lombok.NoArgsConstructor; @@ -26,17 +26,15 @@ import org.breedinginsight.brapps.importer.model.config.ImportFieldTypeEnum; import org.breedinginsight.brapps.importer.model.config.ImportFieldType; import org.breedinginsight.brapps.importer.model.config.ImportConfigMetadata; +import org.breedinginsight.brapps.importer.model.imports.BrAPIImport; @Getter @Setter @NoArgsConstructor -@ImportConfigMetadata(id="PedigreeImport", name="Pedigree Import", - description = "This import is used to create a pedigree history by importing germplasm.") -public class PedigreeImport implements BrAPIImport { +@ImportConfigMetadata(id="GermplasmImport", name="Germplasm Import", + description = "This import is used to create germplasm and create a pedigree by specifying parental connections.") +public class GermplasmImport implements BrAPIImport { @ImportFieldType(type = ImportFieldTypeEnum.OBJECT) @ImportMappingRequired private Germplasm germplasm; - - @ImportFieldType(type = ImportFieldTypeEnum.OBJECT) - private Cross cross; } diff --git a/src/main/java/org/breedinginsight/brapps/importer/model/imports/PedigreeImportPending.java b/src/main/java/org/breedinginsight/brapps/importer/model/imports/germplasm/GermplasmImportPending.java similarity index 81% rename from src/main/java/org/breedinginsight/brapps/importer/model/imports/PedigreeImportPending.java rename to src/main/java/org/breedinginsight/brapps/importer/model/imports/germplasm/GermplasmImportPending.java index a9006e037..ca6596fb3 100644 --- a/src/main/java/org/breedinginsight/brapps/importer/model/imports/PedigreeImportPending.java +++ b/src/main/java/org/breedinginsight/brapps/importer/model/imports/germplasm/GermplasmImportPending.java @@ -15,22 +15,20 @@ * limitations under the License. */ -package org.breedinginsight.brapps.importer.model.imports; +package org.breedinginsight.brapps.importer.model.imports.germplasm; import com.fasterxml.jackson.annotation.JsonInclude; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; -import org.brapi.v2.model.germ.BrAPICross; import org.brapi.v2.model.germ.BrAPIGermplasm; +import org.breedinginsight.brapps.importer.model.imports.PendingImport; import org.breedinginsight.brapps.importer.model.response.PendingImportObject; @Getter @Setter @NoArgsConstructor -public class PedigreeImportPending implements PendingImport { +public class GermplasmImportPending implements PendingImport { @JsonInclude private PendingImportObject germplasm; - @JsonInclude - private PendingImportObject cross; } diff --git a/src/main/java/org/breedinginsight/brapps/importer/model/imports/germplasm/GermplasmImportService.java b/src/main/java/org/breedinginsight/brapps/importer/model/imports/germplasm/GermplasmImportService.java new file mode 100644 index 000000000..b3a0d62d0 --- /dev/null +++ b/src/main/java/org/breedinginsight/brapps/importer/model/imports/germplasm/GermplasmImportService.java @@ -0,0 +1,255 @@ +/* + * 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.brapps.importer.model.imports.germplasm; + +import io.micronaut.http.HttpStatus; +import io.micronaut.http.exceptions.HttpStatusException; +import io.micronaut.http.server.exceptions.InternalServerException; +import org.brapi.client.v2.model.exceptions.ApiException; +import org.brapi.v2.model.core.BrAPIProgram; +import org.brapi.v2.model.germ.*; +import org.breedinginsight.brapps.importer.daos.BrAPICrossDAO; +import org.breedinginsight.brapps.importer.daos.BrAPIGermplasmDAO; +import org.breedinginsight.brapps.importer.daos.BrAPIProgramDAO; +import org.breedinginsight.brapps.importer.model.base.*; +import org.breedinginsight.brapps.importer.model.imports.BrAPIImport; +import org.breedinginsight.brapps.importer.model.imports.BrAPIImportService; +import org.breedinginsight.brapps.importer.model.imports.PendingImport; +import org.breedinginsight.brapps.importer.model.response.ImportPreviewResponse; +import org.breedinginsight.brapps.importer.model.response.ImportPreviewStatistics; +import org.breedinginsight.brapps.importer.model.response.PendingImportObject; +import org.breedinginsight.brapps.importer.model.response.ImportObjectState; +import org.breedinginsight.brapps.importer.services.FileMappingUtil; +import org.breedinginsight.model.Program; +import org.breedinginsight.services.exceptions.UnprocessableEntityException; +import tech.tablesaw.api.Table; + +import javax.inject.Inject; +import javax.inject.Singleton; +import java.util.*; +import java.util.stream.Collectors; + +@Singleton +public class GermplasmImportService extends BrAPIImportService { + + private String IMPORT_TYPE_ID = "GermplasmImport"; + + private BrAPIGermplasmDAO brAPIGermplasmDAO; + private BrAPIProgramDAO brAPIProgramDAO; + + @Inject + public GermplasmImportService(FileMappingUtil fileMappingUtil, + BrAPIProgramDAO brAPIProgramDAO, BrAPIGermplasmDAO brAPIGermplasmDAO, BrAPICrossDAO brAPICrossDAO) + { + this.brAPIGermplasmDAO = brAPIGermplasmDAO; + this.brAPIProgramDAO = brAPIProgramDAO; + } + + @Override + public GermplasmImport getImportClass() { + return new GermplasmImport(); + } + + @Override + public String getImportTypeId() { + return IMPORT_TYPE_ID; + } + + @Override + public ImportPreviewResponse process(List brAPIImports, Table data, Program program, Boolean commit) throws UnprocessableEntityException { + + //BrAPI Objects per row + List germplasmImports = (List)(List) brAPIImports; + + // Get BrAPI Program + BrAPIProgram brAPIProgram; + try { + Optional optionalBrAPIProgram = brAPIProgramDAO.getProgram(program.getId()); + if (optionalBrAPIProgram.isEmpty()) throw new ApiException("Program was not found in the brapi service"); + brAPIProgram = optionalBrAPIProgram.get(); + } catch (ApiException e) { + // Our program should be set up already + throw new InternalServerException(e.toString(), e); + } + + // Get all of our objects specified in the data file by their unique attributes + Set germplasmNames = new HashSet<>(); + for (int i = 0; i < germplasmImports.size(); i++) { + GermplasmImport germplasmImport = germplasmImports.get(i); + if (germplasmImport.getGermplasm() != null){ + if (germplasmImport.getGermplasm().getGermplasmName() != null) { + germplasmNames.add(germplasmImport.getGermplasm().getGermplasmName()); + } + + if (germplasmImport.getGermplasm().getFemaleParent() != null) { + germplasmNames.add(germplasmImport.getGermplasm().getFemaleParent().getReferenceValue()); + } + + if (germplasmImport.getGermplasm().getMaleParent() != null) { + germplasmNames.add(germplasmImport.getGermplasm().getMaleParent().getReferenceValue()); + } + } + } + + // Setting up our data elements + List mappedBrAPIImport = new ArrayList<>(); + Map> germplasmByName = new HashMap<>(); + + // Get existing objects + List existingGermplasms; + try { + existingGermplasms = brAPIGermplasmDAO.getGermplasmByName(new ArrayList<>(germplasmNames), brAPIProgram); + existingGermplasms.forEach(existingGermplasm -> { + germplasmByName.put(existingGermplasm.getGermplasmName(), new PendingImportObject<>(ImportObjectState.EXISTING, existingGermplasm)); + }); + } catch (ApiException e) { + // We shouldn't get an error back from our services. If we do, nothing the user can do about it + throw new InternalServerException(e.toString(), e); + } + + // Create new objects + for (int i = 0; i < germplasmImports.size(); i++) { + GermplasmImport germplasmImport = germplasmImports.get(i); + GermplasmImportPending mappedImportRow = new GermplasmImportPending(); + mappedBrAPIImport.add(mappedImportRow); + Germplasm germplasm = germplasmImport.getGermplasm(); + + // Germplasm + if (germplasm != null && germplasm.getGermplasmName() != null) { + if (!germplasmByName.containsKey(germplasm.getGermplasmName())) { + BrAPIGermplasm newGermplasm = germplasm.constructBrAPIGermplasm(brAPIProgram); + + // Check the parents exist + String femaleParent = germplasm.getFemaleParent() != null ? germplasm.getFemaleParent().getReferenceValue() : null; + String maleParent = germplasm.getMaleParent() != null ? germplasm.getMaleParent().getReferenceValue() : null; + Boolean femaleParentFound = true; + String pedigreeString = null; + if (femaleParent != null){ + if (germplasmByName.containsKey(femaleParent)){ + // Good to go + pedigreeString = femaleParent; + } else { + femaleParentFound = false; + } + } + + if (maleParent != null && femaleParentFound) { + if (germplasmByName.containsKey(maleParent)){ + // Good to go + pedigreeString += "/" + maleParent; + } + } + newGermplasm.setPedigree(pedigreeString); + + germplasmByName.put(newGermplasm.getGermplasmName(), new PendingImportObject<>(ImportObjectState.NEW, newGermplasm)); + } + mappedImportRow.setGermplasm(germplasmByName.get(germplasm.getGermplasmName())); + } + } + + + // Get our new objects to create + List newGermplasmList = germplasmByName.values().stream() + .filter(preview -> preview != null && preview.getState() == ImportObjectState.NEW) + .map(preview -> preview.getBrAPIObject()) + .collect(Collectors.toList()); + + // Construct a dependency tree for POSTing order + Set created = new HashSet<>(); + created.addAll(existingGermplasms.stream().map(brAPIGermplasm -> brAPIGermplasm.getGermplasmName()).collect(Collectors.toList())); + List> postOrder = new ArrayList<>(); + Integer totalRecorded = 0; + + while (totalRecorded < newGermplasmList.size()) { + List createList = new ArrayList<>(); + for (BrAPIGermplasm germplasm: newGermplasmList ){ + if (created.contains(germplasm.getGermplasmName())) continue; + + // If it has no dependencies, add it + if (germplasm.getPedigree() == null) { + createList.add(germplasm); + continue; + } + + // If both parents have been created already, add it + List pedigreeArray = List.of(germplasm.getPedigree().split("/")); + String femaleParent = pedigreeArray.get(0); + String maleParent = pedigreeArray.size() > 1 ? pedigreeArray.get(1) : null; + if (created.contains(femaleParent)){ + if (maleParent == null){ + createList.add(germplasm); + } else if (created.contains(maleParent)) { + createList.add(germplasm); + } + } + } + + totalRecorded += createList.size(); + if (createList.size() > 0) { + created.addAll(createList.stream().map(brAPIGermplasm -> brAPIGermplasm.getGermplasmName()).collect(Collectors.toList())); + postOrder.add(createList); + } else if (totalRecorded < newGermplasmList.size()) { + // We ran into circular dependencies, throw an error + throw new HttpStatusException(HttpStatus.UNPROCESSABLE_ENTITY, "Circular dependency in the pedigree tree"); + } + } + + // Construct our response object + ImportPreviewResponse response = new ImportPreviewResponse(); + ImportPreviewStatistics germplasmStats = new ImportPreviewStatistics(); + germplasmStats.setNewObjectCount(newGermplasmList.size()); + ImportPreviewStatistics pedigreeConnectStats = new ImportPreviewStatistics(); + pedigreeConnectStats.setNewObjectCount( + newGermplasmList.stream().filter(newGermplasm -> newGermplasm != null).collect(Collectors.toList()).size()); + pedigreeConnectStats.setIgnoredObjectCount( + germplasmImports.stream().filter(germplasmImport -> + germplasmImport.getGermplasm() != null && + (germplasmImport.getGermplasm().getFemaleParent() != null || germplasmImport.getGermplasm().getMaleParent() != null) + ).collect(Collectors.toList()).size() - pedigreeConnectStats.getNewObjectCount()); + response.setStatistics(Map.of( + "Germplasm", germplasmStats, + "Pedigree Connections", pedigreeConnectStats + )); + response.setRows((List)(List) mappedBrAPIImport); + + if (!commit) { + return response; + } else { + // POST Germplasm + List createdGermplasm = new ArrayList<>(); + if (newGermplasmList.size() > 0) { + try { + for (List postGroup: postOrder){ + createdGermplasm.addAll(brAPIGermplasmDAO.createBrAPIGermplasm(postGroup)); + } + } catch (ApiException e) { + throw new InternalServerException(e.toString(), e); + } + } + + // Update our records + createdGermplasm.forEach(germplasm -> { + PendingImportObject preview = germplasmByName.get(germplasm.getGermplasmName()); + preview.setBrAPIObject(germplasm); + }); + } + + //DONE! + return response; + } +} diff --git a/src/main/java/org/breedinginsight/brapps/importer/model/response/ImportPreviewStatistics.java b/src/main/java/org/breedinginsight/brapps/importer/model/response/ImportPreviewStatistics.java index 371917623..1e19bb35b 100644 --- a/src/main/java/org/breedinginsight/brapps/importer/model/response/ImportPreviewStatistics.java +++ b/src/main/java/org/breedinginsight/brapps/importer/model/response/ImportPreviewStatistics.java @@ -24,4 +24,5 @@ @Setter public class ImportPreviewStatistics { private Integer newObjectCount; + private Integer ignoredObjectCount; } diff --git a/src/main/java/org/breedinginsight/utilities/BrAPIDAOUtil.java b/src/main/java/org/breedinginsight/utilities/BrAPIDAOUtil.java index fff3d81e2..f814ef837 100644 --- a/src/main/java/org/breedinginsight/utilities/BrAPIDAOUtil.java +++ b/src/main/java/org/breedinginsight/utilities/BrAPIDAOUtil.java @@ -96,8 +96,9 @@ public static List post(List brapiObjects, Function, ApiRespon // Make the POST calls in chunks so we don't overload the brapi server Integer currentRightBorder = 0; while (currentRightBorder < brapiObjects.size()) { - List postChunk = brapiObjects.size() > (currentRightBorder + POST_GROUP_SIZE - 1) ? - brapiObjects.subList(currentRightBorder, currentRightBorder + POST_GROUP_SIZE - 1) : brapiObjects.subList(currentRightBorder, brapiObjects.size() - 1); + List postChunk = brapiObjects.size() > (currentRightBorder + POST_GROUP_SIZE) ? + brapiObjects.subList(currentRightBorder, currentRightBorder + POST_GROUP_SIZE) : + brapiObjects.subList(currentRightBorder, brapiObjects.size()); ApiResponse response = postMethod.apply(postChunk); if (response.getBody() == null) throw new ApiException("Response is missing body"); BrAPIResponse body = (BrAPIResponse) response.getBody();