Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/** Elixir BioHackathon 2022 */
package com.elixir.biohackaton.ISAToSRA;

import com.elixir.biohackaton.ISAToSRA.biosamples.model.BioSample;
import com.elixir.biohackaton.ISAToSRA.biosamples.model.BiosampleAccessionsMap;
import com.elixir.biohackaton.ISAToSRA.biosamples.service.BioSamplesSubmitter;
import com.elixir.biohackaton.ISAToSRA.biosamples.service.MarsReceiptService;
Expand All @@ -11,7 +12,9 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.File;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.List;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

Expand Down Expand Up @@ -54,4 +57,157 @@ void convertToMars() {
System.console().printf("%s", ex);
}
}

@Test
void createBioSamplesUsesSampleCollectionProcessParametersForChildSampleAttributes()
throws Exception {
final String isaJsonFilePath = "../../test-data/biosamples-input-isa.json";
final String isaJsonFile = Files.readString(new File(isaJsonFilePath).toPath());
final ObjectMapper objectMapper = new ObjectMapper();
final IsaJson isaJson = objectMapper.readValue(isaJsonFile, IsaJson.class);
final CapturingBioSamplesSubmitter bioSamplesSubmitter = new CapturingBioSamplesSubmitter();

bioSamplesSubmitter.createBioSamples(isaJson.getInvestigation().getStudies(), "test-token");

final BioSample leafSample =
bioSamplesSubmitter.updatedSamples.stream()
.filter(sample -> "leaf 1".equals(sample.getName()))
.findFirst()
.orElseThrow();
final BioSample sourceSample = bioSamplesSubmitter.createdSampleByName("plant 1");

Assertions.assertTrue(hasAttribute(sourceSample, "genotype", "Col-0"));
Assertions.assertTrue(
hasAttribute(sourceSample, "growth condition", "16 h light / 8 h dark growth chamber"));
Assertions.assertTrue(hasAttribute(sourceSample, "dev_stage", "vegetative rosette stage"));
Assertions.assertTrue(hasAttribute(sourceSample, "isolation_source", "whole plant"));
Assertions.assertTrue(hasAttribute(leafSample, "organism part", "leaf"));
Assertions.assertTrue(
hasAttribute(
leafSample,
"sample description",
"young rosette leaf collected for DNA extraction"));
Assertions.assertTrue(
leafSample.getAttributes().stream()
.anyMatch(
attribute ->
"isolation_source".equals(attribute.getType())
&& "leaf tissue".equals(attribute.getValue())),
"Expected child BioSample to include the sample-collection process parameter.");
Assertions.assertTrue(hasAttribute(leafSample, "collection method", "sterile scalpel excision"));
Assertions.assertTrue(
hasAttribute(leafSample, "sample preservation", "flash frozen in liquid nitrogen"));
}

@Test
void createBioSamplesKeepsEachChildSampleLinkedToItsOwnSource() throws Exception {
final String isaJsonFilePath = "../../test-data/biosamples-input-isa-multi.json";
final String isaJsonFile = Files.readString(new File(isaJsonFilePath).toPath());
final ObjectMapper objectMapper = new ObjectMapper();
final IsaJson isaJson = objectMapper.readValue(isaJsonFile, IsaJson.class);
final CapturingBioSamplesSubmitter bioSamplesSubmitter = new CapturingBioSamplesSubmitter();

bioSamplesSubmitter.createBioSamples(isaJson.getInvestigation().getStudies(), "test-token");

final BioSample leafSample = bioSamplesSubmitter.updatedSampleByName("leaf 1");
final BioSample rootSample = bioSamplesSubmitter.updatedSampleByName("root 1");

Assertions.assertEquals("SAMEA_TEST_1", derivedFromTarget(leafSample));
Assertions.assertEquals("SAMEA_TEST_2", derivedFromTarget(rootSample));
Assertions.assertTrue(hasAttribute(rootSample, "collection date", "2022-01-03"));
Assertions.assertTrue(hasAttribute(rootSample, "organism part", "root"));
Assertions.assertTrue(hasAttribute(rootSample, "isolation_source", "root tissue"));
Assertions.assertTrue(hasAttribute(rootSample, "collection method", "washed root excision"));
Assertions.assertTrue(
hasAttribute(rootSample, "sample preservation", "flash frozen in liquid nitrogen"));
}

