diff --git a/model/src/main/java/org/mskcc/smile/model/SmileSample.java b/model/src/main/java/org/mskcc/smile/model/SmileSample.java index a22c5f70..ced5e37d 100644 --- a/model/src/main/java/org/mskcc/smile/model/SmileSample.java +++ b/model/src/main/java/org/mskcc/smile/model/SmileSample.java @@ -8,6 +8,7 @@ import java.util.List; import java.util.UUID; import org.apache.commons.lang.builder.ToStringBuilder; +import org.mskcc.smile.model.tempo.Tempo; import org.neo4j.ogm.annotation.GeneratedValue; import org.neo4j.ogm.annotation.Id; import org.neo4j.ogm.annotation.NodeEntity; @@ -28,6 +29,8 @@ public class SmileSample implements Serializable { private SmilePatient patient; @Relationship(type = "HAS_METADATA", direction = Relationship.OUTGOING) private List sampleMetadataList; + @Relationship(type = "HAS_TEMPO", direction = Relationship.OUTGOING) + private Tempo tempo; private String sampleClass; private String sampleCategory; private String datasource; @@ -215,6 +218,14 @@ public void setRevisable(Boolean revisable) { this.revisable = revisable; } + public Tempo getTempo() { + return tempo; + } + + public void setTempo(Tempo tempo) { + this.tempo = tempo; + } + @Override public String toString() { return ToStringBuilder.reflectionToString(this); diff --git a/model/src/main/java/org/mskcc/smile/model/tempo/BamComplete.java b/model/src/main/java/org/mskcc/smile/model/tempo/BamComplete.java new file mode 100644 index 00000000..d174c461 --- /dev/null +++ b/model/src/main/java/org/mskcc/smile/model/tempo/BamComplete.java @@ -0,0 +1,55 @@ +package org.mskcc.smile.model.tempo; + +import java.io.Serializable; +import org.apache.commons.lang.builder.ToStringBuilder; +import org.neo4j.ogm.annotation.GeneratedValue; +import org.neo4j.ogm.annotation.Id; +import org.neo4j.ogm.annotation.NodeEntity; + +/** + * + * @author ochoaa + */ +@NodeEntity +public class BamComplete implements Serializable { + @Id @GeneratedValue + private Long id; + private String date; + private String status; + + public BamComplete() {} + + public BamComplete(String date, String status) { + this.date = date; + this.status = status; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getDate() { + return date; + } + + public void setDate(String date) { + this.date = date; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + @Override + public String toString() { + return ToStringBuilder.reflectionToString(this); + } +} diff --git a/model/src/main/java/org/mskcc/smile/model/tempo/Cohort.java b/model/src/main/java/org/mskcc/smile/model/tempo/Cohort.java new file mode 100644 index 00000000..f9e567e2 --- /dev/null +++ b/model/src/main/java/org/mskcc/smile/model/tempo/Cohort.java @@ -0,0 +1,123 @@ +package org.mskcc.smile.model.tempo; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import org.apache.commons.lang.builder.ToStringBuilder; +import org.mskcc.smile.model.SmileSample; +import org.mskcc.smile.model.tempo.json.CohortCompleteJson; +import org.neo4j.ogm.annotation.GeneratedValue; +import org.neo4j.ogm.annotation.Id; +import org.neo4j.ogm.annotation.NodeEntity; +import org.neo4j.ogm.annotation.Relationship; + +/** + * + * @author ochoaa + */ +@NodeEntity +public class Cohort implements Serializable { + @Id @GeneratedValue + private Long id; + private String cohortId; + @Relationship(type = "HAS_COHORT_COMPLETE", direction = Relationship.OUTGOING) + private List cohortCompleteList; + @Relationship(type = "HAS_COHORT_SAMPLE", direction = Relationship.OUTGOING) + private List cohortSamples; + + public Cohort() {} + + public Cohort(CohortCompleteJson ccJson) { + this.cohortId = ccJson.getCohortId(); + addCohortComplete(new CohortComplete(ccJson)); + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getCohortId() { + return cohortId; + } + + public void setCohortId(String cohortId) { + this.cohortId = cohortId; + } + + /** + * Returns list of CohortComplete object instances. + * @return + */ + public List getCohortCompleteList() { + if (cohortCompleteList == null) { + this.cohortCompleteList = new ArrayList<>(); + } + return cohortCompleteList; + } + + /** + * Adds instance of CohortComplete to cohortCompleteList. + * @param cohortComplete + */ + public final void addCohortComplete(CohortComplete cohortComplete) { + if (cohortCompleteList == null) { + this.cohortCompleteList = new ArrayList<>(); + } + cohortCompleteList.add(cohortComplete); + } + + public void setCohortCompleteList(List cohortCompleteList) { + this.cohortCompleteList = cohortCompleteList; + } + + /** + * Returns list of SmileSample instances. + * @return + */ + public List getCohortSamples() { + if (cohortSamples == null) { + this.cohortSamples = new ArrayList<>(); + } + return cohortSamples; + } + + /** + * Adds instance of SmileSample to cohortSamples list. + * @param sample + */ + public final void addCohortSample(SmileSample sample) { + if (cohortSamples == null) { + this.cohortSamples = new ArrayList<>(); + } + cohortSamples.add(sample); + } + + public void setCohortSamples(List cohortSamples) { + this.cohortSamples = cohortSamples; + } + + /** + * Returns latest cohort complete data. + * @return + */ + public CohortComplete getLatestCohortComplete() { + if (cohortCompleteList != null && !cohortCompleteList.isEmpty()) { + if (cohortCompleteList.size() == 1) { + return cohortCompleteList.get(0); + } + Collections.sort(cohortCompleteList); + return cohortCompleteList.get(cohortCompleteList.size() - 1); + } + return null; + } + + @Override + public String toString() { + return ToStringBuilder.reflectionToString(this); + } +} diff --git a/model/src/main/java/org/mskcc/smile/model/tempo/CohortComplete.java b/model/src/main/java/org/mskcc/smile/model/tempo/CohortComplete.java new file mode 100644 index 00000000..482aae84 --- /dev/null +++ b/model/src/main/java/org/mskcc/smile/model/tempo/CohortComplete.java @@ -0,0 +1,137 @@ +package org.mskcc.smile.model.tempo; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import org.apache.commons.lang.builder.ToStringBuilder; +import org.mskcc.smile.model.tempo.json.CohortCompleteJson; +import org.neo4j.ogm.annotation.GeneratedValue; +import org.neo4j.ogm.annotation.Id; +import org.neo4j.ogm.annotation.NodeEntity; + +/** + * + * @author ochoaa + */ +@NodeEntity +public class CohortComplete implements Serializable, Comparable { + @Id @GeneratedValue + @JsonIgnore + private Long id; + private String date; + private String status; + private String type; + private List endUsers; + private List pmUsers; + private String projectTitle; + private String projectSubtitle; + + public CohortComplete() {} + + /** + * Basic constructor from CohortCompleteJson. + * @param ccJson + */ + public CohortComplete(CohortCompleteJson ccJson) { + this.date = ccJson.getDate(); + this.status = ccJson.getStatus(); + this.type = ccJson.getType(); + this.endUsers = ccJson.getEndUsers(); + this.pmUsers = ccJson.getPmUsers(); + this.projectTitle = ccJson.getProjectTitle(); + this.projectSubtitle = ccJson.getProjectSubtitle(); + } + + public String getDate() { + return date; + } + + public void setDate(String date) { + this.date = date; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + /** + * Returns list of end users. + * @return + */ + public List getEndUsers() { + if (endUsers == null) { + this.endUsers = new ArrayList<>(); + } + Collections.sort(endUsers); + return endUsers; + } + + public void setEndUsers(List endUsers) { + this.endUsers = endUsers; + } + + /** + * Returns list of PM users. + * @return + */ + public List getPmUsers() { + if (pmUsers == null) { + this.pmUsers = new ArrayList<>(); + } + Collections.sort(pmUsers); + return pmUsers; + } + + public void setPmUsers(List pmUsers) { + this.pmUsers = pmUsers; + } + + public String getProjectTitle() { + return projectTitle; + } + + public void setProjectTitle(String projectTitle) { + this.projectTitle = projectTitle; + } + + public String getProjectSubtitle() { + return projectSubtitle; + } + + public void setProjectSubtitle(String projectSubtitle) { + this.projectSubtitle = projectSubtitle; + } + + /** + * Override to enable Collections.sorting + * @param cohortComplete + * @return + */ + @Override + public int compareTo(CohortComplete cohortComplete) { + if (date == null || cohortComplete.getDate() == null) { + return 0; + } + return date.compareTo(cohortComplete.getDate()); + } + + @Override + public String toString() { + return ToStringBuilder.reflectionToString(this); + } +} diff --git a/model/src/main/java/org/mskcc/smile/model/tempo/MafComplete.java b/model/src/main/java/org/mskcc/smile/model/tempo/MafComplete.java new file mode 100644 index 00000000..ead5211a --- /dev/null +++ b/model/src/main/java/org/mskcc/smile/model/tempo/MafComplete.java @@ -0,0 +1,71 @@ +package org.mskcc.smile.model.tempo; + +import java.io.Serializable; +import org.apache.commons.lang.builder.ToStringBuilder; +import org.neo4j.ogm.annotation.GeneratedValue; +import org.neo4j.ogm.annotation.Id; +import org.neo4j.ogm.annotation.NodeEntity; + +/** + * Represents a MAF Complete entity. + * @author qu8n + */ +@NodeEntity +public class MafComplete implements Serializable { + @Id @GeneratedValue + private Long id; + private String date; + private String normalPrimaryId; + private String status; + + public MafComplete() {} + + /** + * Constructor for MafComplete. + * @param date + * @param normalPrimaryId + * @param status + */ + public MafComplete(String date, String normalPrimaryId, String status) { + this.date = date; + this.normalPrimaryId = normalPrimaryId; + this.status = status; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getDate() { + return date; + } + + public void setDate(String date) { + this.date = date; + } + + public String getNormalPrimaryId() { + return normalPrimaryId; + } + + public void setNormalPrimaryId(String normalPrimaryId) { + this.normalPrimaryId = normalPrimaryId; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + @Override + public String toString() { + return ToStringBuilder.reflectionToString(this); + } +} diff --git a/model/src/main/java/org/mskcc/smile/model/tempo/QcComplete.java b/model/src/main/java/org/mskcc/smile/model/tempo/QcComplete.java new file mode 100644 index 00000000..f93118cf --- /dev/null +++ b/model/src/main/java/org/mskcc/smile/model/tempo/QcComplete.java @@ -0,0 +1,82 @@ +package org.mskcc.smile.model.tempo; + +import java.io.Serializable; +import org.apache.commons.lang.builder.ToStringBuilder; +import org.neo4j.ogm.annotation.GeneratedValue; +import org.neo4j.ogm.annotation.Id; +import org.neo4j.ogm.annotation.NodeEntity; + +/** + * + * @author ochoaa + */ +@NodeEntity +public class QcComplete implements Serializable { + @Id @GeneratedValue + private Long id; + private String date; + private String result; + private String reason; + private String status; + + public QcComplete() {} + + /** + * Basic QcComplete constructor. + * @param date + * @param result + * @param reason + * @param status + */ + public QcComplete(String date, String result, String reason, String status) { + this.date = date; + this.result = result; + this.reason = reason; + this.status = status; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getDate() { + return date; + } + + public void setDate(String date) { + this.date = date; + } + + public String getResult() { + return result; + } + + public void setResult(String result) { + this.result = result; + } + + public String getReason() { + return reason; + } + + public void setReason(String reason) { + this.reason = reason; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + @Override + public String toString() { + return ToStringBuilder.reflectionToString(this); + } +} diff --git a/model/src/main/java/org/mskcc/smile/model/tempo/Tempo.java b/model/src/main/java/org/mskcc/smile/model/tempo/Tempo.java new file mode 100644 index 00000000..cab6c44e --- /dev/null +++ b/model/src/main/java/org/mskcc/smile/model/tempo/Tempo.java @@ -0,0 +1,196 @@ +package org.mskcc.smile.model.tempo; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import org.apache.commons.lang.builder.ToStringBuilder; +import org.mskcc.smile.model.SmileSample; +import org.neo4j.ogm.annotation.GeneratedValue; +import org.neo4j.ogm.annotation.Id; +import org.neo4j.ogm.annotation.NodeEntity; +import org.neo4j.ogm.annotation.Relationship; + +/** + * + * @author ochoaa + */ +@NodeEntity +public class Tempo implements Serializable { + @Id @GeneratedValue + private Long id; + @Relationship(type = "HAS_EVENT", direction = Relationship.OUTGOING) + private List bamCompleteEvents; + @Relationship(type = "HAS_EVENT", direction = Relationship.OUTGOING) + private List qcCompleteEvents; + @Relationship(type = "HAS_EVENT", direction = Relationship.OUTGOING) + private List mafCompleteEvents; + @Relationship(type = "HAS_TEMPO", direction = Relationship.INCOMING) + private SmileSample sample; + + public Tempo() {} + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + /** + * Returns list of bam complete events. + * @return + */ + public List getBamCompleteEvents() { + if (bamCompleteEvents == null) { + bamCompleteEvents = new ArrayList<>(); + } + return bamCompleteEvents; + } + + public void setBamCompleteEvents(List bamCompleteEvents) { + this.bamCompleteEvents = bamCompleteEvents; + } + + /** + * Adds a bam complete event to list. + * @param bamComplete + */ + public void addBamCompleteEvent(BamComplete bamComplete) { + if (bamCompleteEvents == null) { + bamCompleteEvents = new ArrayList<>(); + } + bamCompleteEvents.add(bamComplete); + } + + /** + * Checks for bam complete event in list of existing bam events. + * @param bamComplete + * @return + */ + public Boolean hasBamCompleteEvent(BamComplete bamComplete) { + if (bamCompleteEvents == null) { + bamCompleteEvents = new ArrayList<>(); + } + if (bamCompleteEvents.isEmpty()) { + return Boolean.FALSE; + } + for (BamComplete event : bamCompleteEvents) { + if (event.getDate().equalsIgnoreCase(bamComplete.getDate()) + && event.getStatus().equalsIgnoreCase(bamComplete.getStatus())) { + return Boolean.TRUE; + } + } + return Boolean.FALSE; + } + + /** + * Returns list of qc complete events. + * @return + */ + public List getQcCompleteEvents() { + if (qcCompleteEvents == null) { + qcCompleteEvents = new ArrayList<>(); + } + return qcCompleteEvents; + } + + public void setQcCompleteEvents(List qcCompleteEvents) { + this.qcCompleteEvents = qcCompleteEvents; + } + + /** + * Adds a qc complete event to list. + * @param qcComplete + */ + public void addQcCompleteEvent(QcComplete qcComplete) { + if (qcCompleteEvents == null) { + qcCompleteEvents = new ArrayList<>(); + } + qcCompleteEvents.add(qcComplete); + } + + /** + * Checks for qc complete event in existing event list. + * @param qcComplete + * @return + */ + public Boolean hasQcCompleteEvent(QcComplete qcComplete) { + if (qcCompleteEvents == null) { + qcCompleteEvents = new ArrayList<>(); + } + if (qcCompleteEvents.isEmpty()) { + return Boolean.FALSE; + } + for (QcComplete event : qcCompleteEvents) { + if (event.getDate().equalsIgnoreCase(qcComplete.getDate()) + && event.getStatus().equalsIgnoreCase(qcComplete.getStatus()) + && event.getReason().equalsIgnoreCase(qcComplete.getReason()) + && event.getResult().equalsIgnoreCase(qcComplete.getResult())) { + return Boolean.TRUE; + } + } + return Boolean.FALSE; + } + + /** + * Returns list of maf complete events. + * @return + */ + public List getMafCompleteEvents() { + if (mafCompleteEvents == null) { + mafCompleteEvents = new ArrayList<>(); + } + return mafCompleteEvents; + } + + public void setMafCompleteEvents(List mafCompleteEvents) { + this.mafCompleteEvents = mafCompleteEvents; + } + + /** + * Adds a maf complete event to list. + * @param mafComplete + */ + public void addMafCompleteEvent(MafComplete mafComplete) { + if (mafCompleteEvents == null) { + mafCompleteEvents = new ArrayList<>(); + } + mafCompleteEvents.add(mafComplete); + } + + /** + * Checks for maf complete event in list of existing maf events. + * @param mafComplete + * @return + */ + public Boolean hasMafCompleteEvent(MafComplete mafComplete) { + if (mafCompleteEvents == null) { + mafCompleteEvents = new ArrayList<>(); + } + if (mafCompleteEvents.isEmpty()) { + return Boolean.FALSE; + } + for (MafComplete event : mafCompleteEvents) { + if (event.getDate().equalsIgnoreCase(mafComplete.getDate()) + && event.getNormalPrimaryId().equalsIgnoreCase(mafComplete.getNormalPrimaryId()) + && event.getStatus().equalsIgnoreCase(mafComplete.getStatus())) { + return Boolean.TRUE; + } + } + return Boolean.FALSE; + } + + public SmileSample getSmileSample() { + return sample; + } + + public void setSmileSample(SmileSample sample) { + this.sample = sample; + } + + @Override + public String toString() { + return ToStringBuilder.reflectionToString(this); + } +} diff --git a/model/src/main/java/org/mskcc/smile/model/tempo/json/CohortCompleteJson.java b/model/src/main/java/org/mskcc/smile/model/tempo/json/CohortCompleteJson.java new file mode 100644 index 00000000..f0b82f06 --- /dev/null +++ b/model/src/main/java/org/mskcc/smile/model/tempo/json/CohortCompleteJson.java @@ -0,0 +1,129 @@ +package org.mskcc.smile.model.tempo.json; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.apache.commons.lang.builder.ToStringBuilder; + +/** + * + * @author ochoaa + */ +@JsonInclude(JsonInclude.Include.NON_NULL) +public class CohortCompleteJson implements Serializable { + @JsonProperty("cohortId") + private String cohortId; + @JsonProperty("date") + private String date; + @JsonProperty("type") + private String type; + @JsonProperty("endUsers") + private List endUsers; + @JsonProperty("pmUsers") + private List pmUsers; + @JsonProperty("projectTitle") + private String projectTitle; + @JsonProperty("projectSubtitle") + private String projectSubtitle; + @JsonProperty("samples") + private List> tumorNormalPairs; + @JsonProperty("status") + private String status; + + public CohortCompleteJson() {} + + public String getCohortId() { + return cohortId; + } + + public void setCohortId(String cohortId) { + this.cohortId = cohortId; + } + + public String getDate() { + return date; + } + + public void setDate(String date) { + this.date = date; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public List getEndUsers() { + return endUsers; + } + + public void setEndUsers(List endUsers) { + this.endUsers = endUsers; + } + + public List getPmUsers() { + return pmUsers; + } + + public void setPmUsers(List pmUsers) { + this.pmUsers = pmUsers; + } + + public String getProjectTitle() { + return projectTitle; + } + + public void setProjectTitle(String projectTitle) { + this.projectTitle = projectTitle; + } + + public String getProjectSubtitle() { + return projectSubtitle; + } + + public void setProjectSubtitle(String projectSubtitle) { + this.projectSubtitle = projectSubtitle; + } + + public List> getTumorNormalPairs() { + return tumorNormalPairs; + } + + public void setTumorNormalPairs(List> tumorNormalPairs) { + this.tumorNormalPairs = tumorNormalPairs; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + /** + * Returns a set of tumor and normal primary ids. + * @return + */ + public Set getTumorNormalPairsAsSet() { + Set samplePrimaryIds = new HashSet<>(); + tumorNormalPairs.forEach((pairs) -> { + pairs.entrySet().forEach((entry) -> { + samplePrimaryIds.add(entry.getValue()); + }); + }); + return samplePrimaryIds; + } + + @Override + public String toString() { + return ToStringBuilder.reflectionToString(this); + } +} diff --git a/persistence/src/main/java/org/mskcc/smile/persistence/neo4j/CohortCompleteRepository.java b/persistence/src/main/java/org/mskcc/smile/persistence/neo4j/CohortCompleteRepository.java new file mode 100644 index 00000000..e5f14a89 --- /dev/null +++ b/persistence/src/main/java/org/mskcc/smile/persistence/neo4j/CohortCompleteRepository.java @@ -0,0 +1,36 @@ +package org.mskcc.smile.persistence.neo4j; + +import java.util.List; +import org.mskcc.smile.model.tempo.Cohort; +import org.mskcc.smile.model.tempo.CohortComplete; +import org.springframework.data.neo4j.annotation.Query; +import org.springframework.data.neo4j.repository.Neo4jRepository; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +/** + * + * @author ochoaa + */ +@Repository +public interface CohortCompleteRepository extends Neo4jRepository { + @Query("MATCH (c: Cohort {cohortId: $cohortId}) RETURN c") + Cohort findCohortByCohortId(@Param("cohortId") String cohortId); + + @Query("MATCH (c: Cohort {cohortId: $cohortId})-[:HAS_COHORT_COMPLETE]->(cc: CohortComplete) " + + "RETURN cc") + List findCohortCompleteEventsByCohortId(@Param("cohortId") String cohortId); + + @Query("MATCH (c: Cohort {cohortId: $cohortId})-[:HAS_COHORT_COMPLETE]->(cc: CohortComplete) " + + "RETURN cc ORDER BY cc.date DESC LIMIT 1") + CohortComplete findLatestCohortCompleteEventByCohortId(@Param("cohortId") String cohortId); + + @Query("MATCH (c: Cohort)-[:HAS_COHORT_SAMPLE]->(s: Sample)-[:HAS_METADATA]->(sm: SampleMetadata " + + "{primaryId: $primaryId}) RETURN c") + List findCohortsBySamplePrimaryId(@Param("primaryId") String primaryId); + + @Query("MATCH (s: Sample)-[:HAS_METADATA]->(sm: SampleMetadata {primaryId: $primaryId}) " + + "MATCH (c: Cohort {cohortId: $cohortId}) MERGE (c)-[:HAS_COHORT_SAMPLE]->(s)") + void addCohortSampleRelationship(@Param("cohortId") String cohortId, + @Param("primaryId") String primaryId); +} diff --git a/persistence/src/main/java/org/mskcc/smile/persistence/neo4j/SmileRequestRepository.java b/persistence/src/main/java/org/mskcc/smile/persistence/neo4j/SmileRequestRepository.java index 39858511..cef94d3e 100644 --- a/persistence/src/main/java/org/mskcc/smile/persistence/neo4j/SmileRequestRepository.java +++ b/persistence/src/main/java/org/mskcc/smile/persistence/neo4j/SmileRequestRepository.java @@ -16,7 +16,7 @@ */ @Repository public interface SmileRequestRepository extends Neo4jRepository { - @Query("MATCH (r: Request {igoRequestId: $reqId}) RETURN r;") + @Query("MATCH (r: Request {igoRequestId: $reqId}) RETURN r") SmileRequest findRequestById(@Param("reqId") String reqId); @Query("MATCH (s: Sample {smileSampleId: $smileSample.smileSampleId}) " diff --git a/persistence/src/main/java/org/mskcc/smile/persistence/neo4j/SmileSampleRepository.java b/persistence/src/main/java/org/mskcc/smile/persistence/neo4j/SmileSampleRepository.java index c037af5f..37c6c033 100644 --- a/persistence/src/main/java/org/mskcc/smile/persistence/neo4j/SmileSampleRepository.java +++ b/persistence/src/main/java/org/mskcc/smile/persistence/neo4j/SmileSampleRepository.java @@ -38,12 +38,12 @@ public interface SmileSampleRepository extends Neo4jRepository findAllSampleAliases(@Param("smileSampleId") UUID smileSampleId); @Query("MATCH (s: Sample {smileSampleId: $smileSampleId})" + "MATCH (s)-[:HAS_METADATA]->(sm: SampleMetadata)" - + "RETURN sm;") + + "RETURN sm") List findAllSampleMetadataListBySampleId(@Param("smileSampleId") UUID smileSampleId); @Query("MATCH (s: Sample {smileSampleId: $smileSample.smileSampleId})" @@ -54,9 +54,9 @@ public interface SmileSampleRepository extends Neo4jRepository findMatchedNormalsByResearchSample( @Param("smileSample") SmileSample smileSample); - @Query("Match (r: Request {igoRequestId: $reqId})-[:HAS_SAMPLE]->" + @Query("MATCH (r: Request {igoRequestId: $reqId})-[:HAS_SAMPLE]->" + "(s: Sample) " - + "RETURN s;") + + "RETURN s") List findResearchSamplesByRequest(@Param("reqId") String reqId); @Query("MATCH (r: Request {igoRequestId: $reqId}) " @@ -104,7 +104,7 @@ void updateSamplePatientRelationship(@Param("smileSampleId") UUID smileSampleId, + "OR sa.value = $inputId " + "OR s.smileSampleId = $inputId " + "OR sm.cmoSampleName = $inputId " - + "RETURN s;") + + "RETURN s") SmileSample findSampleByInputId(@Param("inputId") String inputId); @Query("MATCH (s: Sample {smileSampleId: $smileSampleId}) " @@ -129,4 +129,12 @@ void createSampleRequestRelationship(@Param("smileSampleId") UUID smileSampleId, + "RETURN s") SmileSample updateRevisableBySampleId(@Param("smileSampleId") UUID smileSampleId, @Param("revisable") Boolean revisable); + + @Query("MATCH (s: Sample)-[:HAS_METADATA]->(sm: SampleMetadata {primaryId: $primaryId}) RETURN s") + SmileSample sampleExistsByPrimaryId(@Param("primaryId") String primaryId); + + @Query("MATCH (c: Cohort {cohortId: $cohortId})-[:HAS_COHORT_SAMPLE]->" + + "(s: Sample) " + + "RETURN s") + List findSamplesByCohortId(@Param("cohortId") String cohortId); } diff --git a/persistence/src/main/java/org/mskcc/smile/persistence/neo4j/TempoRepository.java b/persistence/src/main/java/org/mskcc/smile/persistence/neo4j/TempoRepository.java new file mode 100644 index 00000000..cd09c01b --- /dev/null +++ b/persistence/src/main/java/org/mskcc/smile/persistence/neo4j/TempoRepository.java @@ -0,0 +1,72 @@ +package org.mskcc.smile.persistence.neo4j; + +import java.util.List; +import java.util.UUID; +import org.mskcc.smile.model.tempo.BamComplete; +import org.mskcc.smile.model.tempo.MafComplete; +import org.mskcc.smile.model.tempo.QcComplete; +import org.mskcc.smile.model.tempo.Tempo; +import org.springframework.data.neo4j.annotation.Query; +import org.springframework.data.neo4j.repository.Neo4jRepository; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +/** + * + * @author ochoaa + */ +@Repository +public interface TempoRepository extends Neo4jRepository { + @Query("MATCH (s: Sample {smileSampleId: $smileSampleId})-[:HAS_TEMPO]->(t: Tempo) " + + "RETURN t") + Tempo findTempoBySmileSampleId(@Param("smileSampleId") UUID smileSampleId); + + @Query("MATCH (s: Sample)-[:HAS_METADATA]->(sm: SampleMetadata {primaryId: $primaryId}) " + + "MATCH (s)-[:HAS_TEMPO]->(t:Tempo) RETURN t") + Tempo findTempoBySamplePrimaryId(@Param("primaryId") String primaryId); + + @Query("MATCH (t: Tempo) WHERE ID(t) = $tempoId MATCH (t)-[:HAS_EVENT]->(bc: BamComplete) " + + "RETURN bc") + List findBamCompleteEventsByTempoId(@Param("tempoId") Long tempoId); + + @Query("MATCH (t: Tempo) WHERE ID(t) = $tempoId MATCH (t)-[:HAS_EVENT]->(qc: QcComplete) " + + "RETURN qc") + List findQcCompleteEventsByTempoId(@Param("tempoId") Long tempoId); + @Query("MATCH (t: Tempo) WHERE ID(t) = $tempoId MATCH (t)-[:HAS_EVENT]->(mc: MafComplete) " + + "RETURN mc") + List findMafCompleteEventsByTempoId(@Param("tempoId") Long tempoId); + + @Query("MATCH (t:Tempo) WHERE ID(t) = $tempoId MATCH (t)-[:HAS_EVENT]->(bc: BamComplete) " + + "RETURN bc ORDER BY bc.date DESC LIMIT 1") + BamComplete findLatestBamCompleteEventByTempoId(@Param("tempoId") Long tempoId); + + @Query("MATCH (t:Tempo) WHERE ID(t) = $tempoId MATCH (t)-[:HAS_EVENT]->(mc: MafComplete) " + + "RETURN mc ORDER BY mc.date DESC LIMIT 1") + MafComplete findLatestMafCompleteEventByTempoId(@Param("tempoId") Long tempoId); + + @Query("MATCH (t:Tempo) WHERE ID(t) = $tempoId MATCH (t)-[:HAS_EVENT]->(qc: QcComplete) " + + "RETURN qc ORDER BY qc.date DESC LIMIT 1") + QcComplete findLatestQcCompleteEventByTempoId(@Param("tempoId") Long tempoId); + + @Query("MATCH (s: Sample)-[:HAS_METADATA]->(sm: SampleMetadata {primaryId: $primaryId}) " + + "MERGE (s)-[:HAS_TEMPO]->(t: Tempo) WITH s,t " + + "MERGE (t)-[:HAS_EVENT]->(bc: BamComplete {date: $bcEvent.date, " + + "status: $bcEvent.status}) WITH s,t,bc RETURN t") + Tempo mergeBamCompleteEventBySamplePrimaryId(@Param("primaryId") String primaryId, + @Param("bcEvent") BamComplete bcEvent); + + @Query("MATCH (s: Sample)-[:HAS_METADATA]->(sm: SampleMetadata {primaryId: $primaryId}) " + + "MERGE (s)-[:HAS_TEMPO]->(t: Tempo) WITH s,t " + + "MERGE (t)-[:HAS_EVENT]->(qc: QcComplete {date: $qcEvent.date, result: $qcEvent.result, " + + "reason: $qcEvent.reason, status: $qcEvent.status}) WITH s,t,qc RETURN t") + Tempo mergeQcCompleteEventBySamplePrimaryId(@Param("primaryId") String primaryId, + @Param("qcEvent") QcComplete qcEvent); + + @Query("MATCH (s: Sample)-[:HAS_METADATA]->(sm: SampleMetadata {primaryId: $primaryId}) " + + "MERGE (s)-[:HAS_TEMPO]->(t: Tempo) WITH s,t " + + "MERGE (t)-[:HAS_EVENT]->(mc: MafComplete {date: $mcEvent.date, " + + "normalPrimaryId: $mcEvent.normalPrimaryId, status: $mcEvent.status}) " + + "WITH s,t,mc RETURN t") + Tempo mergeMafCompleteEventBySamplePrimaryId(@Param("primaryId") String primaryId, + @Param("mcEvent") MafComplete mcEvent); +} diff --git a/pom.xml b/pom.xml index d7971f61..63672b6f 100644 --- a/pom.xml +++ b/pom.xml @@ -45,13 +45,13 @@ 3.2.14 5.3.2 4.4.9 - 1.16.0 + 1.17.0 com.github.mskcc - 1.3.11.RELEASE + 1.4.1.RELEASE com.github.mskcc - 1.3.11.RELEASE + 1.4.1.RELEASE v2.3 diff --git a/server/src/main/java/org/mskcc/smile/SmileApp.java b/server/src/main/java/org/mskcc/smile/SmileApp.java index bca83684..bfb827fa 100644 --- a/server/src/main/java/org/mskcc/smile/SmileApp.java +++ b/server/src/main/java/org/mskcc/smile/SmileApp.java @@ -8,6 +8,7 @@ import org.mskcc.smile.service.CorrectCmoPatientHandlingService; import org.mskcc.smile.service.RequestReplyHandlingService; import org.mskcc.smile.service.ResearchMessageHandlingService; +import org.mskcc.smile.service.TempoMessageHandlingService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; @@ -49,6 +50,9 @@ public class SmileApp implements CommandLineRunner { @Autowired private CorrectCmoPatientHandlingService correctCmoPatientHandlingService; + @Autowired + private TempoMessageHandlingService tempoMessageHandlingService; + @Autowired private RequestReplyHandlingService requestReplyHandlingService; @@ -86,6 +90,7 @@ public void run(String... args) throws Exception { researchMessageHandlingService.initialize(messagingGateway); clinicalMessageHandlingService.initialize(messagingGateway); correctCmoPatientHandlingService.initialize(messagingGateway); + tempoMessageHandlingService.intialize(messagingGateway); smileAppClose.await(); } catch (Exception e) { LOG.error("Encountered error during initialization", e); @@ -102,6 +107,7 @@ public void run() { researchMessageHandlingService.shutdown(); clinicalMessageHandlingService.shutdown(); correctCmoPatientHandlingService.shutdown(); + tempoMessageHandlingService.shutdown(); messagingGateway.shutdown(); } catch (Exception e) { LOG.error("Encountered error during shutdown process", e); diff --git a/service/src/main/java/org/mskcc/smile/service/CohortCompleteService.java b/service/src/main/java/org/mskcc/smile/service/CohortCompleteService.java new file mode 100644 index 00000000..ce9cc595 --- /dev/null +++ b/service/src/main/java/org/mskcc/smile/service/CohortCompleteService.java @@ -0,0 +1,18 @@ +package org.mskcc.smile.service; + +import java.util.List; +import java.util.Set; +import org.mskcc.smile.model.tempo.Cohort; +import org.mskcc.smile.model.tempo.CohortComplete; + +/** + * + * @author ochoaa + */ +public interface CohortCompleteService { + Cohort saveCohort(Cohort cohort, Set samplePrimaryIds) throws Exception; + Cohort getCohortByCohortId(String cohortId) throws Exception; + List getCohortsBySamplePrimaryId(String primaryId) throws Exception; + Boolean hasUpdates(Cohort cohort, CohortComplete newCohortComplete) throws Exception; + Cohort updateCohort(Cohort cohort) throws Exception; +} diff --git a/service/src/main/java/org/mskcc/smile/service/SmileSampleService.java b/service/src/main/java/org/mskcc/smile/service/SmileSampleService.java index f155eca1..a8cb52e1 100644 --- a/service/src/main/java/org/mskcc/smile/service/SmileSampleService.java +++ b/service/src/main/java/org/mskcc/smile/service/SmileSampleService.java @@ -34,4 +34,6 @@ List getSamplesByCategoryAndCmoPatientId(String cmoPatientId, List getSamplesByDate(String inputDate); SmileSample getSampleByInputId(String inputId); void createSampleRequestRelationship(UUID smileSampleId, UUID smileRequestId); + Boolean sampleExistsByPrimaryId(String primaryId); + List getSamplesByCohortId(String cohortId)throws Exception; } diff --git a/service/src/main/java/org/mskcc/smile/service/TempoMessageHandlingService.java b/service/src/main/java/org/mskcc/smile/service/TempoMessageHandlingService.java new file mode 100644 index 00000000..167f99ba --- /dev/null +++ b/service/src/main/java/org/mskcc/smile/service/TempoMessageHandlingService.java @@ -0,0 +1,21 @@ +package org.mskcc.smile.service; + +import java.util.Map; +import org.mskcc.cmo.messaging.Gateway; +import org.mskcc.smile.model.tempo.BamComplete; +import org.mskcc.smile.model.tempo.MafComplete; +import org.mskcc.smile.model.tempo.QcComplete; +import org.mskcc.smile.model.tempo.json.CohortCompleteJson; + +/** + * + * @author ochoaa + */ +public interface TempoMessageHandlingService { + void intialize(Gateway gateway) throws Exception; + void bamCompleteHandler(Map.Entry bcEvent) throws Exception; + void qcCompleteHandler(Map.Entry bcEvent) throws Exception; + void mafCompleteHandler(Map.Entry mcEvent) throws Exception; + void cohortCompleteHandler(CohortCompleteJson ccEvent) throws Exception; + void shutdown() throws Exception; +} diff --git a/service/src/main/java/org/mskcc/smile/service/TempoService.java b/service/src/main/java/org/mskcc/smile/service/TempoService.java new file mode 100644 index 00000000..084823b9 --- /dev/null +++ b/service/src/main/java/org/mskcc/smile/service/TempoService.java @@ -0,0 +1,20 @@ +package org.mskcc.smile.service; + +import org.mskcc.smile.model.SmileSample; +import org.mskcc.smile.model.tempo.BamComplete; +import org.mskcc.smile.model.tempo.MafComplete; +import org.mskcc.smile.model.tempo.QcComplete; +import org.mskcc.smile.model.tempo.Tempo; + +/** + * + * @author ochoaa + */ +public interface TempoService { + Tempo saveTempoData(Tempo tempo); + Tempo getTempoDataBySampleId(SmileSample smileSample); + Tempo getTempoDataBySamplePrimaryId(String primaryId); + Tempo mergeBamCompleteEventBySamplePrimaryId(String primaryId, BamComplete bamComplete); + Tempo mergeQcCompleteEventBySamplePrimaryId(String primaryId, QcComplete qcComplete); + Tempo mergeMafCompleteEventBySamplePrimaryId(String primaryId, MafComplete mafComplete); +} diff --git a/service/src/main/java/org/mskcc/smile/service/impl/CohortCompleteServiceImpl.java b/service/src/main/java/org/mskcc/smile/service/impl/CohortCompleteServiceImpl.java new file mode 100644 index 00000000..6bc776b5 --- /dev/null +++ b/service/src/main/java/org/mskcc/smile/service/impl/CohortCompleteServiceImpl.java @@ -0,0 +1,82 @@ +package org.mskcc.smile.service.impl; + +import com.fasterxml.jackson.databind.ObjectMapper; +import java.util.List; +import java.util.Set; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.mskcc.smile.commons.JsonComparator; +import org.mskcc.smile.model.tempo.Cohort; +import org.mskcc.smile.model.tempo.CohortComplete; +import org.mskcc.smile.persistence.neo4j.CohortCompleteRepository; +import org.mskcc.smile.service.CohortCompleteService; +import org.mskcc.smile.service.SmileSampleService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * + * @author ochoaa + */ +@Component +public class CohortCompleteServiceImpl implements CohortCompleteService { + @Autowired + private JsonComparator jsonComparator; + @Autowired + private CohortCompleteRepository cohortCompleteRepository; + @Autowired + private SmileSampleService sampleService; + + private ObjectMapper mapper = new ObjectMapper(); + + private static final Log LOG = LogFactory.getLog(CohortCompleteServiceImpl.class); + + @Override + public Cohort saveCohort(Cohort cohort, Set samplePrimaryIds) throws Exception { + // persist new cohort complete event to the db + cohortCompleteRepository.save(cohort); + // create cohort-smaple relationships + for (String primaryId : samplePrimaryIds) { + // confirm sample exists by primary id and then link to cohort + if (sampleService.sampleExistsByPrimaryId(primaryId)) { + cohortCompleteRepository.addCohortSampleRelationship(cohort.getCohortId(), primaryId); + } + } + return getCohortByCohortId(cohort.getCohortId()); + } + + @Override + public Cohort getCohortByCohortId(String cohortId) throws Exception { + Cohort cohort = cohortCompleteRepository.findCohortByCohortId(cohortId); + return getDetailedCohortData(cohort); + } + + @Override + public List getCohortsBySamplePrimaryId(String primaryId) throws Exception { + return cohortCompleteRepository.findCohortsBySamplePrimaryId(primaryId); + } + + @Override + public Boolean hasUpdates(Cohort cohort, CohortComplete cohortComplete) throws Exception { + String existingCohortComplete = mapper.writeValueAsString(cohort.getLatestCohortComplete()); + String currentCohortComplete = mapper.writeValueAsString(cohortComplete); + return !jsonComparator.isConsistentGenericComparison(existingCohortComplete, currentCohortComplete); + } + + @Override + public Cohort updateCohort(Cohort cohort) throws Exception { + return cohortCompleteRepository.save(cohort); + } + + private Cohort getDetailedCohortData(Cohort cohort) throws Exception { + if (cohort == null || cohort.getId() == null) { + return null; + } + // get cohort samples + cohort.setCohortSamples(sampleService.getSamplesByCohortId(cohort.getCohortId())); + // get cohort complete events + cohort.setCohortCompleteList( + cohortCompleteRepository.findCohortCompleteEventsByCohortId(cohort.getCohortId())); + return cohort; + } +} diff --git a/service/src/main/java/org/mskcc/smile/service/impl/SampleServiceImpl.java b/service/src/main/java/org/mskcc/smile/service/impl/SampleServiceImpl.java index d9fb3e59..8592ceef 100644 --- a/service/src/main/java/org/mskcc/smile/service/impl/SampleServiceImpl.java +++ b/service/src/main/java/org/mskcc/smile/service/impl/SampleServiceImpl.java @@ -24,6 +24,7 @@ import org.mskcc.smile.service.SmilePatientService; import org.mskcc.smile.service.SmileRequestService; import org.mskcc.smile.service.SmileSampleService; +import org.mskcc.smile.service.TempoService; import org.mskcc.smile.service.util.SampleDataFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -45,6 +46,9 @@ public class SampleServiceImpl implements SmileSampleService { @Autowired private SmilePatientService patientService; + @Autowired + private TempoService tempoService; + @Autowired private CrdbMappingService crdbMappingService; @@ -407,6 +411,7 @@ public SmileSample getDetailedSmileSample(SmileSample sample) throws ParseExcept SmilePatient patient = patientService.getPatientByCmoPatientId(cmoPatientId); sample.setPatient(patient); sample.setSampleAliases(sampleRepository.findAllSampleAliases(sample.getSmileSampleId())); + sample.setTempo(tempoService.getTempoDataBySampleId(sample)); return sample; } @@ -465,6 +470,22 @@ public void createSampleRequestRelationship(UUID smileSampleId, UUID smileReques sampleRepository.createSampleRequestRelationship(smileSampleId, smileRequestId); } + @Override + public Boolean sampleExistsByPrimaryId(String primaryId) { + return (sampleRepository.sampleExistsByPrimaryId(primaryId) != null); + } + + @Override + public List getSamplesByCohortId(String cohortId) throws Exception { + List samples = sampleRepository.findSamplesByCohortId(cohortId); + + List detailedSamples = new ArrayList<>(); + for (SmileSample s: samples) { + detailedSamples.add(getDetailedSmileSample(s)); + } + return detailedSamples; + } + private List getSampleMetadataWithStatus(List smList) { for (SampleMetadata sm : smList) { sm.setStatus(sampleRepository.findStatusForSampleMetadataById(sm.getId())); diff --git a/service/src/main/java/org/mskcc/smile/service/impl/TempoMessageHandlingServiceImpl.java b/service/src/main/java/org/mskcc/smile/service/impl/TempoMessageHandlingServiceImpl.java new file mode 100644 index 00000000..73906378 --- /dev/null +++ b/service/src/main/java/org/mskcc/smile/service/impl/TempoMessageHandlingServiceImpl.java @@ -0,0 +1,466 @@ +package org.mskcc.smile.service.impl; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.nats.client.Message; +import java.nio.charset.StandardCharsets; +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.Phaser; +import java.util.concurrent.TimeUnit; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.mskcc.cmo.messaging.Gateway; +import org.mskcc.cmo.messaging.MessageConsumer; +import org.mskcc.smile.model.tempo.BamComplete; +import org.mskcc.smile.model.tempo.Cohort; +import org.mskcc.smile.model.tempo.CohortComplete; +import org.mskcc.smile.model.tempo.MafComplete; +import org.mskcc.smile.model.tempo.QcComplete; +import org.mskcc.smile.model.tempo.Tempo; +import org.mskcc.smile.model.tempo.json.CohortCompleteJson; +import org.mskcc.smile.service.CohortCompleteService; +import org.mskcc.smile.service.SmileSampleService; +import org.mskcc.smile.service.TempoMessageHandlingService; +import org.mskcc.smile.service.TempoService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +/** + * + * @author ochoaa + */ +@Component +public class TempoMessageHandlingServiceImpl implements TempoMessageHandlingService { + @Value("${tempo.wes_bam_complete_topic}") + private String TEMPO_WES_BAM_COMPLETE_TOPIC; + + @Value("${tempo.wes_qc_complete_topic}") + private String TEMPO_WES_QC_COMPLETE_TOPIC; + + @Value("${tempo.wes_maf_complete_topic}") + private String TEMPO_WES_MAF_COMPLETE_TOPIC; + + @Value("${tempo.wes_cohort_complete_topic}") + private String TEMPO_WES_COHORT_COMPLETE_TOPIC; + + @Value("${num.tempo_msg_handler_threads}") + private int NUM_TEMPO_MSG_HANDLERS; + + @Autowired + private SmileSampleService sampleService; + + @Autowired + private TempoService tempoService; + + @Autowired + private CohortCompleteService cohortCompleteService; + + private static Gateway messagingGateway; + private static final Log LOG = LogFactory.getLog(TempoMessageHandlingServiceImpl.class); + private final ObjectMapper mapper = new ObjectMapper(); + + private static boolean initialized = false; + private static volatile boolean shutdownInitiated; + private static final ExecutorService exec = Executors.newCachedThreadPool(); + private static final BlockingQueue> bamCompleteQueue = + new LinkedBlockingQueue>(); + private static final BlockingQueue> qcCompleteQueue = + new LinkedBlockingQueue>(); + private static final BlockingQueue> mafCompleteQueue = + new LinkedBlockingQueue>(); + private static final BlockingQueue cohortCompleteQueue = + new LinkedBlockingQueue(); + + private static CountDownLatch bamCompleteHandlerShutdownLatch; + private static CountDownLatch qcCompleteHandlerShutdownLatch; + private static CountDownLatch mafCompleteHandlerShutdownLatch; + private static CountDownLatch cohortCompleteHandlerShutdownLatch; + + private class BamCompleteHandler implements Runnable { + final Phaser phaser; + boolean interrupted = false; + + BamCompleteHandler(Phaser phaser) { + this.phaser = phaser; + } + + @Override + public void run() { + phaser.arrive(); + while (true) { + try { + Entry bcEvent = bamCompleteQueue.poll(100, TimeUnit.MILLISECONDS); + if (bcEvent != null) { + // first determine if sample exists by the provided primary id + String primaryId = bcEvent.getKey(); + BamComplete bamComplete = bcEvent.getValue(); + if (sampleService.sampleExistsByPrimaryId(primaryId)) { + // merge and/or create tempo bam complete event to sample + Tempo tempo = tempoService.getTempoDataBySamplePrimaryId(primaryId); + if (tempo == null + || !tempo.hasBamCompleteEvent(bamComplete)) { + tempoService.mergeBamCompleteEventBySamplePrimaryId(primaryId, + bamComplete); + } + } else { + LOG.error("Sample with primary id " + primaryId + " does not exist"); + } + } + } catch (InterruptedException e) { + interrupted = true; + } catch (Exception e) { + LOG.error("Error during handling of BAM complete event", e); + } + } + } + } + + private class QcCompleteHandler implements Runnable { + final Phaser phaser; + boolean interrupted = false; + + QcCompleteHandler(Phaser phaser) { + this.phaser = phaser; + } + + @Override + public void run() { + phaser.arrive(); + while (true) { + try { + Entry qcEvent = qcCompleteQueue.poll(100, TimeUnit.MILLISECONDS); + if (qcEvent != null) { + // first determine if sample exists by the provided primary id + String primaryId = qcEvent.getKey(); + QcComplete qcComplete = qcEvent.getValue(); + if (sampleService.sampleExistsByPrimaryId(primaryId)) { + // merge and/or create tempo qc complete event to sample + Tempo tempo = tempoService.getTempoDataBySamplePrimaryId(primaryId); + if (tempo == null + || !tempo.hasQcCompleteEvent(qcComplete)) { + tempoService.mergeQcCompleteEventBySamplePrimaryId(primaryId, + qcComplete); + } + } else { + LOG.error("Sample with primary id: " + primaryId + " does not exist"); + } + } + } catch (InterruptedException e) { + interrupted = true; + } catch (Exception e) { + LOG.error("Error during handling of BAM complete event", e); + } + } + } + } + + private class MafCompleteHandler implements Runnable { + final Phaser phaser; + boolean interrupted = false; + + MafCompleteHandler(Phaser phaser) { + this.phaser = phaser; + } + + @Override + public void run() { + phaser.arrive(); + while (true) { + try { + Entry mcEvent = mafCompleteQueue.poll(100, TimeUnit.MILLISECONDS); + if (mcEvent != null) { + // first determine if sample exists by the provided primary id + String primaryId = mcEvent.getKey(); + MafComplete mafComplete = mcEvent.getValue(); + if (sampleService.sampleExistsByPrimaryId(primaryId)) { + // merge and/or create tempo maf complete event to sample + Tempo tempo = tempoService.getTempoDataBySamplePrimaryId(primaryId); + if (tempo == null + || !tempo.hasMafCompleteEvent(mafComplete)) { + tempoService.mergeMafCompleteEventBySamplePrimaryId(primaryId, + mafComplete); + } + } else { + LOG.error("Sample with primary id " + primaryId + " does not exist"); + } + } + } catch (InterruptedException e) { + interrupted = true; + } catch (Exception e) { + LOG.error("Error during handling of MAF complete event", e); + } + } + } + } + + private class CohortCompleteHandler implements Runnable { + final Phaser phaser; + boolean interrupted = false; + + CohortCompleteHandler(Phaser phaser) { + this.phaser = phaser; + } + + @Override + public void run() { + phaser.arrive(); + while (true) { + try { + CohortCompleteJson ccJson = cohortCompleteQueue.poll(100, TimeUnit.MILLISECONDS); + if (ccJson != null) { + // cohorts are never redelivered. only updates to end users + // (access) can change but associated cohort samples do not change + Cohort cohort = new Cohort(ccJson); + Cohort existingCohort = + cohortCompleteService.getCohortByCohortId(ccJson.getCohortId()); + if (existingCohort == null) { + LOG.info("Persisting new cohort: " + ccJson.getCohortId()); + // tumor-normal pairs are provided as map entries - this block + // compiles them into a set list of strings + cohortCompleteService.saveCohort(cohort, ccJson.getTumorNormalPairsAsSet()); + } else if (cohortCompleteService.hasUpdates(existingCohort, + cohort.getLatestCohortComplete())) { + LOG.info("Received updates for cohort: " + ccJson.getCohortId()); + existingCohort.addCohortComplete(cohort.getLatestCohortComplete()); + cohortCompleteService.updateCohort(existingCohort); + } else { + LOG.error("Cohort " + ccJson.getCohortId() + + " already exists and no new updates were received."); + } + } + } catch (InterruptedException e) { + interrupted = true; + } catch (Exception e) { + LOG.error("Error during handling of Cohort complete event", e); + } + } + } + } + + @Override + public void intialize(Gateway gateway) throws Exception { + if (!initialized) { + messagingGateway = gateway; + setupBamCompleteHandler(messagingGateway, this); + setupQcCompleteHandler(messagingGateway, this); + setupMafCompleteHandler(messagingGateway, this); + setupCohortCompleteHandler(messagingGateway, this); + initializeMessageHandlers(); + initialized = true; + } else { + LOG.error("Messaging Handler Service has already been initialized, ignoring request.\n"); + } + } + + @Override + public void bamCompleteHandler(Map.Entry bcEvent) throws Exception { + if (!initialized) { + throw new IllegalStateException("Message Handling Service has not been initialized"); + } + if (!shutdownInitiated) { + bamCompleteQueue.put(bcEvent); + } else { + LOG.error("Shutdown initiated, not accepting BAM event: " + bcEvent); + throw new IllegalStateException("Shutdown initiated, not handling any more TEMPO events"); + } + } + + @Override + public void qcCompleteHandler(Map.Entry qcEvent) throws Exception { + if (!initialized) { + throw new IllegalStateException("Message Handling Service has not been initialized"); + } + if (!shutdownInitiated) { + qcCompleteQueue.put(qcEvent); + } else { + LOG.error("Shutdown initiated, not accepting QC event: " + qcEvent); + throw new IllegalStateException("Shutdown initiated, not handling any more TEMPO events"); + } + } + + @Override + public void mafCompleteHandler(Map.Entry mcEvent) throws Exception { + if (!initialized) { + throw new IllegalStateException("Message Handling Service has not been initialized"); + } + if (!shutdownInitiated) { + mafCompleteQueue.put(mcEvent); + } else { + LOG.error("Shutdown initiated, not accepting MAF event: " + mcEvent); + throw new IllegalStateException("Shutdown initiated, not handling any more TEMPO events"); + } + } + + @Override + public void cohortCompleteHandler(CohortCompleteJson cohortEvent) throws Exception { + if (!initialized) { + throw new IllegalStateException("Message Handling Service has not been initialized"); + } + if (!shutdownInitiated) { + cohortCompleteQueue.put(cohortEvent); + } else { + LOG.error("Shutdown initiated, not accepting Cohort event: " + cohortEvent); + throw new IllegalStateException("Shutdown initiated, not handling any more TEMPO events"); + } + } + + @Override + public void shutdown() throws Exception { + if (!initialized) { + throw new IllegalStateException("Message Handling Service has not been initialized"); + } + exec.shutdownNow(); + bamCompleteHandlerShutdownLatch.await(); + qcCompleteHandlerShutdownLatch.await(); + mafCompleteHandlerShutdownLatch.await(); + cohortCompleteHandlerShutdownLatch.await(); + shutdownInitiated = true; + } + + private void initializeMessageHandlers() throws Exception { + // bam complete handler + bamCompleteHandlerShutdownLatch = new CountDownLatch(NUM_TEMPO_MSG_HANDLERS); + final Phaser bamCompletePhaser = new Phaser(); + bamCompletePhaser.register(); + for (int lc = 0; lc < NUM_TEMPO_MSG_HANDLERS; lc++) { + bamCompletePhaser.register(); + exec.execute(new BamCompleteHandler(bamCompletePhaser)); + } + bamCompletePhaser.arriveAndAwaitAdvance(); + + // qc complete handler + qcCompleteHandlerShutdownLatch = new CountDownLatch(NUM_TEMPO_MSG_HANDLERS); + final Phaser qcCompletePhaser = new Phaser(); + qcCompletePhaser.register(); + for (int lc = 0; lc < NUM_TEMPO_MSG_HANDLERS; lc++) { + qcCompletePhaser.register(); + exec.execute(new QcCompleteHandler(qcCompletePhaser)); + } + qcCompletePhaser.arriveAndAwaitAdvance(); + + // maf complete handler + mafCompleteHandlerShutdownLatch = new CountDownLatch(NUM_TEMPO_MSG_HANDLERS); + final Phaser mafCompletePhaser = new Phaser(); + mafCompletePhaser.register(); + for (int lc = 0; lc < NUM_TEMPO_MSG_HANDLERS; lc++) { + mafCompletePhaser.register(); + exec.execute(new MafCompleteHandler(mafCompletePhaser)); + } + mafCompletePhaser.arriveAndAwaitAdvance(); + + // cohort complete handler + cohortCompleteHandlerShutdownLatch = new CountDownLatch(NUM_TEMPO_MSG_HANDLERS); + final Phaser cohortCompletePhaser = new Phaser(); + cohortCompletePhaser.register(); + for (int lc = 0; lc < NUM_TEMPO_MSG_HANDLERS; lc++) { + cohortCompletePhaser.register(); + exec.execute(new CohortCompleteHandler(cohortCompletePhaser)); + } + cohortCompletePhaser.arriveAndAwaitAdvance(); + } + + private void setupBamCompleteHandler(Gateway gateway, + TempoMessageHandlingService tempoMessageHandlingService) throws Exception { + gateway.subscribe(TEMPO_WES_BAM_COMPLETE_TOPIC, Object.class, new MessageConsumer() { + @Override + public void onMessage(Message msg, Object message) { + try { + String bamCompleteJson = mapper.readValue( + new String(msg.getData(), StandardCharsets.UTF_8), + String.class); + Map bamCompleteMap = mapper.readValue(bamCompleteJson, Map.class); + BamComplete bamComplete = new BamComplete(bamCompleteMap.get("date"), + bamCompleteMap.get("status")); + String primaryId = bamCompleteMap.get("primaryId"); + Map.Entry eventData = + new AbstractMap.SimpleImmutableEntry<>(primaryId, bamComplete); + tempoMessageHandlingService.bamCompleteHandler(eventData); + } catch (Exception e) { + LOG.error("Exception occurred during processing of BAM complete event: " + + TEMPO_WES_BAM_COMPLETE_TOPIC, e); + } + } + }); + } + + private void setupQcCompleteHandler(Gateway gateway, + TempoMessageHandlingService tempoMessageHandlingService) throws Exception { + gateway.subscribe(TEMPO_WES_QC_COMPLETE_TOPIC, Object.class, new MessageConsumer() { + @Override + public void onMessage(Message msg, Object message) { + try { + String qcCompleteJson = mapper.readValue( + new String(msg.getData(), StandardCharsets.UTF_8), + String.class); + Map qcCompleteMap = mapper.readValue(qcCompleteJson, Map.class); + QcComplete qcComplete = new QcComplete(qcCompleteMap.get("date"), + qcCompleteMap.get("result"), qcCompleteMap.get("reason"), + qcCompleteMap.get("status")); + String primaryId = qcCompleteMap.get("primaryId"); + Map.Entry eventData = + new AbstractMap.SimpleImmutableEntry<>(primaryId, qcComplete); + tempoMessageHandlingService.qcCompleteHandler(eventData); + } catch (Exception e) { + LOG.error("Exception occurred during processing of QC complete event: " + + TEMPO_WES_QC_COMPLETE_TOPIC, e); + } + } + }); + } + + private void setupMafCompleteHandler(Gateway gateway, + TempoMessageHandlingService tempoMessageHandlingService) throws Exception { + gateway.subscribe(TEMPO_WES_MAF_COMPLETE_TOPIC, Object.class, new MessageConsumer() { + @Override + public void onMessage(Message msg, Object message) { + try { + String mafCompleteJson = mapper.readValue( + new String(msg.getData(), StandardCharsets.UTF_8), + String.class); + Map mafCompleteMap = mapper.readValue(mafCompleteJson, Map.class); + MafComplete mafComplete = new MafComplete(mafCompleteMap.get("date"), + mafCompleteMap.get("normalPrimaryId"), + mafCompleteMap.get("status")); + String primaryId = mafCompleteMap.get("primaryId"); + Map.Entry eventData = + new AbstractMap.SimpleImmutableEntry<>(primaryId, mafComplete); + tempoMessageHandlingService.mafCompleteHandler(eventData); + } catch (Exception e) { + LOG.error("Exception occurred during processing of MAF complete event: " + + TEMPO_WES_MAF_COMPLETE_TOPIC, e); + } + } + }); + } + + private void setupCohortCompleteHandler(Gateway gateway, + TempoMessageHandlingService tempoMessageHandlingService) throws Exception { + gateway.subscribe(TEMPO_WES_COHORT_COMPLETE_TOPIC, Object.class, new MessageConsumer() { + @Override + public void onMessage(Message msg, Object message) { + try { + String cohortCompleteJson = mapper.readValue( + new String(msg.getData(), StandardCharsets.UTF_8), + String.class); + CohortCompleteJson cohortCompleteData = mapper.readValue(cohortCompleteJson, + CohortCompleteJson.class); + tempoMessageHandlingService.cohortCompleteHandler(cohortCompleteData); + } catch (Exception e) { + LOG.error("Exception occurred during processing of Cohort Complete event: " + + TEMPO_WES_COHORT_COMPLETE_TOPIC, e); + } + } + }); + } +} diff --git a/service/src/main/java/org/mskcc/smile/service/impl/TempoServiceImpl.java b/service/src/main/java/org/mskcc/smile/service/impl/TempoServiceImpl.java new file mode 100644 index 00000000..1e61c536 --- /dev/null +++ b/service/src/main/java/org/mskcc/smile/service/impl/TempoServiceImpl.java @@ -0,0 +1,67 @@ +package org.mskcc.smile.service.impl; + +import org.mskcc.smile.model.SmileSample; +import org.mskcc.smile.model.tempo.BamComplete; +import org.mskcc.smile.model.tempo.MafComplete; +import org.mskcc.smile.model.tempo.QcComplete; +import org.mskcc.smile.model.tempo.Tempo; +import org.mskcc.smile.persistence.neo4j.TempoRepository; +import org.mskcc.smile.service.TempoService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * + * @author ochoaa + */ +@Component +public class TempoServiceImpl implements TempoService { + + @Autowired + private TempoRepository tempoRepository; + + @Override + public Tempo saveTempoData(Tempo tempo) { + return tempoRepository.save(tempo); + } + + @Override + public Tempo getTempoDataBySampleId(SmileSample smileSample) { + Tempo tempo = tempoRepository.findTempoBySmileSampleId(smileSample.getSmileSampleId()); + return getDetailedTempoData(tempo); + } + + @Override + public Tempo getTempoDataBySamplePrimaryId(String primaryId) { + Tempo tempo = tempoRepository.findTempoBySamplePrimaryId(primaryId); + return getDetailedTempoData(tempo); + } + + @Override + public Tempo mergeBamCompleteEventBySamplePrimaryId(String primaryId, BamComplete bamCompleteEvent) { + Tempo tempo = tempoRepository.mergeBamCompleteEventBySamplePrimaryId(primaryId, bamCompleteEvent); + return getDetailedTempoData(tempo); + } + + @Override + public Tempo mergeQcCompleteEventBySamplePrimaryId(String primaryId, QcComplete qcCompleteEvent) { + Tempo tempo = tempoRepository.mergeQcCompleteEventBySamplePrimaryId(primaryId, qcCompleteEvent); + return getDetailedTempoData(tempo); + } + + @Override + public Tempo mergeMafCompleteEventBySamplePrimaryId(String primaryId, MafComplete mafCompleteEvent) { + Tempo tempo = tempoRepository.mergeMafCompleteEventBySamplePrimaryId(primaryId, mafCompleteEvent); + return getDetailedTempoData(tempo); + } + + private Tempo getDetailedTempoData(Tempo tempo) { + if (tempo == null || tempo.getId() == null) { + return null; + } + tempo.setBamCompleteEvents(tempoRepository.findBamCompleteEventsByTempoId(tempo.getId())); + tempo.setQcCompleteEvents(tempoRepository.findQcCompleteEventsByTempoId(tempo.getId())); + tempo.setMafCompleteEvents(tempoRepository.findMafCompleteEventsByTempoId(tempo.getId())); + return tempo; + } +} diff --git a/service/src/test/java/org/mskcc/smile/service/CorrectCmoPatientIdHandlerTest.java b/service/src/test/java/org/mskcc/smile/service/CorrectCmoPatientIdHandlerTest.java index 21b196ce..b5ec76c6 100644 --- a/service/src/test/java/org/mskcc/smile/service/CorrectCmoPatientIdHandlerTest.java +++ b/service/src/test/java/org/mskcc/smile/service/CorrectCmoPatientIdHandlerTest.java @@ -41,7 +41,7 @@ public class CorrectCmoPatientIdHandlerTest { private SmilePatientService patientService; @Container - private static final Neo4jContainer databaseServer = new Neo4jContainer<>() + private static final Neo4jContainer databaseServer = new Neo4jContainer<>() .withEnv("NEO4J_dbms_security_procedures_unrestricted", "apoc.*,algo.*"); @TestConfiguration diff --git a/service/src/test/java/org/mskcc/smile/service/MockDataUtils.java b/service/src/test/java/org/mskcc/smile/service/MockDataUtils.java index d80c6884..be56be9f 100644 --- a/service/src/test/java/org/mskcc/smile/service/MockDataUtils.java +++ b/service/src/test/java/org/mskcc/smile/service/MockDataUtils.java @@ -27,18 +27,48 @@ public final class MockDataUtils { = "data/dmp_clinical/mocked_dmp_data_details.txt"; private final String MOCKED_DMP_PATIENT_MAPPING_FILEPATH = "data/dmp_clinical/mocked_dmp_patient_mappings.txt"; + private final String MOCKED_TEMPO_DATA_DETAILS_FILEPATH = "data/tempo/mocked_tempo_data_details.txt"; private final String MOCKED_JSON_DATA_DIR = "data"; private final ClassPathResource mockJsonTestDataResource = new ClassPathResource(MOCKED_JSON_DATA_DIR); // mocked data maps public Map mockedRequestJsonDataMap; public Map mockedDmpMetadataMap; + public Map mockedTempoDataMap; public Map mockedDmpPatientMapping; public Map mockedDmpSampleMapping; // expected patient-sample counts (research and clinical) public final Map EXPECTED_PATIENT_SAMPLES_COUNT = initExpectedPatientSamplesCount(); + + /** + * Inits the mocked tempo data map. + * @throws IOException + */ + @Autowired + public void mockedTempoDataMap() throws IOException { + this.mockedTempoDataMap = new HashMap<>(); + ClassPathResource jsonDataDetailsResource = + new ClassPathResource(MOCKED_TEMPO_DATA_DETAILS_FILEPATH); + BufferedReader reader = new BufferedReader(new FileReader(jsonDataDetailsResource.getFile())); + List columns = new ArrayList<>(); + String line; + while ((line = reader.readLine()) != null) { + String[] data = line.split("\t"); + if (columns.isEmpty()) { + columns = Arrays.asList(data); + continue; + } + String identifier = data[columns.indexOf("identifier")]; + String filepath = data[columns.indexOf("filepath")]; + String description = data[columns.indexOf("description")]; + mockedTempoDataMap.put(identifier, + createMockJsonTestData(identifier, filepath, description)); + } + reader.close(); + } + /** * Inits the mocked dmp metadata map. * @throws IOException diff --git a/service/src/test/java/org/mskcc/smile/service/PatientServiceTest.java b/service/src/test/java/org/mskcc/smile/service/PatientServiceTest.java index c5df3035..1afb2a08 100644 --- a/service/src/test/java/org/mskcc/smile/service/PatientServiceTest.java +++ b/service/src/test/java/org/mskcc/smile/service/PatientServiceTest.java @@ -40,7 +40,7 @@ public class PatientServiceTest { private SmilePatientService patientService; @Container - private static final Neo4jContainer databaseServer = new Neo4jContainer<>() + private static final Neo4jContainer databaseServer = new Neo4jContainer<>() .withEnv("NEO4J_dbms_security_procedures_unrestricted", "apoc.*,algo.*"); @TestConfiguration diff --git a/service/src/test/java/org/mskcc/smile/service/RequestServiceTest.java b/service/src/test/java/org/mskcc/smile/service/RequestServiceTest.java index e9f6c729..b9d17b43 100644 --- a/service/src/test/java/org/mskcc/smile/service/RequestServiceTest.java +++ b/service/src/test/java/org/mskcc/smile/service/RequestServiceTest.java @@ -10,6 +10,7 @@ import org.mskcc.smile.persistence.neo4j.SmilePatientRepository; import org.mskcc.smile.persistence.neo4j.SmileRequestRepository; import org.mskcc.smile.persistence.neo4j.SmileSampleRepository; +import org.mskcc.smile.persistence.neo4j.TempoRepository; import org.mskcc.smile.service.util.RequestDataFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.data.neo4j.DataNeo4jTest; @@ -36,8 +37,11 @@ public class RequestServiceTest { @Autowired private SmilePatientService patientService; + @Autowired + private TempoService tempoService; + @Container - private static final Neo4jContainer databaseServer = new Neo4jContainer<>() + private static final Neo4jContainer databaseServer = new Neo4jContainer<>() .withEnv("NEO4J_dbms_security_procedures_unrestricted", "apoc.*,algo.*"); @TestConfiguration @@ -54,6 +58,7 @@ public org.neo4j.ogm.config.Configuration configuration() { private final SmileRequestRepository requestRepository; private final SmileSampleRepository sampleRepository; private final SmilePatientRepository patientRepository; + private final TempoRepository tempoRepository; /** * Initializes the Neo4j repositories. @@ -62,15 +67,19 @@ public org.neo4j.ogm.config.Configuration configuration() { * @param patientRepository * @param requestService * @param sampleService + * @param patientService + * @param tempoRepository + * @param tempoService */ @Autowired public RequestServiceTest(SmileRequestRepository requestRepository, SmileSampleRepository sampleRepository, SmilePatientRepository patientRepository, SmileRequestService requestService, SmileSampleService sampleService, - SmilePatientService patientService) { + SmilePatientService patientService, TempoRepository tempoRepository, TempoService tempoService) { this.requestRepository = requestRepository; this.sampleRepository = sampleRepository; this.patientRepository = patientRepository; + this.tempoRepository = tempoRepository; } /** @@ -228,7 +237,7 @@ public void testRequestSamplesWithUpdates() throws Exception { Assertions.assertThat(sampleList.size()).isEqualTo(2); } - + /** * Tests case where incoming request contains samples with * invalid metadata updates and should not be persisted. @@ -238,20 +247,20 @@ public void testRequestSamplesWithUpdates() throws Exception { public void testInvaildIgoRequestUpdates() throws Exception { String requestId = "MOCKREQUEST1_B"; SmileRequest origRequest = requestService.getSmileRequestById(requestId); - + MockJsonTestData updatedRequestData = mockDataUtils.mockedRequestJsonDataMap .get("mockIncomingRequest1UpdatedJsonDataWith2T2N"); SmileRequest updatedRequest = RequestDataFactory.buildNewLimsRequestFromJson( updatedRequestData.getJsonString()); - + Boolean hasUpdates = requestService.requestHasMetadataUpdates(origRequest.getLatestRequestMetadata(), updatedRequest.getLatestRequestMetadata(), Boolean.TRUE); Assertions.assertThat(hasUpdates).isTrue(); - + requestService.saveRequest(updatedRequest); SmileRequest existingRequest = requestService.getSmileRequestById(requestId); Assertions.assertThat(existingRequest.getIsCmoRequest()).isTrue(); - Assertions.assertThat(existingRequest.getQcAccessEmails()).isNotEqualTo("invalid-igo-update"); + Assertions.assertThat(existingRequest.getQcAccessEmails()).isNotEqualTo("invalid-igo-update"); } /** diff --git a/service/src/test/java/org/mskcc/smile/service/SampleServiceTest.java b/service/src/test/java/org/mskcc/smile/service/SampleServiceTest.java index def21138..78be5543 100644 --- a/service/src/test/java/org/mskcc/smile/service/SampleServiceTest.java +++ b/service/src/test/java/org/mskcc/smile/service/SampleServiceTest.java @@ -13,6 +13,7 @@ import org.mskcc.smile.persistence.neo4j.SmilePatientRepository; import org.mskcc.smile.persistence.neo4j.SmileRequestRepository; import org.mskcc.smile.persistence.neo4j.SmileSampleRepository; +import org.mskcc.smile.persistence.neo4j.TempoRepository; import org.mskcc.smile.service.util.RequestDataFactory; import org.mskcc.smile.service.util.SampleDataFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -46,8 +47,11 @@ public class SampleServiceTest { @Autowired private SmilePatientService patientService; + @Autowired + private TempoService tempoService; + @Container - private static final Neo4jContainer databaseServer = new Neo4jContainer<>() + private static final Neo4jContainer databaseServer = new Neo4jContainer<>() .withEnv("NEO4J_dbms_security_procedures_unrestricted", "apoc.*,algo.*"); @TestConfiguration @@ -64,6 +68,7 @@ public org.neo4j.ogm.config.Configuration configuration() { private final SmileRequestRepository requestRepository; private final SmileSampleRepository sampleRepository; private final SmilePatientRepository patientRepository; + private final TempoRepository tempoRepository; /** * Initializes the Neo4j repositories. @@ -72,15 +77,20 @@ public org.neo4j.ogm.config.Configuration configuration() { * @param patientRepository * @param requestService * @param sampleService + * @param patientService + * @param tempoRepository + * @param tempoService */ @Autowired public SampleServiceTest(SmileRequestRepository requestRepository, SmileSampleRepository sampleRepository, SmilePatientRepository patientRepository, SmileRequestService requestService, SmileSampleService sampleService, - SmilePatientService patientService) { + SmilePatientService patientService, TempoRepository tempoRepository, TempoService tempoService) { this.requestRepository = requestRepository; this.sampleRepository = sampleRepository; this.patientRepository = patientRepository; + this.tempoRepository = tempoRepository; + this.tempoService = tempoService; } /** @@ -406,7 +416,7 @@ public void testUpdateSampleMetadata() throws Exception { .getResearchSampleMetadataHistoryByIgoId(igoId); Assertions.assertThat(sampleMetadataHistory.size()).isEqualTo(2); } - + /** * Tests if sampleMetadata with invalid updates are not persisted to database * @throws Exception @@ -427,7 +437,7 @@ public void testInvalidIgoUpdateSampleMetadata() throws Exception { } } Assertions.assertThat(updatedSample).isNotNull(); - + String invalidCollectionYear = "INVALID IGO UPDATE"; SampleMetadata updatedMetadata = updatedSample.getLatestSampleMetadata(); updatedMetadata.setImportDate("2000-10-15"); diff --git a/service/src/test/java/org/mskcc/smile/service/SmileTestApp.java b/service/src/test/java/org/mskcc/smile/service/SmileTestApp.java index 13e10d73..7687f034 100644 --- a/service/src/test/java/org/mskcc/smile/service/SmileTestApp.java +++ b/service/src/test/java/org/mskcc/smile/service/SmileTestApp.java @@ -6,6 +6,7 @@ import org.mskcc.smile.commons.impl.JsonComparatorImpl; import org.mskcc.smile.persistence.jpa.CrdbRepository; import org.mskcc.smile.service.impl.ClinicalMessageHandlingServiceImpl; +import org.mskcc.smile.service.impl.CohortCompleteServiceImpl; import org.mskcc.smile.service.impl.CorrectCmoPatientHandlingServiceImpl; import org.mskcc.smile.service.impl.CrdbMappingServiceImpl; import org.mskcc.smile.service.impl.PatientServiceImpl; @@ -13,6 +14,8 @@ import org.mskcc.smile.service.impl.RequestServiceImpl; import org.mskcc.smile.service.impl.ResearchMessageHandlingServiceImpl; import org.mskcc.smile.service.impl.SampleServiceImpl; +import org.mskcc.smile.service.impl.TempoMessageHandlingServiceImpl; +import org.mskcc.smile.service.impl.TempoServiceImpl; import org.mskcc.smile.service.util.RequestStatusLogger; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.domain.EntityScan; @@ -49,6 +52,16 @@ public JsonComparator jsonComparator() { return new JsonComparatorImpl(); } + @Bean + public TempoService tempoService() { + return new TempoServiceImpl(); + } + + @Bean + public CohortCompleteService cohortCompleteService() { + return new CohortCompleteServiceImpl(); + } + @MockBean public CrdbRepository crdbRepository; @@ -76,4 +89,7 @@ public JsonComparator jsonComparator() { @MockBean public RequestReplyHandlingServiceImpl requestReplyHandlingService; + @MockBean + public TempoMessageHandlingServiceImpl tempoMessageHandlingService; + } diff --git a/service/src/test/java/org/mskcc/smile/service/TempoServiceTest.java b/service/src/test/java/org/mskcc/smile/service/TempoServiceTest.java new file mode 100644 index 00000000..7e1343ad --- /dev/null +++ b/service/src/test/java/org/mskcc/smile/service/TempoServiceTest.java @@ -0,0 +1,247 @@ +package org.mskcc.smile.service; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.util.List; +import java.util.Map; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; +import org.mskcc.smile.model.SmileRequest; +import org.mskcc.smile.model.SmileSample; +import org.mskcc.smile.model.tempo.BamComplete; +import org.mskcc.smile.model.tempo.Cohort; +import org.mskcc.smile.model.tempo.CohortComplete; +import org.mskcc.smile.model.tempo.MafComplete; +import org.mskcc.smile.model.tempo.QcComplete; +import org.mskcc.smile.model.tempo.Tempo; +import org.mskcc.smile.model.tempo.json.CohortCompleteJson; +import org.mskcc.smile.persistence.neo4j.SmilePatientRepository; +import org.mskcc.smile.persistence.neo4j.SmileRequestRepository; +import org.mskcc.smile.persistence.neo4j.SmileSampleRepository; +import org.mskcc.smile.persistence.neo4j.TempoRepository; +import org.mskcc.smile.service.util.RequestDataFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.data.neo4j.DataNeo4jTest; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; +import org.testcontainers.containers.Neo4jContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +/** + * + * @author ochoaa + */ +@Testcontainers +@DataNeo4jTest +@Import(MockDataUtils.class) +public class TempoServiceTest { + @Autowired + private MockDataUtils mockDataUtils; + + @Autowired + private SmileRequestService requestService; + + @Autowired + private SmileSampleService sampleService; + + @Autowired + private SmilePatientService patientService; + + @Autowired + private CohortCompleteService cohortCompleteService; + + @Autowired + private TempoService tempoService; + + private final ObjectMapper mapper = new ObjectMapper(); + + @Container + private static final Neo4jContainer databaseServer = new Neo4jContainer<>() + .withEnv("NEO4J_dbms_security_procedures_unrestricted", "apoc.*,algo.*"); + + @TestConfiguration + static class Config { + @Bean + public org.neo4j.ogm.config.Configuration configuration() { + return new org.neo4j.ogm.config.Configuration.Builder() + .uri(databaseServer.getBoltUrl()) + .credentials("neo4j", databaseServer.getAdminPassword()) + .build(); + } + } + + private final SmileRequestRepository requestRepository; + private final SmileSampleRepository sampleRepository; + private final SmilePatientRepository patientRepository; + private final TempoRepository tempoRepository; + + /** + * Initializes the Neo4j repositories. + * @param requestRepository + * @param sampleRepository + * @param patientRepository + * @param tempoRepository + */ + @Autowired + public TempoServiceTest(SmileRequestRepository requestRepository, + SmileSampleRepository sampleRepository, SmilePatientRepository patientRepository, + TempoRepository tempoRepository) { + this.requestRepository = requestRepository; + this.sampleRepository = sampleRepository; + this.patientRepository = patientRepository; + this.tempoRepository = tempoRepository; + } + + /** + * Persists the Mock Request data to the test database. + * @throws Exception + */ + @Autowired + public void initializeMockDatabase() throws Exception { + // mock request id: MOCKREQUEST1_B + MockJsonTestData request1Data = mockDataUtils.mockedRequestJsonDataMap + .get("mockIncomingRequest1JsonDataWith2T2N"); + SmileRequest request1 = RequestDataFactory.buildNewLimsRequestFromJson(request1Data.getJsonString()); + requestService.saveRequest(request1); + } + + @Test + public void testBamCompleteEventSave() throws Exception { + Tempo tempo1 = new Tempo(); + tempo1.addBamCompleteEvent(getBamCompleteEventData("mockBamCompleteSampleB1")); + + // get sample from db + String requestId = "MOCKREQUEST1_B"; + String igoId = "MOCKREQUEST1_B_1"; + SmileSample sample1 = sampleService.getResearchSampleByRequestAndIgoId(requestId, igoId); + tempo1.setSmileSample(sample1); // this should link the tempo node to the correct sample node + tempoService.saveTempoData(tempo1); + + // confirm can be fetched in this direction (tempo node to sample) + Tempo tempoAfterSave = tempoService.getTempoDataBySampleId(sample1); + Assertions.assertThat(tempoAfterSave).isNotNull(); + + // confirm can get to tempo data from the sample node as well + SmileSample sample1Updated = sampleService.getResearchSampleByRequestAndIgoId(requestId, igoId); + Tempo sampleTempoUpdated = sample1Updated.getTempo(); + Assertions.assertThat(sampleTempoUpdated).isNotNull(); + } + + @Test + public void testTempoMultipleBamCompleteEvents() throws Exception { + Tempo tempo3 = new Tempo(); + // this bam complete has a FAIL status + tempo3.addBamCompleteEvent(getBamCompleteEventData("mockBamCompleteSampleB3")); + + String requestId = "MOCKREQUEST1_B"; + String igoId = "MOCKREQUEST1_B_3"; + SmileSample sample3 = sampleService.getResearchSampleByRequestAndIgoId(requestId, igoId); + tempo3.setSmileSample(sample3); // this should link the tempo node to the correct sample node + tempoService.saveTempoData(tempo3); + + // confirm can be fetched in this direction (tempo node to sample) + Tempo tempoAfterSave = tempoService.getTempoDataBySampleId(sample3); + Assertions.assertThat(tempoAfterSave).isNotNull(); + Assertions.assertThat(tempoAfterSave.getBamCompleteEvents().size()).isEqualTo(1); + + // mock a new bam complete event for sample, this time with status PASS + tempoAfterSave.addBamCompleteEvent(getBamCompleteEventData("mockBamCompleteSampleB3pass")); + tempoService.saveTempoData(tempo3); // persist new bam complete event + + // fetch updated tempo data for sample - there should be two + // bam complete events after the second update + Tempo tempoAfterSaveAgain = tempoService.getTempoDataBySampleId(sample3); + Assertions.assertThat(tempoAfterSaveAgain.getBamCompleteEvents().size()).isEqualTo(2); + } + + @Test + public void testMafCompleteEventSave() throws Exception { + String igoId = "MOCKREQUEST1_B_1"; + MafComplete mafCompleteB1 = getMafCompleteEventData("mockMafCompleteSampleB1"); + tempoService.mergeMafCompleteEventBySamplePrimaryId(igoId, mafCompleteB1); + // confirming that the query does return the correct amount of specific events + // and distinguishes them from other event types + Tempo tempoAfterSave = tempoService.getTempoDataBySamplePrimaryId(igoId); + Assertions.assertThat(tempoAfterSave.getMafCompleteEvents().size()).isEqualTo(1); + } + + @Test + public void testQcCompleteEventSave() throws Exception { + String igoId = "MOCKREQUEST1_B_1"; + QcComplete qcComplete1 = getQcCompleteEventData("mockQcCompleteSampleB1"); + tempoService.mergeQcCompleteEventBySamplePrimaryId(igoId, qcComplete1); + // confirming that the query does return the correct amount of specific events + // and distinguishes them from other event types + Tempo tempoAfterSave = tempoService.getTempoDataBySamplePrimaryId(igoId); + Assertions.assertThat(tempoAfterSave.getQcCompleteEvents().size()).isEqualTo(1); + } + + @Test + public void testCohortCompleteEventSave() throws Exception { + CohortCompleteJson ccJson = getCohortEventData("mockCohortCompleteCCSPPPQQQQ"); + cohortCompleteService.saveCohort(new Cohort(ccJson), ccJson.getTumorNormalPairsAsSet()); + // cohort should have 4 samples linked to it + Cohort cohort = cohortCompleteService.getCohortByCohortId("CCS_PPPQQQQ"); + Assertions.assertThat(cohort.getCohortSamples().size()).isEqualTo(4); + + // confirm we can get number of cohorts for a sample by primary id + List cohortsBySample = cohortCompleteService.getCohortsBySamplePrimaryId("MOCKREQUEST1_B_1"); + Assertions.assertThat(cohortsBySample.size()).isEqualTo(1); + + // save a new cohort with the same sample as above + CohortCompleteJson ccJson2 = getCohortEventData("mockCohortCompleteCCSPPPQQQQ2"); + cohortCompleteService.saveCohort(new Cohort(ccJson2), ccJson2.getTumorNormalPairsAsSet()); + + // sample should now have 2 cohorts linked to it + List cohortsBySampleUpdated = + cohortCompleteService.getCohortsBySamplePrimaryId("MOCKREQUEST1_B_1"); + Assertions.assertThat(cohortsBySampleUpdated.size()).isEqualTo(2); + } + + @Test + public void testUpdateCohortCompleteData() throws Exception { + CohortCompleteJson ccJson = getCohortEventData("mockCohortCompleteCCSPPPQQQQ"); + cohortCompleteService.saveCohort(new Cohort(ccJson), ccJson.getTumorNormalPairsAsSet()); + Cohort cohort = cohortCompleteService.getCohortByCohortId("CCS_PPPQQQQ"); + + CohortCompleteJson ccJsonUpdate = getCohortEventData("mockCohortCompleteCCSPPPQQQQUpdated"); + + Cohort updatedCohort = new Cohort(ccJsonUpdate); + CohortComplete updatedCohortComplete = updatedCohort.getLatestCohortComplete(); + Boolean hasUpdates = cohortCompleteService.hasUpdates(cohort, updatedCohortComplete); + Assertions.assertThat(hasUpdates).isTrue(); + } + + private CohortCompleteJson getCohortEventData(String dataIdentifier) throws JsonProcessingException { + MockJsonTestData mockData = mockDataUtils.mockedTempoDataMap.get(dataIdentifier); + CohortCompleteJson cohortCompleteData = mapper.readValue(mockData.getJsonString(), + CohortCompleteJson.class); + return cohortCompleteData; + } + + private QcComplete getQcCompleteEventData(String dataIdentifier) throws JsonProcessingException { + MockJsonTestData mockData = mockDataUtils.mockedTempoDataMap.get(dataIdentifier); + Map qcCompleteMap = mapper.readValue(mockData.getJsonString(), Map.class); + QcComplete qcComplete = new QcComplete(qcCompleteMap.get("date"), + qcCompleteMap.get("result"), qcCompleteMap.get("reason"), + qcCompleteMap.get("status")); + return qcComplete; + } + + private MafComplete getMafCompleteEventData(String dataIdentifier) throws JsonProcessingException { + MockJsonTestData mockData = mockDataUtils.mockedTempoDataMap.get(dataIdentifier); + Map mafCompleteMap = mapper.readValue(mockData.getJsonString(), Map.class); + MafComplete mafComplete = new MafComplete(mafCompleteMap.get("date"), + mafCompleteMap.get("normalPrimaryId"), + mafCompleteMap.get("status")); + return mafComplete; + } + + private BamComplete getBamCompleteEventData(String dataIdentifier) throws JsonProcessingException { + MockJsonTestData mockData = mockDataUtils.mockedTempoDataMap.get(dataIdentifier); + Map bamCompleteMap = mapper.readValue(mockData.getJsonString(), Map.class); + return new BamComplete(bamCompleteMap.get("date"), bamCompleteMap.get("status")); + } +} diff --git a/service/src/test/resources/data/tempo/bam_complete_MOCKREQUEST1_B_1.json b/service/src/test/resources/data/tempo/bam_complete_MOCKREQUEST1_B_1.json new file mode 100644 index 00000000..9d6ec21d --- /dev/null +++ b/service/src/test/resources/data/tempo/bam_complete_MOCKREQUEST1_B_1.json @@ -0,0 +1,5 @@ +{ + "primaryId": "MOCKREQUEST1_B_1", + "date": "2023-12-25 14:20", + "status": "PASS" +} diff --git a/service/src/test/resources/data/tempo/bam_complete_MOCKREQUEST1_B_2.json b/service/src/test/resources/data/tempo/bam_complete_MOCKREQUEST1_B_2.json new file mode 100644 index 00000000..cc6fdac2 --- /dev/null +++ b/service/src/test/resources/data/tempo/bam_complete_MOCKREQUEST1_B_2.json @@ -0,0 +1,5 @@ +{ + "primaryId": "MOCKREQUEST1_B_2", + "date": "2023-12-25 16:11", + "status": "PASS" +} diff --git a/service/src/test/resources/data/tempo/bam_complete_MOCKREQUEST1_B_3.json b/service/src/test/resources/data/tempo/bam_complete_MOCKREQUEST1_B_3.json new file mode 100644 index 00000000..a52e2e43 --- /dev/null +++ b/service/src/test/resources/data/tempo/bam_complete_MOCKREQUEST1_B_3.json @@ -0,0 +1,5 @@ +{ + "primaryId": "MOCKREQUEST1_B_3", + "date": "2023-12-25 13:44", + "status": "FAIL" +} diff --git a/service/src/test/resources/data/tempo/bam_complete_MOCKREQUEST1_B_3_pass.json b/service/src/test/resources/data/tempo/bam_complete_MOCKREQUEST1_B_3_pass.json new file mode 100644 index 00000000..d032b8e2 --- /dev/null +++ b/service/src/test/resources/data/tempo/bam_complete_MOCKREQUEST1_B_3_pass.json @@ -0,0 +1,5 @@ +{ + "primaryId": "MOCKREQUEST1_B_3", + "date": "2023-12-30 06:20", + "status": "PASS" +} diff --git a/service/src/test/resources/data/tempo/bam_complete_MOCKREQUEST1_B_4.json b/service/src/test/resources/data/tempo/bam_complete_MOCKREQUEST1_B_4.json new file mode 100644 index 00000000..c57d70d9 --- /dev/null +++ b/service/src/test/resources/data/tempo/bam_complete_MOCKREQUEST1_B_4.json @@ -0,0 +1,5 @@ +{ + "primaryId": "MOCKREQUEST1_B_4", + "date": "2023-12-25 11:33", + "status": "PASS" +} diff --git a/service/src/test/resources/data/tempo/cohort_complete_CCS_PPPQQQQ.json b/service/src/test/resources/data/tempo/cohort_complete_CCS_PPPQQQQ.json new file mode 100644 index 00000000..9087b7f7 --- /dev/null +++ b/service/src/test/resources/data/tempo/cohort_complete_CCS_PPPQQQQ.json @@ -0,0 +1,26 @@ +{ + "cohortId": "CCS_PPPQQQQ", + "date": "2022-11-12 21:59", + "type": "investigator", + "endUsers": [ + "enduser1", + "enduser2" + ], + "pmUsers": [ + "pmuser1", + "pmuser2" + ], + "projectTitle": "A title", + "projectSubtitle": "A longer description", + "samples": [ + { + "primaryId": "MOCKREQUEST1_B_1", + "normalPrimaryId": "MOCKREQUEST1_B_2" + }, + { + "primaryId": "MOCKREQUEST1_B_3", + "normalPrimaryId": "MOCKREQUEST1_B_4" + } + ], + "status": "PASS" +} \ No newline at end of file diff --git a/service/src/test/resources/data/tempo/cohort_complete_CCS_PPPQQQQ_2.json b/service/src/test/resources/data/tempo/cohort_complete_CCS_PPPQQQQ_2.json new file mode 100644 index 00000000..03773ad8 --- /dev/null +++ b/service/src/test/resources/data/tempo/cohort_complete_CCS_PPPQQQQ_2.json @@ -0,0 +1,22 @@ +{ + "cohortId": "CCS_PPPQQQQ_2", + "date": "2022-11-12 21:59", + "type": "investigator", + "endUsers": [ + "enduser1", + "enduser2" + ], + "pmUsers": [ + "pmuser1", + "pmuser2" + ], + "projectTitle": "A title", + "projectSubtitle": "A longer description", + "samples": [ + { + "primaryId": "MOCKREQUEST1_B_1", + "normalPrimaryId": "MOCKREQUEST1_B_2" + } + ], + "status": "PASS" +} diff --git a/service/src/test/resources/data/tempo/cohort_complete_CCS_PPPQQQQ_updated.json b/service/src/test/resources/data/tempo/cohort_complete_CCS_PPPQQQQ_updated.json new file mode 100644 index 00000000..c4315f04 --- /dev/null +++ b/service/src/test/resources/data/tempo/cohort_complete_CCS_PPPQQQQ_updated.json @@ -0,0 +1,28 @@ +{ + "cohortId": "CCS_PPPQQQQ", + "date": "2022-11-12 21:59", + "type": "investigator", + "endUsers": [ + "enduser1", + "enduser2", + "enduser3" + ], + "pmUsers": [ + "pmuser1", + "pmuser2", + "pmuser3" + ], + "projectTitle": "A title", + "projectSubtitle": "A longer description", + "samples": [ + { + "primaryId": "MOCKREQUEST1_B_1", + "normalPrimaryId": "MOCKREQUEST1_B_2" + }, + { + "primaryId": "MOCKREQUEST1_B_3", + "normalPrimaryId": "MOCKREQUEST1_B_4" + } + ], + "status": "PASS" +} diff --git a/service/src/test/resources/data/tempo/maf_complete_MOCKREQUEST1_B_1.json b/service/src/test/resources/data/tempo/maf_complete_MOCKREQUEST1_B_1.json new file mode 100644 index 00000000..754910c7 --- /dev/null +++ b/service/src/test/resources/data/tempo/maf_complete_MOCKREQUEST1_B_1.json @@ -0,0 +1,6 @@ +{ + "primaryId": "MOCKREQUEST1_B_1", + "date": "2022-10-31 23:15", + "normalPrimaryId": "12345_A_4", + "status":"PASS" +} \ No newline at end of file diff --git a/service/src/test/resources/data/tempo/maf_complete_MOCKREQUEST1_B_2.json b/service/src/test/resources/data/tempo/maf_complete_MOCKREQUEST1_B_2.json new file mode 100644 index 00000000..ace1bf2f --- /dev/null +++ b/service/src/test/resources/data/tempo/maf_complete_MOCKREQUEST1_B_2.json @@ -0,0 +1,6 @@ +{ + "primaryId": "MOCKREQUEST1_B_2", + "date": "2022-10-31 23:20", + "normalPrimaryId": "12345_A_5", + "status":"PASS" +} \ No newline at end of file diff --git a/service/src/test/resources/data/tempo/maf_complete_MOCKREQUEST1_B_3.json b/service/src/test/resources/data/tempo/maf_complete_MOCKREQUEST1_B_3.json new file mode 100644 index 00000000..500188ff --- /dev/null +++ b/service/src/test/resources/data/tempo/maf_complete_MOCKREQUEST1_B_3.json @@ -0,0 +1,6 @@ +{ + "primaryId": "MOCKREQUEST1_B_3", + "date": "2022-10-31 23:25", + "normalPrimaryId": "12345_A_6", + "status":"FAIL" +} \ No newline at end of file diff --git a/service/src/test/resources/data/tempo/maf_complete_MOCKREQUEST1_B_3_pass.json b/service/src/test/resources/data/tempo/maf_complete_MOCKREQUEST1_B_3_pass.json new file mode 100644 index 00000000..a0ed00e2 --- /dev/null +++ b/service/src/test/resources/data/tempo/maf_complete_MOCKREQUEST1_B_3_pass.json @@ -0,0 +1,6 @@ +{ + "primaryId": "MOCKREQUEST1_B_3", + "date": "2022-10-31 23:25", + "normalPrimaryId": "12345_A_6", + "status":"PASS" +} \ No newline at end of file diff --git a/service/src/test/resources/data/tempo/maf_complete_MOCKREQUEST1_B_4.json b/service/src/test/resources/data/tempo/maf_complete_MOCKREQUEST1_B_4.json new file mode 100644 index 00000000..5f999040 --- /dev/null +++ b/service/src/test/resources/data/tempo/maf_complete_MOCKREQUEST1_B_4.json @@ -0,0 +1,6 @@ +{ + "primaryId": "MOCKREQUEST1_B_4", + "date": "2022-10-31 23:30", + "normalPrimaryId": "12345_A_7", + "status":"PASS" +} \ No newline at end of file diff --git a/service/src/test/resources/data/tempo/mocked_tempo_data_details.txt b/service/src/test/resources/data/tempo/mocked_tempo_data_details.txt new file mode 100644 index 00000000..248cc9ca --- /dev/null +++ b/service/src/test/resources/data/tempo/mocked_tempo_data_details.txt @@ -0,0 +1,18 @@ +identifier filepath description +mockBamCompleteSampleB1 tempo/bam_complete_MOCKREQUEST1_B_1.json Mocked BAM complete JSON for sample MOCKREQUEST1_B_1 +mockBamCompleteSampleB2 tempo/bam_complete_MOCKREQUEST1_B_2.json Mocked BAM complete JSON for sample MOCKREQUEST1_B_2 +mockBamCompleteSampleB3 tempo/bam_complete_MOCKREQUEST1_B_3.json Mocked BAM complete JSON for sample MOCKREQUEST1_B_3 with status FAIL +mockBamCompleteSampleB4 tempo/bam_complete_MOCKREQUEST1_B_4.json Mocked BAM complete JSON for sample MOCKREQUEST1_B_4 +mockBamCompleteSampleB3pass tempo/bam_complete_MOCKREQUEST1_B_3_pass.json Mocked BAM complete JSON for sample MOCKREQUEST1_B_3 with status PASS +mockQcCompleteSampleB1 tempo/qc_complete_MOCKREQUEST1_B_1.json Mocked QC complete JSON for sample MOCKREQUEST1_B_1 +mockQcCompleteSampleB2 tempo/qc_complete_MOCKREQUEST1_B_2.json Mocked QC complete JSON for sample MOCKREQUEST1_B_2 +mockQcCompleteSampleB3 tempo/qc_complete_MOCKREQUEST1_B_3.json Mocked QC complete JSON for sample MOCKREQUEST1_B_3 +mockQcCompleteSampleB4 tempo/qc_complete_MOCKREQUEST1_B_4.json Mocked QC complete JSON for sample MOCKREQUEST1_B_4 +mockMafCompleteSampleB1 tempo/maf_complete_MOCKREQUEST1_B_1.json Mocked MAF complete JSON for sample MOCKREQUEST1_B_1 +mockMafCompleteSampleB2 tempo/maf_complete_MOCKREQUEST1_B_2.json Mocked MAF complete JSON for sample MOCKREQUEST1_B_2 +mockMafCompleteSampleB3 tempo/maf_complete_MOCKREQUEST1_B_3.json Mocked MAF complete JSON for sample MOCKREQUEST1_B_3 with status FAIL +mockMafCompleteSampleB4 tempo/maf_complete_MOCKREQUEST1_B_4.json Mocked MAF complete JSON for sample MOCKREQUEST1_B_4 +mockMafCompleteSampleB3pass tempo/maf_complete_MOCKREQUEST1_B_3_pass.json Mocked MAF complete JSON for sample MOCKREQUEST1_B_3 with status PASS +mockCohortCompleteCCSPPPQQQQ tempo/cohort_complete_CCS_PPPQQQQ.json Mocked Cohort complete JSON with cohort ID CCS_PPPQQQQ. Has 4 samples associated with it. +mockCohortCompleteCCSPPPQQQQ2 tempo/cohort_complete_CCS_PPPQQQQ_2.json Mocked Cohort complete JSON with cohort ID CCS_PPPQQQQ_2. Has 2 samples associated with it. +mockCohortCompleteCCSPPPQQQQUpdated tempo/cohort_complete_CCS_PPPQQQQ_updated.json Mocked updated Cohort complete JSON with cohort ID CCS_PPPQQQQ. Has 4 samples associated with it. diff --git a/service/src/test/resources/data/tempo/qc_complete_MOCKREQUEST1_B_1.json b/service/src/test/resources/data/tempo/qc_complete_MOCKREQUEST1_B_1.json new file mode 100644 index 00000000..45952646 --- /dev/null +++ b/service/src/test/resources/data/tempo/qc_complete_MOCKREQUEST1_B_1.json @@ -0,0 +1,7 @@ +{ + "primaryId": "MOCKREQUEST1_B_1", + "date": "2022-10-30 16:15", + "result": "warn", + "reason": "Contamination", + "status": "PASS" +} diff --git a/service/src/test/resources/data/tempo/qc_complete_MOCKREQUEST1_B_2.json b/service/src/test/resources/data/tempo/qc_complete_MOCKREQUEST1_B_2.json new file mode 100644 index 00000000..14d6e241 --- /dev/null +++ b/service/src/test/resources/data/tempo/qc_complete_MOCKREQUEST1_B_2.json @@ -0,0 +1,7 @@ +{ + "primaryId": "MOCKREQUEST1_B_2", + "date": "2022-10-30 16:15", + "result": "warn", + "reason": "Contamination", + "status": "PASS" +} diff --git a/service/src/test/resources/data/tempo/qc_complete_MOCKREQUEST1_B_3.json b/service/src/test/resources/data/tempo/qc_complete_MOCKREQUEST1_B_3.json new file mode 100644 index 00000000..c776a1e5 --- /dev/null +++ b/service/src/test/resources/data/tempo/qc_complete_MOCKREQUEST1_B_3.json @@ -0,0 +1,7 @@ +{ + "primaryId": "MOCKREQUEST1_B_3", + "date": "2022-10-30 16:15", + "result": "warn", + "reason": "Contamination", + "status": "PASS" +} diff --git a/service/src/test/resources/data/tempo/qc_complete_MOCKREQUEST1_B_4.json b/service/src/test/resources/data/tempo/qc_complete_MOCKREQUEST1_B_4.json new file mode 100644 index 00000000..125111be --- /dev/null +++ b/service/src/test/resources/data/tempo/qc_complete_MOCKREQUEST1_B_4.json @@ -0,0 +1,7 @@ +{ + "primaryId": "MOCKREQUEST1_B_4", + "date": "2022-10-30 16:15", + "result": "warn", + "reason": "Contamination", + "status": "PASS" +} diff --git a/src/main/resources/application.properties.EXAMPLE b/src/main/resources/application.properties.EXAMPLE index 7540ee98..21578112 100644 --- a/src/main/resources/application.properties.EXAMPLE +++ b/src/main/resources/application.properties.EXAMPLE @@ -75,3 +75,10 @@ smile.publishing_failures_filepath= # request handling failure filepath smile.request_handling_failures_filepath= + +# tempo topics +num.tempo_msg_handler_threads= +tempo.wes_bam_complete_topic= +tempo.wes_qc_complete_topic= +tempo.wes_maf_complete_topic= +tempo.wes_cohort_complete_topic=