@Test
void createBioSamplesSupportsMultipleChildSamplesFromTheSameSource() throws Exception {
final String isaJsonFilePath = "../../test-data/biosamples-input-isa-multi.json";
final String isaJsonFile = Files.readString(new File(isaJsonFilePath).toPath());
final ObjectMapper objectMapper = new ObjectMapper();
final IsaJson isaJson = objectMapper.readValue(isaJsonFile, IsaJson.class);
final Study study = isaJson.getInvestigation().getStudies().get(0);

study.getMaterials().getSamples().stream()
.filter(sample -> "root 1".equals(sample.getName()))
.findFirst()
.orElseThrow()
.getDerivesFrom()
.get(0)
.setId("#source/330");
study.getProcessSequence().stream()
.filter(process -> "#process/sample_collection/431".equals(process.getId()))
.findFirst()
.orElseThrow()
.getInputs()
.get(0)
.setId("#source/330");

final CapturingBioSamplesSubmitter bioSamplesSubmitter = new CapturingBioSamplesSubmitter();
bioSamplesSubmitter.createBioSamples(isaJson.getInvestigation().getStudies(), "test-token");

final BioSample leafSample = bioSamplesSubmitter.updatedSampleByName("leaf 1");
final BioSample rootSample = bioSamplesSubmitter.updatedSampleByName("root 1");

Assertions.assertEquals("SAMEA_TEST_1", derivedFromTarget(leafSample));
Assertions.assertEquals("SAMEA_TEST_1", derivedFromTarget(rootSample));
Assertions.assertTrue(hasAttribute(rootSample, "organism part", "root"));
Assertions.assertTrue(hasAttribute(rootSample, "collection method", "washed root excision"));
}

private static String derivedFromTarget(final BioSample sample) {
return sample.getRelationships().stream()
.filter(relationship -> "derived from".equals(relationship.getType()))
.findFirst()
.orElseThrow()
.getTarget();
}

private static boolean hasAttribute(
final BioSample sample, final String attributeType, final String attributeValue) {
return sample.getAttributes().stream()
.anyMatch(
attribute ->
attributeType.equals(attribute.getType())
&& attributeValue.equals(attribute.getValue()));
}

private static class CapturingBioSamplesSubmitter extends BioSamplesSubmitter {
private final List<BioSample> createdSamples = new ArrayList<>();
private final List<BioSample> updatedSamples = new ArrayList<>();
private int accessionSequence = 1;

@Override
protected BioSample createSampleInBioSamples(final BioSample sample, final String webinToken) {
final BioSample createdSample =
BioSample.Builder.fromSample(sample)
.withAccession("SAMEA_TEST_" + accessionSequence++)
.build();
createdSamples.add(createdSample);
return createdSample;
}

@Override
protected BioSample updateSampleWithRelationshipsToBioSamples(
final BioSample sampleWithRelationship, final String webinToken) {
updatedSamples.add(sampleWithRelationship);
return sampleWithRelationship;
}

private BioSample updatedSampleByName(final String sampleName) {
return updatedSamples.stream()
.filter(sample -> sampleName.equals(sample.getName()))
.findFirst()
.orElseThrow();
}

private BioSample createdSampleByName(final String sampleName) {
return createdSamples.stream()
.filter(sample -> sampleName.equals(sample.getName()))
.findFirst()
.orElseThrow();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
import com.elixir.biohackaton.ISAToSRA.sra.service.ReceiptConversionService;
import com.elixir.biohackaton.ISAToSRA.sra.service.WebinExperimentXmlCreator;
import com.elixir.biohackaton.ISAToSRA.sra.service.WebinHttpSubmissionService;
import com.elixir.biohackaton.ISAToSRA.sra.service.WebinProjectXmlCreator;
import com.elixir.biohackaton.ISAToSRA.sra.service.WebinRunXmlCreator;
import com.elixir.biohackaton.ISAToSRA.sra.service.WebinStudyXmlCreator;
import com.fasterxml.jackson.core.type.TypeReference;
Expand Down Expand Up @@ -41,14 +40,14 @@
*
* <ol>
* <li>Parse ISA-JSON payload
* <li>Convert ISA-JSON elements to ENA XML (Study, Project, Experiment, Run)
* <li>Convert ISA-JSON elements to ENA XML (Study, Experiment, Run)
* <li>Submit XML to ENA Webin API
* <li>Convert ENA receipt to MARS receipt format
* <li>Return MARS receipt as JSON
* </ol>
*
* <p>The conversion follows a bottom-up approach for Experiments and Runs (starting from DataFiles
* and working up), while Study and Project use a top-down approach.
* and working up), while Study uses a top-down approach.
*/
@Slf4j
@RestController
Expand All @@ -57,8 +56,6 @@ public class WebinIsaToXmlSubmissionController {

@Autowired private WebinExperimentXmlCreator webinExperimentXmlCreator;

@Autowired private WebinProjectXmlCreator webinProjectXmlCreator;

@Autowired private WebinRunXmlCreator webinRunXmlCreator;

@Autowired private WebinHttpSubmissionService webinHttpSubmissionService;
Expand Down Expand Up @@ -90,7 +87,6 @@ public class WebinIsaToXmlSubmissionController {
* <li>Get BioSamples accessions for study samples
* <li>Convert Library → ENA EXPERIMENT (bottom-up: DataFile → Library)
* <li>Convert DataFile → ENA RUN (bottom-up: DataFile → Experiment reference)
* <li>Convert Investigation → ENA PROJECT (top-down)
* <li>Submit XML to ENA Webin API
* <li>Convert ENA receipt to MARS receipt format
* </ol>
Expand Down Expand Up @@ -154,27 +150,23 @@ public String performSubmissionToEna(
this.webinRunXmlCreator.createENARunSetElement(
webinElement, studies, experimentSequenceMap, randomSubmissionIdentifier);

// Step 5: Convert Investigation → ENA PROJECT (top-down approach)
this.webinProjectXmlCreator.createENAProjectSetElement(
webinElement, getInvestigation(isaJson), randomSubmissionIdentifier);

// Debug: Print generated XML to console
final OutputFormat format = OutputFormat.createPrettyPrint();
final XMLWriter writer = new XMLWriter(System.out, format);
writer.write(document);

// Step 6: Submit XML to ENA Webin API
// Step 5: Submit XML to ENA Webin API
final String receiptXml =
webinHttpSubmissionService.performWebinSubmission(
webinUserName, document.asXML(), webinPassword);

// Step 7: Convert ENA XML receipt to JSON
// Step 6: Convert ENA XML receipt to JSON
final Receipt receiptJson = receiptConversionService.readReceiptXml(receiptXml);

// Step 8: Convert ENA receipt to MARS receipt format
// Step 7: Convert ENA receipt to MARS receipt format
final MarsReceipt marsReceipt = marsReceiptService.convertReceiptToMars(receiptJson, isaJson);

// Step 9: Return MARS receipt as JSON
// Step 8: Return MARS receipt as JSON
return marsReceiptService.convertMarsReceiptToJson(marsReceipt);
}

Expand All @@ -194,27 +186,11 @@ public List<Study> getStudies(final IsaJson isaJson) {
return null;
}

/**
* Extracts Investigation object from ISA-JSON.
*
* @param isaJson the parsed ISA-JSON object
* @return Investigation object, or null if extraction fails
*/
public Investigation getInvestigation(final IsaJson isaJson) {
try {
return isaJson.getInvestigation();
} catch (final Exception e) {
log.error("Failed to parse ISA JSON and get investigation", e);
}

return null;
}

/**
* Parses BioSamples accessions from input parameter or extracts from ISA-JSON.
*
* <p>If the bioSampleAccessions JSON string is provided, it will be parsed and used. Otherwise,
* falls back to extracting from ISA-JSON Study samples first, then sources.
* falls back to extracting from ISA-JSON Study samples.
*
* <p>Expected format: JSON string with "SOURCE" as key, e.g., {@code {"SOURCE":"SAMEA130793922"}}
*
Expand Down
Loading
Loading