diff --git a/docs/sequencingModule.drawio b/docs/sequencingModule.drawio index a85395de..a3df300d 100644 --- a/docs/sequencingModule.drawio +++ b/docs/sequencingModule.drawio @@ -1,4 +1,4 @@ - + @@ -255,10 +255,10 @@ - + - + @@ -542,6 +542,28 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/sequencingModule.drawio.png b/docs/sequencingModule.drawio.png index 935d0f7b..78c78166 100644 Binary files a/docs/sequencingModule.drawio.png and b/docs/sequencingModule.drawio.png differ diff --git a/pom.xml b/pom.xml index 94a8cf8b..910b61bb 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ ca.gc.aafc seqdb.api - 2.17.0 + 2.18.0 jar seqdb-api diff --git a/src/main/java/ca/gc/aafc/seqdb/api/ResourceRepositoryConfig.java b/src/main/java/ca/gc/aafc/seqdb/api/ResourceRepositoryConfig.java index 4a6ebaca..f080ac23 100644 --- a/src/main/java/ca/gc/aafc/seqdb/api/ResourceRepositoryConfig.java +++ b/src/main/java/ca/gc/aafc/seqdb/api/ResourceRepositoryConfig.java @@ -1,6 +1,12 @@ package ca.gc.aafc.seqdb.api; +import io.crnk.core.engine.registry.ResourceRegistry; +import javax.inject.Inject; + import ca.gc.aafc.dina.DinaBaseApiAutoConfiguration; +import ca.gc.aafc.seqdb.api.dto.SequenceManagedAttributeDto; +import ca.gc.aafc.seqdb.api.util.ManagedAttributeIdMapper; + import org.springframework.boot.autoconfigure.domain.EntityScan; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; @@ -11,4 +17,14 @@ @EntityScan("ca.gc.aafc.seqdb.api.entities") @ComponentScan(basePackageClasses = DinaBaseApiAutoConfiguration.class) public class ResourceRepositoryConfig { + + @Inject + @SuppressWarnings({"deprecation", "unchecked"}) + public void setupManagedAttributeLookup(ResourceRegistry resourceRegistry) { + var resourceInfo = resourceRegistry.getEntry(SequenceManagedAttributeDto.class) + .getResourceInformation(); + + resourceInfo.setIdStringMapper( + new ManagedAttributeIdMapper(resourceInfo.getIdStringMapper())); + } } diff --git a/src/main/java/ca/gc/aafc/seqdb/api/SequenceVocabularyConfiguration.java b/src/main/java/ca/gc/aafc/seqdb/api/SequenceVocabularyConfiguration.java index 9ee6b2d5..92da6aa5 100644 --- a/src/main/java/ca/gc/aafc/seqdb/api/SequenceVocabularyConfiguration.java +++ b/src/main/java/ca/gc/aafc/seqdb/api/SequenceVocabularyConfiguration.java @@ -16,6 +16,7 @@ @PropertySource(value = "classpath:vocabulary/pcrBatchType.yml", factory = YamlPropertyLoaderFactory.class) @PropertySource(value = "classpath:vocabulary/sequencingType.yml", factory = YamlPropertyLoaderFactory.class) @PropertySource(value = "classpath:vocabulary/molecularAnalysisType.yml", factory = YamlPropertyLoaderFactory.class) +@PropertySource(value = "classpath:vocabulary/qualityControlType.yml", factory = YamlPropertyLoaderFactory.class) @ConfigurationProperties @Validated public class SequenceVocabularyConfiguration extends VocabularyConfiguration { @@ -23,6 +24,7 @@ public class SequenceVocabularyConfiguration extends VocabularyConfiguration> vocabulary) { super(vocabulary); diff --git a/src/main/java/ca/gc/aafc/seqdb/api/dto/GenericMolecularAnalysisDto.java b/src/main/java/ca/gc/aafc/seqdb/api/dto/GenericMolecularAnalysisDto.java index b5580ac9..81b3c0f1 100644 --- a/src/main/java/ca/gc/aafc/seqdb/api/dto/GenericMolecularAnalysisDto.java +++ b/src/main/java/ca/gc/aafc/seqdb/api/dto/GenericMolecularAnalysisDto.java @@ -1,9 +1,12 @@ package ca.gc.aafc.seqdb.api.dto; +import io.crnk.core.resource.annotations.JsonApiField; import io.crnk.core.resource.annotations.JsonApiId; import io.crnk.core.resource.annotations.JsonApiRelation; import io.crnk.core.resource.annotations.JsonApiResource; +import io.crnk.core.resource.annotations.PatchStrategy; import java.time.OffsetDateTime; +import java.util.Map; import java.util.UUID; import lombok.AllArgsConstructor; import lombok.Builder; @@ -41,4 +44,11 @@ public class GenericMolecularAnalysisDto { @JsonApiRelation private ExternalRelationDto protocol; + /** + * Map of Managed attribute key to value object. + */ + @JsonApiField(patchStrategy = PatchStrategy.SET) + @Builder.Default + private Map managedAttributes = Map.of(); + } diff --git a/src/main/java/ca/gc/aafc/seqdb/api/dto/MetagenomicsBatchDto.java b/src/main/java/ca/gc/aafc/seqdb/api/dto/MetagenomicsBatchDto.java new file mode 100644 index 00000000..d89477bb --- /dev/null +++ b/src/main/java/ca/gc/aafc/seqdb/api/dto/MetagenomicsBatchDto.java @@ -0,0 +1,45 @@ +package ca.gc.aafc.seqdb.api.dto; + +import ca.gc.aafc.dina.dto.ExternalRelationDto; +import ca.gc.aafc.dina.dto.RelatedEntity; +import ca.gc.aafc.dina.repository.meta.JsonApiExternalRelation; +import ca.gc.aafc.seqdb.api.entities.MetagenomicsBatch; + +import io.crnk.core.resource.annotations.JsonApiId; +import io.crnk.core.resource.annotations.JsonApiRelation; +import io.crnk.core.resource.annotations.JsonApiResource; +import java.time.OffsetDateTime; +import java.util.UUID; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Builder +@Data +@NoArgsConstructor +@AllArgsConstructor +@JsonApiResource(type = MetagenomicsBatchDto.TYPENAME) +@RelatedEntity(MetagenomicsBatch.class) +public class MetagenomicsBatchDto { + + public static final String TYPENAME = "metagenomics-batch"; + + @JsonApiId + private UUID uuid; + + private String createdBy; + + private OffsetDateTime createdOn; + private String group; + + private String name; + + @JsonApiExternalRelation(type = "protocol") + @JsonApiRelation + private ExternalRelationDto protocol; + + @JsonApiRelation + private IndexSetDto indexSet; + +} diff --git a/src/main/java/ca/gc/aafc/seqdb/api/dto/MetagenomicsBatchItemDto.java b/src/main/java/ca/gc/aafc/seqdb/api/dto/MetagenomicsBatchItemDto.java new file mode 100644 index 00000000..f398075c --- /dev/null +++ b/src/main/java/ca/gc/aafc/seqdb/api/dto/MetagenomicsBatchItemDto.java @@ -0,0 +1,53 @@ +package ca.gc.aafc.seqdb.api.dto; + +import ca.gc.aafc.dina.dto.RelatedEntity; +import ca.gc.aafc.seqdb.api.dto.pcr.PcrBatchItemDto; +import ca.gc.aafc.seqdb.api.entities.MetagenomicsBatchItem; + +import io.crnk.core.resource.annotations.JsonApiId; +import io.crnk.core.resource.annotations.JsonApiRelation; +import io.crnk.core.resource.annotations.JsonApiResource; +import java.time.OffsetDateTime; +import java.util.UUID; + +import org.javers.core.metamodel.annotation.ShallowReference; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Builder +@Data +@NoArgsConstructor +@AllArgsConstructor +@JsonApiResource(type = MetagenomicsBatchItemDto.TYPENAME) +@RelatedEntity(MetagenomicsBatchItem.class) +public class MetagenomicsBatchItemDto { + + public static final String TYPENAME = "metagenomics-batch-item"; + + @JsonApiId + private UUID uuid; + + private String createdBy; + + private OffsetDateTime createdOn; + + @JsonApiRelation + private MetagenomicsBatchDto metagenomicsBatch; + + @JsonApiRelation + private PcrBatchItemDto pcrBatchItem; + + @ShallowReference + @JsonApiRelation + private MolecularAnalysisRunItemDto molecularAnalysisRunItem; + + @JsonApiRelation + private NgsIndexDto indexI5; + + @JsonApiRelation + private NgsIndexDto indexI7; + +} diff --git a/src/main/java/ca/gc/aafc/seqdb/api/dto/QualityControlDto.java b/src/main/java/ca/gc/aafc/seqdb/api/dto/QualityControlDto.java new file mode 100644 index 00000000..ac2f07c4 --- /dev/null +++ b/src/main/java/ca/gc/aafc/seqdb/api/dto/QualityControlDto.java @@ -0,0 +1,48 @@ +package ca.gc.aafc.seqdb.api.dto; + +import io.crnk.core.resource.annotations.JsonApiId; +import io.crnk.core.resource.annotations.JsonApiRelation; +import io.crnk.core.resource.annotations.JsonApiResource; +import java.time.OffsetDateTime; +import java.util.UUID; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import org.javers.core.metamodel.annotation.Id; +import org.javers.core.metamodel.annotation.PropertyName; +import org.javers.core.metamodel.annotation.TypeName; + +import ca.gc.aafc.dina.dto.RelatedEntity; +import ca.gc.aafc.seqdb.api.entities.QualityControl; + +@Builder +@Data +@NoArgsConstructor +@AllArgsConstructor +@JsonApiResource(type = QualityControlDto.TYPENAME) +@TypeName(QualityControlDto.TYPENAME) +@RelatedEntity(QualityControl.class) +public class QualityControlDto { + + public static final String TYPENAME = "quality-control"; + + @JsonApiId + @Id + @PropertyName("id") + private UUID uuid; + + private String createdBy; + private OffsetDateTime createdOn; + + private String group; + + private String name; + + private String qcType; + + @JsonApiRelation + private MolecularAnalysisRunItemDto molecularAnalysisRunItem; + +} diff --git a/src/main/java/ca/gc/aafc/seqdb/api/dto/SequenceManagedAttributeDto.java b/src/main/java/ca/gc/aafc/seqdb/api/dto/SequenceManagedAttributeDto.java new file mode 100644 index 00000000..6d4f83ca --- /dev/null +++ b/src/main/java/ca/gc/aafc/seqdb/api/dto/SequenceManagedAttributeDto.java @@ -0,0 +1,41 @@ +package ca.gc.aafc.seqdb.api.dto; + +import ca.gc.aafc.dina.dto.RelatedEntity; +import ca.gc.aafc.dina.i18n.MultilingualDescription; +import ca.gc.aafc.dina.vocabulary.TypedVocabularyElement; +import ca.gc.aafc.seqdb.api.entities.SequenceManagedAttribute; + +import io.crnk.core.resource.annotations.JsonApiId; +import io.crnk.core.resource.annotations.JsonApiResource; +import lombok.Data; + +import java.time.OffsetDateTime; +import java.util.UUID; + +import org.javers.core.metamodel.annotation.Id; +import org.javers.core.metamodel.annotation.PropertyName; +import org.javers.core.metamodel.annotation.TypeName; + +@RelatedEntity(SequenceManagedAttribute.class) +@Data +@JsonApiResource(type = SequenceManagedAttributeDto.TYPENAME) +@TypeName(SequenceManagedAttributeDto.TYPENAME) +public class SequenceManagedAttributeDto { + public static final String TYPENAME = "managed-attribute"; + + @JsonApiId + @Id + @PropertyName("id") + private UUID uuid; + private String name; + private String key; + private TypedVocabularyElement.VocabularyElementType vocabularyElementType; + private String unit; + private SequenceManagedAttribute.ManagedAttributeComponent managedAttributeComponent; + private String[] acceptedValues; + private OffsetDateTime createdOn; + private String createdBy; + private String group; + private MultilingualDescription multilingualDescription; + +} diff --git a/src/main/java/ca/gc/aafc/seqdb/api/entities/GenericMolecularAnalysis.java b/src/main/java/ca/gc/aafc/seqdb/api/entities/GenericMolecularAnalysis.java index 4686a85d..fc4a933b 100644 --- a/src/main/java/ca/gc/aafc/seqdb/api/entities/GenericMolecularAnalysis.java +++ b/src/main/java/ca/gc/aafc/seqdb/api/entities/GenericMolecularAnalysis.java @@ -1,6 +1,7 @@ package ca.gc.aafc.seqdb.api.entities; import java.time.OffsetDateTime; +import java.util.Map; import java.util.UUID; import javax.persistence.Column; import javax.persistence.Entity; @@ -20,6 +21,7 @@ import org.hibernate.annotations.Generated; import org.hibernate.annotations.GenerationTime; import org.hibernate.annotations.NaturalId; +import org.hibernate.annotations.Type; import ca.gc.aafc.dina.entity.DinaEntity; @@ -61,4 +63,10 @@ public class GenericMolecularAnalysis implements DinaEntity { @Size(max = 50) private String analysisType; + @Type(type = "jsonb") + @NotNull + @Builder.Default + @Column(name = "managed_attributes") + private Map managedAttributes = Map.of(); + } diff --git a/src/main/java/ca/gc/aafc/seqdb/api/entities/MetagenomicsBatch.java b/src/main/java/ca/gc/aafc/seqdb/api/entities/MetagenomicsBatch.java new file mode 100644 index 00000000..eb2a175f --- /dev/null +++ b/src/main/java/ca/gc/aafc/seqdb/api/entities/MetagenomicsBatch.java @@ -0,0 +1,68 @@ +package ca.gc.aafc.seqdb.api.entities; + +import org.hibernate.annotations.Generated; +import org.hibernate.annotations.GenerationTime; +import org.hibernate.annotations.NaturalId; + +import ca.gc.aafc.dina.entity.DinaEntity; +import ca.gc.aafc.seqdb.api.entities.libraryprep.IndexSet; + +import java.time.OffsetDateTime; +import java.util.UUID; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Entity +@Builder +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Table(name = "metagenomics_batch") +public class MetagenomicsBatch implements DinaEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer id; + + @NotNull + @NaturalId + private UUID uuid; + + @NotBlank + @Column(name = "created_by", updatable = false) + private String createdBy; + + @Column(name = "created_on", insertable = false, updatable = false) + @Generated(value = GenerationTime.INSERT) + private OffsetDateTime createdOn; + + @Column(name = "_group") + private String group; + + @NotBlank + @Size(max = 100) + private String name; + + private UUID protocol; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "index_set_id") + private IndexSet indexSet; + +} diff --git a/src/main/java/ca/gc/aafc/seqdb/api/entities/MetagenomicsBatchItem.java b/src/main/java/ca/gc/aafc/seqdb/api/entities/MetagenomicsBatchItem.java new file mode 100644 index 00000000..f28fc972 --- /dev/null +++ b/src/main/java/ca/gc/aafc/seqdb/api/entities/MetagenomicsBatchItem.java @@ -0,0 +1,84 @@ +package ca.gc.aafc.seqdb.api.entities; + +import org.hibernate.annotations.Generated; +import org.hibernate.annotations.GenerationTime; +import org.hibernate.annotations.NaturalId; + +import ca.gc.aafc.dina.entity.DinaEntity; +import ca.gc.aafc.seqdb.api.entities.libraryprep.NgsIndex; +import ca.gc.aafc.seqdb.api.entities.pcr.PcrBatchItem; + +import java.time.OffsetDateTime; +import java.util.UUID; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Entity +@Builder +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Table(name = "metagenomics_batch_item") +public class MetagenomicsBatchItem implements DinaEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer id; + + @NotNull + @NaturalId + private UUID uuid; + + @NotBlank + @Column(name = "created_by", updatable = false) + private String createdBy; + + @Column(name = "created_on", insertable = false, updatable = false) + @Generated(value = GenerationTime.INSERT) + private OffsetDateTime createdOn; + + // eager since we need it for group-based permission + @ManyToOne(fetch = FetchType.EAGER) + @JoinColumn(name = "metagenomics_batch_id") + private MetagenomicsBatch metagenomicsBatch; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "pcr_batch_item_id") + private PcrBatchItem pcrBatchItem; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "molecular_analysis_run_item_id") + private MolecularAnalysisRunItem molecularAnalysisRunItem; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "indexi5_id") + private NgsIndex indexI5; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "indexi7_id") + private NgsIndex indexI7; + + @Override + public String getGroup() { + if (metagenomicsBatch == null) { + return null; + } + return metagenomicsBatch.getGroup(); + } + +} diff --git a/src/main/java/ca/gc/aafc/seqdb/api/entities/QualityControl.java b/src/main/java/ca/gc/aafc/seqdb/api/entities/QualityControl.java new file mode 100644 index 00000000..9932671c --- /dev/null +++ b/src/main/java/ca/gc/aafc/seqdb/api/entities/QualityControl.java @@ -0,0 +1,70 @@ +package ca.gc.aafc.seqdb.api.entities; + +import java.time.OffsetDateTime; +import java.util.UUID; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import org.hibernate.annotations.Generated; +import org.hibernate.annotations.GenerationTime; +import org.hibernate.annotations.NaturalId; + +import ca.gc.aafc.dina.entity.DinaEntity; + +@Getter +@Entity +@Builder +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Table(name = "quality_control") +public class QualityControl implements DinaEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer id; + + @NotNull + @NaturalId + private UUID uuid; + + @NotBlank + @Column(name = "created_by", updatable = false) + private String createdBy; + + @Column(name = "created_on", insertable = false, updatable = false) + @Generated(value = GenerationTime.INSERT) + private OffsetDateTime createdOn; + + @Column(name = "_group") + private String group; + + @NotBlank + @Size(max = 100) + private String name; + + @NotBlank + @Size(max = 50) + @Column(name = "qc_type") + private String qcType; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "molecular_analysis_run_item_id") + private MolecularAnalysisRunItem molecularAnalysisRunItem; + +} diff --git a/src/main/java/ca/gc/aafc/seqdb/api/entities/SequenceManagedAttribute.java b/src/main/java/ca/gc/aafc/seqdb/api/entities/SequenceManagedAttribute.java new file mode 100644 index 00000000..8db2517b --- /dev/null +++ b/src/main/java/ca/gc/aafc/seqdb/api/entities/SequenceManagedAttribute.java @@ -0,0 +1,118 @@ +package ca.gc.aafc.seqdb.api.entities; + +import java.time.OffsetDateTime; +import java.util.UUID; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.validation.Valid; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.RequiredArgsConstructor; +import lombok.Setter; +import lombok.Getter; + +import org.hibernate.annotations.Generated; +import org.hibernate.annotations.GenerationTime; +import org.hibernate.annotations.NaturalId; +import org.hibernate.annotations.NaturalIdCache; +import org.hibernate.annotations.Type; + +import ca.gc.aafc.dina.entity.DinaEntityIdentifiableByName; +import ca.gc.aafc.dina.entity.ManagedAttribute; +import ca.gc.aafc.dina.i18n.MultilingualDescription; +import ca.gc.aafc.dina.i18n.MultilingualTitle; + +@Entity(name = "managed_attribute") +@Getter +@Setter +@Builder +@AllArgsConstructor +@RequiredArgsConstructor +@NaturalIdCache +public class SequenceManagedAttribute implements ManagedAttribute, DinaEntityIdentifiableByName { + + public enum ManagedAttributeComponent { + GENERIC_MOLECULAR_ANALYSIS; + + public static ManagedAttributeComponent fromString(String s) { + for (ManagedAttributeComponent source : ManagedAttributeComponent.values()) { + if (source.name().equalsIgnoreCase(s)) { + return source; + } + } + return null; + } + } + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer id; + + @NaturalId + @NotNull + @Column(name = "uuid", unique = true) + private UUID uuid; + + @Column(name = "created_on", insertable = false, updatable = false) + @Generated(value = GenerationTime.INSERT) + private OffsetDateTime createdOn; + + @NotBlank + @Column(name = "created_by", updatable = false) + private String createdBy; + + @NotBlank + private String name; + + @Type(type = "jsonb") + @Column(name = "multilingual_description") + @Valid + private MultilingualDescription multilingualDescription; + + @NotBlank + @Column(name = "_group") + @Size(max = 50) + private String group; + + @NotBlank + @Size(max = 50) + @Column(updatable = false) + private String key; + + @NotNull + @Type(type = "pgsql_enum") + @Enumerated(EnumType.STRING) + @Column(name = "type") + private VocabularyElementType vocabularyElementType; + + @NotNull + @Enumerated(EnumType.STRING) + @Column(name = "component") + private ManagedAttributeComponent managedAttributeComponent; + + @Type(type = "string-array") + @Column(name = "accepted_values", columnDefinition = "text[]") + private String[] acceptedValues; + + @Size(max = 50) + private String unit; + + @Override + public String getTerm() { + return null; + } + + @Override + public MultilingualTitle getMultilingualTitle() { + return null; + } + +} diff --git a/src/main/java/ca/gc/aafc/seqdb/api/repository/MetagenomicsBatchItemRepository.java b/src/main/java/ca/gc/aafc/seqdb/api/repository/MetagenomicsBatchItemRepository.java new file mode 100644 index 00000000..37c2be3f --- /dev/null +++ b/src/main/java/ca/gc/aafc/seqdb/api/repository/MetagenomicsBatchItemRepository.java @@ -0,0 +1,51 @@ +package ca.gc.aafc.seqdb.api.repository; + +import org.springframework.boot.info.BuildProperties; +import org.springframework.stereotype.Repository; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import ca.gc.aafc.dina.mapper.DinaMapper; +import ca.gc.aafc.dina.repository.DinaRepository; +import ca.gc.aafc.dina.repository.external.ExternalResourceProvider; +import ca.gc.aafc.dina.security.DinaAuthenticatedUser; +import ca.gc.aafc.dina.security.auth.DinaAuthorizationService; +import ca.gc.aafc.seqdb.api.dto.MetagenomicsBatchItemDto; +import ca.gc.aafc.seqdb.api.entities.MetagenomicsBatchItem; +import ca.gc.aafc.seqdb.api.service.MetagenomicsBatchItemService; + +import java.util.Optional; +import lombok.NonNull; + +@Repository +public class MetagenomicsBatchItemRepository extends DinaRepository { + + private Optional dinaAuthenticatedUser; + + public MetagenomicsBatchItemRepository( + @NonNull MetagenomicsBatchItemService dinaService, + DinaAuthorizationService groupAuthorizationService, + @NonNull BuildProperties props, + ExternalResourceProvider externalResourceProvider, + Optional dinaAuthenticatedUser, + ObjectMapper objMapper) { + super( + dinaService, + groupAuthorizationService, + Optional.empty(), + new DinaMapper<>(MetagenomicsBatchItemDto.class), + MetagenomicsBatchItemDto.class, + MetagenomicsBatchItem.class, + null, + externalResourceProvider, + props, objMapper); + this.dinaAuthenticatedUser = dinaAuthenticatedUser; + } + + @Override + public S create(S resource) { + dinaAuthenticatedUser.ifPresent( + authenticatedUser -> resource.setCreatedBy(authenticatedUser.getUsername())); + return super.create(resource); + } +} diff --git a/src/main/java/ca/gc/aafc/seqdb/api/repository/MetagenomicsBatchRepository.java b/src/main/java/ca/gc/aafc/seqdb/api/repository/MetagenomicsBatchRepository.java new file mode 100644 index 00000000..76aa18c2 --- /dev/null +++ b/src/main/java/ca/gc/aafc/seqdb/api/repository/MetagenomicsBatchRepository.java @@ -0,0 +1,51 @@ +package ca.gc.aafc.seqdb.api.repository; + +import org.springframework.boot.info.BuildProperties; +import org.springframework.stereotype.Repository; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import ca.gc.aafc.dina.mapper.DinaMapper; +import ca.gc.aafc.dina.repository.DinaRepository; +import ca.gc.aafc.dina.repository.external.ExternalResourceProvider; +import ca.gc.aafc.dina.security.DinaAuthenticatedUser; +import ca.gc.aafc.dina.security.auth.DinaAuthorizationService; +import ca.gc.aafc.seqdb.api.dto.MetagenomicsBatchDto; +import ca.gc.aafc.seqdb.api.entities.MetagenomicsBatch; +import ca.gc.aafc.seqdb.api.service.MetagenomicsBatchService; + +import java.util.Optional; +import lombok.NonNull; + +@Repository +public class MetagenomicsBatchRepository extends DinaRepository { + + private Optional dinaAuthenticatedUser; + + public MetagenomicsBatchRepository( + @NonNull MetagenomicsBatchService dinaService, + DinaAuthorizationService groupAuthorizationService, + @NonNull BuildProperties props, + ExternalResourceProvider externalResourceProvider, + Optional dinaAuthenticatedUser, + ObjectMapper objMapper) { + super( + dinaService, + groupAuthorizationService, + Optional.empty(), + new DinaMapper<>(MetagenomicsBatchDto.class), + MetagenomicsBatchDto.class, + MetagenomicsBatch.class, + null, + externalResourceProvider, + props, objMapper); + this.dinaAuthenticatedUser = dinaAuthenticatedUser; + } + + @Override + public S create(S resource) { + dinaAuthenticatedUser.ifPresent( + authenticatedUser -> resource.setCreatedBy(authenticatedUser.getUsername())); + return super.create(resource); + } +} diff --git a/src/main/java/ca/gc/aafc/seqdb/api/repository/QualityControlRepository.java b/src/main/java/ca/gc/aafc/seqdb/api/repository/QualityControlRepository.java new file mode 100644 index 00000000..b8b50ed0 --- /dev/null +++ b/src/main/java/ca/gc/aafc/seqdb/api/repository/QualityControlRepository.java @@ -0,0 +1,53 @@ +package ca.gc.aafc.seqdb.api.repository; + +import org.springframework.boot.info.BuildProperties; +import org.springframework.stereotype.Repository; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import ca.gc.aafc.dina.mapper.DinaMapper; +import ca.gc.aafc.dina.repository.DinaRepository; +import ca.gc.aafc.dina.repository.external.ExternalResourceProvider; +import ca.gc.aafc.dina.security.DinaAuthenticatedUser; +import ca.gc.aafc.dina.security.auth.DinaAuthorizationService; +import ca.gc.aafc.seqdb.api.dto.QualityControlDto; +import ca.gc.aafc.seqdb.api.entities.QualityControl; +import ca.gc.aafc.seqdb.api.service.QualityControlService; + +import java.util.Optional; +import lombok.NonNull; + +@Repository +public class QualityControlRepository extends DinaRepository { + + private Optional dinaAuthenticatedUser; + + public QualityControlRepository( + @NonNull QualityControlService dinaService, + DinaAuthorizationService groupAuthorizationService, + @NonNull BuildProperties props, + ExternalResourceProvider externalResourceProvider, + Optional dinaAuthenticatedUser, + ObjectMapper objMapper) { + super( + dinaService, + groupAuthorizationService, + Optional.empty(), + new DinaMapper<>(QualityControlDto.class), + QualityControlDto.class, + QualityControl.class, + null, + externalResourceProvider, + props, objMapper); + + this.dinaAuthenticatedUser = dinaAuthenticatedUser; + } + + @Override + public S create(S resource) { + dinaAuthenticatedUser.ifPresent( + authenticatedUser -> resource.setCreatedBy(authenticatedUser.getUsername())); + return super.create(resource); + } + +} diff --git a/src/main/java/ca/gc/aafc/seqdb/api/repository/SequenceManagedAttributeRepository.java b/src/main/java/ca/gc/aafc/seqdb/api/repository/SequenceManagedAttributeRepository.java new file mode 100644 index 00000000..9ee6309d --- /dev/null +++ b/src/main/java/ca/gc/aafc/seqdb/api/repository/SequenceManagedAttributeRepository.java @@ -0,0 +1,88 @@ +package ca.gc.aafc.seqdb.api.repository; + +import org.springframework.boot.info.BuildProperties; +import org.springframework.stereotype.Repository; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import ca.gc.aafc.dina.mapper.DinaMapper; +import ca.gc.aafc.dina.repository.DinaRepository; +import ca.gc.aafc.dina.repository.external.ExternalResourceProvider; +import ca.gc.aafc.dina.security.DinaAuthenticatedUser; +import ca.gc.aafc.dina.security.TextHtmlSanitizer; +import ca.gc.aafc.dina.service.AuditService; +import ca.gc.aafc.seqdb.api.dto.SequenceManagedAttributeDto; +import ca.gc.aafc.seqdb.api.entities.SequenceManagedAttribute; +import ca.gc.aafc.seqdb.api.security.SuperUserInGroupCUDAuthorizationService; +import ca.gc.aafc.seqdb.api.service.SequenceManagedAttributeService; + +import io.crnk.core.exception.ResourceNotFoundException; +import io.crnk.core.queryspec.QuerySpec; +import java.io.Serializable; +import java.util.Optional; +import java.util.regex.Pattern; +import lombok.NonNull; + +@Repository +public class SequenceManagedAttributeRepository extends DinaRepository { + + private final SequenceManagedAttributeService dinaService; + private Optional dinaAuthenticatedUser; + + public static final Pattern KEY_LOOKUP_PATTERN = Pattern.compile("(.*)\\.(.*)"); + + public SequenceManagedAttributeRepository( + @NonNull SequenceManagedAttributeService service, + @NonNull SuperUserInGroupCUDAuthorizationService authService, + ExternalResourceProvider externalResourceProvider, + @NonNull AuditService auditService, + @NonNull BuildProperties buildProperties, + Optional dinaAuthenticatedUser, + ObjectMapper objectMapper + ) { + super( + service, authService, + Optional.of(auditService), + new DinaMapper<>(SequenceManagedAttributeDto.class), + SequenceManagedAttributeDto.class, + SequenceManagedAttribute.class, + null, + externalResourceProvider, + buildProperties, objectMapper); + this.dinaService = service; + this.dinaAuthenticatedUser = dinaAuthenticatedUser; + } + + @Override + public S create(S resource) { + dinaAuthenticatedUser.ifPresent( + authenticatedUser -> resource.setCreatedBy(authenticatedUser.getUsername())); + return super.create(resource); + } + + @Override + public SequenceManagedAttributeDto findOne(Serializable id, QuerySpec querySpec) { + + // Allow lookup by component type + key. + // e.g. collecting_event.attribute_name + var matcher = KEY_LOOKUP_PATTERN.matcher(id.toString()); + if (matcher.groupCount() == 2) { + if (matcher.find()) { + SequenceManagedAttribute.ManagedAttributeComponent component = SequenceManagedAttribute.ManagedAttributeComponent + .fromString(matcher.group(1)); + String attributeKey = matcher.group(2); + + SequenceManagedAttribute managedAttribute = + dinaService.findOneByKeyAndComponent(attributeKey, component); + + if (managedAttribute != null) { + return getMappingLayer().toDtoSimpleMapping(managedAttribute); + } else { + throw new ResourceNotFoundException("Managed Attribute not found: " + TextHtmlSanitizer.sanitizeText(id.toString())); + } + } + } + + return super.findOne(id, querySpec); + } +} diff --git a/src/main/java/ca/gc/aafc/seqdb/api/security/SuperUserInGroupCUDAuthorizationService.java b/src/main/java/ca/gc/aafc/seqdb/api/security/SuperUserInGroupCUDAuthorizationService.java new file mode 100644 index 00000000..e46af135 --- /dev/null +++ b/src/main/java/ca/gc/aafc/seqdb/api/security/SuperUserInGroupCUDAuthorizationService.java @@ -0,0 +1,39 @@ +package ca.gc.aafc.seqdb.api.security; + +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Service; + +import ca.gc.aafc.dina.security.auth.PermissionAuthorizationService; + +/** + * Authorization service that will authorize SUPER_USER (and DINA_ADMIN) for the group of the entity. + * The currentUser must be at least SUPER_USER on the group defined on the entity. + */ +@Service +public class SuperUserInGroupCUDAuthorizationService extends PermissionAuthorizationService { + + @Override + @PreAuthorize("hasMinimumGroupAndRolePermissions(@currentUser, 'SUPER_USER', #entity)") + public void authorizeCreate(Object entity) { + } + + @Override + @PreAuthorize("hasMinimumGroupAndRolePermissions(@currentUser, 'SUPER_USER', #entity)") + public void authorizeUpdate(Object entity) { + } + + @Override + @PreAuthorize("hasMinimumGroupAndRolePermissions(@currentUser, 'SUPER_USER', #entity)") + public void authorizeDelete(Object entity) { + } + + // Do nothing for now + @Override + public void authorizeRead(Object entity) { + } + + @Override + public String getName() { + return "SuperUserInGroupCUDAuthorizationService"; + } +} diff --git a/src/main/java/ca/gc/aafc/seqdb/api/service/GenericMolecularAnalysisService.java b/src/main/java/ca/gc/aafc/seqdb/api/service/GenericMolecularAnalysisService.java index c807e8a7..f4b77d8b 100644 --- a/src/main/java/ca/gc/aafc/seqdb/api/service/GenericMolecularAnalysisService.java +++ b/src/main/java/ca/gc/aafc/seqdb/api/service/GenericMolecularAnalysisService.java @@ -3,19 +3,33 @@ import java.util.UUID; import lombok.NonNull; +import org.apache.commons.collections.MapUtils; import org.springframework.stereotype.Service; import org.springframework.validation.SmartValidator; import ca.gc.aafc.dina.jpa.BaseDAO; import ca.gc.aafc.dina.service.DefaultDinaService; import ca.gc.aafc.seqdb.api.entities.GenericMolecularAnalysis; +import ca.gc.aafc.seqdb.api.entities.SequenceManagedAttribute; +import ca.gc.aafc.seqdb.api.validation.SequenceManagedAttributeValueValidator; @Service public class GenericMolecularAnalysisService extends DefaultDinaService { + private static final SequenceManagedAttributeValueValidator.SequenceManagedAttributeValidationContext + GENERIC_MOLECULAR_ANALYSIS_VALIDATION_CONTEXT = + SequenceManagedAttributeValueValidator.SequenceManagedAttributeValidationContext + .from(SequenceManagedAttribute.ManagedAttributeComponent.GENERIC_MOLECULAR_ANALYSIS); + + private final SequenceManagedAttributeValueValidator sequenceManagedAttributeValueValidator; + public GenericMolecularAnalysisService( - @NonNull BaseDAO baseDAO, @NonNull SmartValidator sv) { + @NonNull BaseDAO baseDAO, + @NonNull SequenceManagedAttributeValueValidator sequenceManagedAttributeValueValidator, + @NonNull SmartValidator sv) { super(baseDAO, sv); + + this.sequenceManagedAttributeValueValidator = sequenceManagedAttributeValueValidator; } @Override @@ -23,4 +37,17 @@ protected void preCreate(GenericMolecularAnalysis entity) { entity.setUuid(UUID.randomUUID()); } + @Override + public void validateBusinessRules(GenericMolecularAnalysis entity) { + validateManagedAttribute(entity); + } + + private void validateManagedAttribute(GenericMolecularAnalysis genericMolecularAnalysis) { + if (MapUtils.isNotEmpty(genericMolecularAnalysis.getManagedAttributes())) { + sequenceManagedAttributeValueValidator.validate(genericMolecularAnalysis, + genericMolecularAnalysis.getManagedAttributes(), + GENERIC_MOLECULAR_ANALYSIS_VALIDATION_CONTEXT); + } + } + } diff --git a/src/main/java/ca/gc/aafc/seqdb/api/service/MetagenomicsBatchItemService.java b/src/main/java/ca/gc/aafc/seqdb/api/service/MetagenomicsBatchItemService.java new file mode 100644 index 00000000..611cc728 --- /dev/null +++ b/src/main/java/ca/gc/aafc/seqdb/api/service/MetagenomicsBatchItemService.java @@ -0,0 +1,27 @@ +package ca.gc.aafc.seqdb.api.service; + +import lombok.NonNull; + +import org.springframework.stereotype.Service; +import org.springframework.validation.SmartValidator; + +import ca.gc.aafc.dina.jpa.BaseDAO; +import ca.gc.aafc.dina.service.DefaultDinaService; +import ca.gc.aafc.dina.util.UUIDHelper; +import ca.gc.aafc.seqdb.api.entities.MetagenomicsBatchItem; + +@Service +public class MetagenomicsBatchItemService extends DefaultDinaService { + + public MetagenomicsBatchItemService( + @NonNull BaseDAO baseDAO, @NonNull SmartValidator sv) { + super(baseDAO, sv); + } + + @Override + protected void preCreate(MetagenomicsBatchItem entity) { + entity.setUuid(UUIDHelper.generateUUIDv7()); + } + +} + diff --git a/src/main/java/ca/gc/aafc/seqdb/api/service/MetagenomicsBatchService.java b/src/main/java/ca/gc/aafc/seqdb/api/service/MetagenomicsBatchService.java new file mode 100644 index 00000000..fb5ef2df --- /dev/null +++ b/src/main/java/ca/gc/aafc/seqdb/api/service/MetagenomicsBatchService.java @@ -0,0 +1,26 @@ +package ca.gc.aafc.seqdb.api.service; + +import lombok.NonNull; + +import org.springframework.stereotype.Service; +import org.springframework.validation.SmartValidator; + +import ca.gc.aafc.dina.jpa.BaseDAO; +import ca.gc.aafc.dina.service.DefaultDinaService; +import ca.gc.aafc.dina.util.UUIDHelper; +import ca.gc.aafc.seqdb.api.entities.MetagenomicsBatch; + +@Service +public class MetagenomicsBatchService extends DefaultDinaService { + + public MetagenomicsBatchService( + @NonNull BaseDAO baseDAO, @NonNull SmartValidator sv) { + super(baseDAO, sv); + } + + @Override + protected void preCreate(MetagenomicsBatch entity) { + entity.setUuid(UUIDHelper.generateUUIDv7()); + } + +} diff --git a/src/main/java/ca/gc/aafc/seqdb/api/service/QualityControlService.java b/src/main/java/ca/gc/aafc/seqdb/api/service/QualityControlService.java new file mode 100644 index 00000000..3411283c --- /dev/null +++ b/src/main/java/ca/gc/aafc/seqdb/api/service/QualityControlService.java @@ -0,0 +1,37 @@ +package ca.gc.aafc.seqdb.api.service; + +import lombok.NonNull; + +import org.springframework.stereotype.Service; +import org.springframework.validation.SmartValidator; + +import ca.gc.aafc.dina.jpa.BaseDAO; +import ca.gc.aafc.dina.service.DefaultDinaService; +import ca.gc.aafc.dina.util.UUIDHelper; +import ca.gc.aafc.seqdb.api.entities.QualityControl; +import ca.gc.aafc.seqdb.api.validation.QualityControlVocabularyValidator; + +@Service +public class QualityControlService extends DefaultDinaService { + + private final QualityControlVocabularyValidator qualityControlVocabularyValidator; + + public QualityControlService( + @NonNull BaseDAO baseDAO, + QualityControlVocabularyValidator qualityControlVocabularyValidator, + @NonNull SmartValidator sv) { + super(baseDAO, sv); + this.qualityControlVocabularyValidator = qualityControlVocabularyValidator; + } + + @Override + protected void preCreate(QualityControl entity) { + entity.setUuid(UUIDHelper.generateUUIDv7()); + } + + @Override + public void validateBusinessRules(QualityControl entity) { + applyBusinessRule(entity, qualityControlVocabularyValidator); + } + +} diff --git a/src/main/java/ca/gc/aafc/seqdb/api/service/SequenceManagedAttributeService.java b/src/main/java/ca/gc/aafc/seqdb/api/service/SequenceManagedAttributeService.java new file mode 100644 index 00000000..0e5b4931 --- /dev/null +++ b/src/main/java/ca/gc/aafc/seqdb/api/service/SequenceManagedAttributeService.java @@ -0,0 +1,69 @@ +package ca.gc.aafc.seqdb.api.service; + +import liquibase.repackaged.org.apache.commons.lang3.StringUtils; +import lombok.NonNull; + +import org.apache.commons.lang3.tuple.Pair; +import org.springframework.stereotype.Service; +import org.springframework.validation.SmartValidator; + +import ca.gc.aafc.dina.jpa.BaseDAO; +import ca.gc.aafc.dina.service.ManagedAttributeService; +import ca.gc.aafc.dina.service.PostgresJsonbService; +import ca.gc.aafc.dina.util.UUIDHelper; +import ca.gc.aafc.seqdb.api.entities.SequenceManagedAttribute; + +@Service +public class SequenceManagedAttributeService extends ManagedAttributeService { + + public static final String MANAGED_ATTRIBUTES_COL_NAME = "managed_attributes"; + + public static final String GENERIC_MOLECULAR_ANALYSIS_TABLE_NAME = "generic_molecular_analysis"; + + private static final String COMPONENT_FIELD_NAME = "managedAttributeComponent"; + + private final PostgresJsonbService jsonbService; + + public SequenceManagedAttributeService( + @NonNull BaseDAO baseDAO, + @NonNull SmartValidator sv, + @NonNull PostgresJsonbService postgresJsonbService) { + super(baseDAO, sv, SequenceManagedAttribute.class); + this.jsonbService = postgresJsonbService; + } + + @Override + protected void preCreate(SequenceManagedAttribute entity) { + entity.setUuid(UUIDHelper.generateUUIDv7()); + entity.setGroup(standardizeGroupName(entity)); + super.preCreate(entity); + } + + @Override + protected void preDelete(SequenceManagedAttribute entity) { + switch (entity.getManagedAttributeComponent()) { + case GENERIC_MOLECULAR_ANALYSIS: + checkKeysFor(entity.getKey(), GENERIC_MOLECULAR_ANALYSIS_TABLE_NAME); + break; + default: + throw new IllegalStateException( + "Unexpected managed attribute component of: " + entity.getManagedAttributeComponent()); + } + } + + public SequenceManagedAttribute findOneByKeyAndComponent(String key, + SequenceManagedAttribute.ManagedAttributeComponent component) { + if (StringUtils.isBlank(key) || component == null) { + return null; + } + return findOneByKeyAnd(key, Pair.of(COMPONENT_FIELD_NAME, component)); + } + + private void checkKeysFor(String key, String tableName) { + Integer countFirstLevelKeys = jsonbService.countFirstLevelKeys( + tableName, SequenceManagedAttributeService.MANAGED_ATTRIBUTES_COL_NAME, key); + if (countFirstLevelKeys > 0) { + throw new IllegalStateException("Managed attribute key: " + key + ", is currently in use."); + } + } +} diff --git a/src/main/java/ca/gc/aafc/seqdb/api/util/ManagedAttributeIdMapper.java b/src/main/java/ca/gc/aafc/seqdb/api/util/ManagedAttributeIdMapper.java new file mode 100644 index 00000000..97417783 --- /dev/null +++ b/src/main/java/ca/gc/aafc/seqdb/api/util/ManagedAttributeIdMapper.java @@ -0,0 +1,31 @@ +package ca.gc.aafc.seqdb.api.util; + +import io.crnk.core.engine.parser.StringMapper; +import java.util.UUID; +import lombok.RequiredArgsConstructor; + +/** + * Lets you use either the UUID or the component type + key as the ID. + * e.g. /managed-attribute/generic_molecular_analysis.attribute_name. + */ +@RequiredArgsConstructor +public class ManagedAttributeIdMapper implements StringMapper { + private final StringMapper stringMapper; + + @Override + public Object parse(String input) { + // If the input's not in UUID format then use the raw string as the ID: + try { + UUID.fromString(input); + } catch (IllegalArgumentException e) { + return input; + } + return stringMapper.parse(input); + } + + @Override + public String toString(Object input) { + return stringMapper.toString(input); + } + +} diff --git a/src/main/java/ca/gc/aafc/seqdb/api/validation/PcrBatchVocabularyValidator.java b/src/main/java/ca/gc/aafc/seqdb/api/validation/PcrBatchVocabularyValidator.java index 1f5b4d5b..b7881255 100644 --- a/src/main/java/ca/gc/aafc/seqdb/api/validation/PcrBatchVocabularyValidator.java +++ b/src/main/java/ca/gc/aafc/seqdb/api/validation/PcrBatchVocabularyValidator.java @@ -21,12 +21,12 @@ public class PcrBatchVocabularyValidator extends VocabularyBasedValidator batchTypeVocabulary; PcrBatchVocabularyValidator(MessageSource messageSource, SequenceVocabularyConfiguration vocabularyConfiguration) { - super(messageSource, PcrBatch.class); + super(PcrBatch.class, messageSource); batchTypeVocabulary = vocabularyConfiguration.getVocabularyByKey(SequenceVocabularyConfiguration.PCR_BATCH_TYPE_VOCAB_KEY); } @Override - protected void validateVocabularyBasedAttribute(PcrBatch target, Errors errors) { + public void validateTarget(PcrBatch target, Errors errors) { if (StringUtils.isBlank(target.getBatchType())) { return; } diff --git a/src/main/java/ca/gc/aafc/seqdb/api/validation/QualityControlVocabularyValidator.java b/src/main/java/ca/gc/aafc/seqdb/api/validation/QualityControlVocabularyValidator.java new file mode 100644 index 00000000..43495bce --- /dev/null +++ b/src/main/java/ca/gc/aafc/seqdb/api/validation/QualityControlVocabularyValidator.java @@ -0,0 +1,34 @@ +package ca.gc.aafc.seqdb.api.validation; + +import java.util.List; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.context.MessageSource; +import org.springframework.stereotype.Component; +import org.springframework.validation.Errors; + +import ca.gc.aafc.dina.vocabulary.VocabularyElementConfiguration; +import ca.gc.aafc.seqdb.api.SequenceVocabularyConfiguration; +import ca.gc.aafc.seqdb.api.entities.QualityControl; + +@Component +public class QualityControlVocabularyValidator extends VocabularyBasedValidator { + + private static final String QUALITY_CONTROL_FIELD_NAME = "qcType"; + private final List qcTypeVocabulary; + + QualityControlVocabularyValidator(MessageSource messageSource, SequenceVocabularyConfiguration vocabularyConfiguration) { + super(QualityControl.class, messageSource); + qcTypeVocabulary = vocabularyConfiguration.getVocabularyByKey(SequenceVocabularyConfiguration.QUALITY_CONTROL_TYPE_VOCAB_KEY); + } + + @Override + public void validateTarget(QualityControl target, Errors errors) { + if (StringUtils.isBlank(target.getQcType())) { + return; + } + + target.setQcType(validateAndStandardizeValueAgainstVocabulary(target.getQcType(), + QUALITY_CONTROL_FIELD_NAME, qcTypeVocabulary, errors)); + } +} diff --git a/src/main/java/ca/gc/aafc/seqdb/api/validation/SeqBatchVocabularyValidator.java b/src/main/java/ca/gc/aafc/seqdb/api/validation/SeqBatchVocabularyValidator.java index 05f90622..4ab8ec42 100644 --- a/src/main/java/ca/gc/aafc/seqdb/api/validation/SeqBatchVocabularyValidator.java +++ b/src/main/java/ca/gc/aafc/seqdb/api/validation/SeqBatchVocabularyValidator.java @@ -21,12 +21,12 @@ public class SeqBatchVocabularyValidator extends VocabularyBasedValidator sequencingTypeVocabulary; SeqBatchVocabularyValidator(MessageSource messageSource, SequenceVocabularyConfiguration vocabularyConfiguration) { - super(messageSource, SeqBatch.class); + super(SeqBatch.class, messageSource); sequencingTypeVocabulary = vocabularyConfiguration.getVocabularyByKey(SequenceVocabularyConfiguration.SEQUENCING_TYPE_VOCAB_KEY); } @Override - protected void validateVocabularyBasedAttribute(SeqBatch target, Errors errors) { + public void validateTarget(SeqBatch target, Errors errors) { if (StringUtils.isBlank(target.getSequencingType())) { return; } diff --git a/src/main/java/ca/gc/aafc/seqdb/api/validation/SequenceManagedAttributeValueValidator.java b/src/main/java/ca/gc/aafc/seqdb/api/validation/SequenceManagedAttributeValueValidator.java new file mode 100644 index 00000000..7b2185f7 --- /dev/null +++ b/src/main/java/ca/gc/aafc/seqdb/api/validation/SequenceManagedAttributeValueValidator.java @@ -0,0 +1,110 @@ +package ca.gc.aafc.seqdb.api.validation; + +import org.apache.commons.lang3.tuple.Pair; +import org.springframework.context.MessageSource; +import org.springframework.context.i18n.LocaleContextHolder; +import org.springframework.stereotype.Component; +import org.springframework.validation.Errors; + +import ca.gc.aafc.dina.entity.DinaEntity; +import ca.gc.aafc.dina.service.ManagedAttributeService; +import ca.gc.aafc.dina.validation.ManagedAttributeValueValidator; +import ca.gc.aafc.dina.validation.ValidationContext; +import ca.gc.aafc.seqdb.api.entities.SequenceManagedAttribute; + +import java.util.EnumMap; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import javax.inject.Named; +import lombok.NonNull; + +@Component +public class SequenceManagedAttributeValueValidator extends ManagedAttributeValueValidator { + + private static final String INVALID_VALIDATION_CONTEXT_KEY = "managedAttribute.validation.context.invalid"; + private static final String COMPONENT_FIELD_NAME = "managedAttributeComponent"; + + private final ManagedAttributeService dinaService; + private final MessageSource messageSource; + + public SequenceManagedAttributeValueValidator( + @Named("validationMessageSource") MessageSource baseMessageSource, // from dina-base + @NonNull MessageSource messageSource, + @NonNull ManagedAttributeService dinaService) { + super(baseMessageSource, dinaService); + this.dinaService = dinaService; + this.messageSource = messageSource; + } + + public void validate(D entity, Map managedAttributes, SequenceManagedAttributeValidationContext context) { + super.validate(entity, managedAttributes, context); + } + + /** + * override base class version to also add a restriction on the component since the uniqueness + * for CollectionManagedAttribute is key + component. + * @param keys + * @param validationContext + * @return + */ + @Override + protected Map findAttributesForValidation(Set keys, ValidationContext validationContext) { + return dinaService.findAttributesForKeys(keys, Pair.of(COMPONENT_FIELD_NAME, validationContext.getValue())); + } + + @Override + protected boolean preValidateValue(SequenceManagedAttribute managedAttributeDefinition, + String value, Errors errors, ValidationContext validationContext) { + + // expected context based on the component + SequenceManagedAttributeValidationContext expectedContext = + SequenceManagedAttributeValidationContext.from(managedAttributeDefinition.getManagedAttributeComponent()); + + if (!expectedContext.equals(validationContext)) { + errors.reject(INVALID_VALIDATION_CONTEXT_KEY, getMessageForKey(INVALID_VALIDATION_CONTEXT_KEY, + Objects.toString(validationContext), expectedContext.toString())); + return false; + } + return true; + } + + /** + * Wrapper class to expose {@link ca.gc.aafc.seqdb.api.entities.SequenceManagedAttribute.ManagedAttributeComponent} as + * {@link ValidationContext}. + */ + public static final class SequenceManagedAttributeValidationContext implements ValidationContext { + // make sure to only keep 1 instance per enum value + private static final EnumMap + INSTANCES = new EnumMap<>(SequenceManagedAttribute.ManagedAttributeComponent.class); + + private final SequenceManagedAttribute.ManagedAttributeComponent managedAttributeComponent; + + /** + * Use {@link #from(SequenceManagedAttribute.ManagedAttributeComponent)} method + * @param managedAttributeComponent + */ + private SequenceManagedAttributeValidationContext(SequenceManagedAttribute.ManagedAttributeComponent managedAttributeComponent) { + this.managedAttributeComponent = managedAttributeComponent; + } + + public static SequenceManagedAttributeValidationContext from(SequenceManagedAttribute.ManagedAttributeComponent managedAttributeComponent) { + return INSTANCES.computeIfAbsent(managedAttributeComponent, SequenceManagedAttributeValidationContext::new); + } + + @Override + public String toString() { + return managedAttributeComponent.toString(); + } + + @Override + public Object getValue() { + return managedAttributeComponent; + } + } + + // to be replaced by dina-base whne 0.132 will be released + private String getMessageForKey(String key, Object... objects) { + return messageSource.getMessage(key, objects, LocaleContextHolder.getLocale()); + } +} diff --git a/src/main/java/ca/gc/aafc/seqdb/api/validation/VocabularyBasedValidator.java b/src/main/java/ca/gc/aafc/seqdb/api/validation/VocabularyBasedValidator.java index d2c9f95d..11e907d0 100644 --- a/src/main/java/ca/gc/aafc/seqdb/api/validation/VocabularyBasedValidator.java +++ b/src/main/java/ca/gc/aafc/seqdb/api/validation/VocabularyBasedValidator.java @@ -1,41 +1,23 @@ package ca.gc.aafc.seqdb.api.validation; -import java.util.List; -import java.util.Optional; - import org.springframework.context.MessageSource; -import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.validation.Errors; -import org.springframework.validation.Validator; +import ca.gc.aafc.dina.validation.DinaBaseValidator; import ca.gc.aafc.dina.vocabulary.VocabularyElementConfiguration; +import java.util.List; +import java.util.Optional; + /** * Move to dina-base */ -abstract class VocabularyBasedValidator implements Validator { +abstract class VocabularyBasedValidator extends DinaBaseValidator { public static final String VALUE_NOT_IN_VOCABULARY = "validation.constraint.violation.notInVocabulary"; - private final MessageSource messageSource; - private final Class supportedClass; - - VocabularyBasedValidator(MessageSource messageSource, Class supportedClass) { - this.messageSource = messageSource; - this.supportedClass = supportedClass; - } - - @Override - public boolean supports(Class clazz) { - return supportedClass.isAssignableFrom(clazz); - } - - @Override - public void validate(Object target, Errors errors) { - if (!supports(target.getClass())) { - throw new IllegalArgumentException("VocabularyBasedValidator not supported for class " + target.getClass()); - } - validateVocabularyBasedAttribute(supportedClass.cast(target), errors); + VocabularyBasedValidator(Class supportedClass, MessageSource messageSource) { + super(supportedClass, messageSource); } /** @@ -67,14 +49,4 @@ protected String validateAndStandardizeValueAgainstVocabulary(String value, Stri protected Optional findInVocabulary(String value, List vocabularyElements) { return vocabularyElements.stream().filter(o -> o.getKey().equalsIgnoreCase(value)).findFirst(); } - - protected abstract void validateVocabularyBasedAttribute(T target, Errors errors); - - protected String getMessage(String key) { - return messageSource.getMessage(key, null, LocaleContextHolder.getLocale()); - } - protected String getMessage(String key, Object... messageArgs) { - return messageSource.getMessage(key, messageArgs, LocaleContextHolder.getLocale()); - } - } diff --git a/src/main/resources/db/changelog/db.changelog-master.xml b/src/main/resources/db/changelog/db.changelog-master.xml index dc1ac0aa..afc89dec 100644 --- a/src/main/resources/db/changelog/db.changelog-master.xml +++ b/src/main/resources/db/changelog/db.changelog-master.xml @@ -58,4 +58,8 @@ + + + + diff --git a/src/main/resources/db/changelog/migrations/54-Add_metagenomics_tables.xml b/src/main/resources/db/changelog/migrations/54-Add_metagenomics_tables.xml new file mode 100644 index 00000000..67d44fa1 --- /dev/null +++ b/src/main/resources/db/changelog/migrations/54-Add_metagenomics_tables.xml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/db/changelog/migrations/55-Add_managed_attribute.xml b/src/main/resources/db/changelog/migrations/55-Add_managed_attribute.xml new file mode 100644 index 00000000..0eeb27f3 --- /dev/null +++ b/src/main/resources/db/changelog/migrations/55-Add_managed_attribute.xml @@ -0,0 +1,51 @@ + + + + + + CREATE TYPE managed_attribute_type AS ENUM ( + 'INTEGER', + 'STRING', + 'DATE', + 'BOOL', + 'DECIMAL' + ); + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/db/changelog/migrations/56-Add_managed_attribute_to_molecular_analysis.xml b/src/main/resources/db/changelog/migrations/56-Add_managed_attribute_to_molecular_analysis.xml new file mode 100644 index 00000000..9710d649 --- /dev/null +++ b/src/main/resources/db/changelog/migrations/56-Add_managed_attribute_to_molecular_analysis.xml @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/src/main/resources/db/changelog/migrations/57-Add_quality_control_table.xml b/src/main/resources/db/changelog/migrations/57-Add_quality_control_table.xml new file mode 100644 index 00000000..b8c0e7a4 --- /dev/null +++ b/src/main/resources/db/changelog/migrations/57-Add_quality_control_table.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/vocabulary/pcrBatchType.yml b/src/main/resources/vocabulary/pcrBatchType.yml index e5e94bfa..769645bd 100644 --- a/src/main/resources/vocabulary/pcrBatchType.yml +++ b/src/main/resources/vocabulary/pcrBatchType.yml @@ -14,10 +14,11 @@ vocabulary: title: Illumina (NGS) - lang: fr title: Illumina (NGS) - - key: round_2 - name: Round 2 + - key: illumina_metagenomics + name: Illumina Metagenomics multilingualTitle.titles: - lang: en - title: Round 2 + title: Illumina Metagenomics - lang: fr - title: Round 2 \ No newline at end of file + title: Illumina Metagenomics + \ No newline at end of file diff --git a/src/main/resources/vocabulary/qualityControlType.yml b/src/main/resources/vocabulary/qualityControlType.yml new file mode 100644 index 00000000..a88d9ac0 --- /dev/null +++ b/src/main/resources/vocabulary/qualityControlType.yml @@ -0,0 +1,23 @@ +vocabulary: + qualityControlType: + - key: reserpine_standard + name: reserpine standard + multilingualTitle.titles: + - lang: en + title: Reserpine Standard + - lang: fr + title: Standard Reserpine + - key: acn_blank + name: acn blank + multilingualTitle.titles: + - lang: en + title: ACN Blank + - lang: fr + title: ACN Blank (fr) + - key: meoh_blank + name: meoh blank + multilingualTitle.titles: + - lang: en + title: MEOH Blank + - lang: fr + title: MEOH Blank (fr) diff --git a/src/test/java/ca/gc/aafc/seqdb/api/BaseIntegrationTest.java b/src/test/java/ca/gc/aafc/seqdb/api/BaseIntegrationTest.java index c676b83a..e7dbd329 100644 --- a/src/test/java/ca/gc/aafc/seqdb/api/BaseIntegrationTest.java +++ b/src/test/java/ca/gc/aafc/seqdb/api/BaseIntegrationTest.java @@ -29,7 +29,7 @@ public abstract class BaseIntegrationTest { @PersistenceContext protected EntityManager entityManager; - @Inject - protected DatabaseSupportService service; +// @Inject +// protected DatabaseSupportService service; } diff --git a/src/test/java/ca/gc/aafc/seqdb/api/SequenceModuleBaseIT.java b/src/test/java/ca/gc/aafc/seqdb/api/SequenceModuleBaseIT.java index 6cbd7348..9bfe1f9d 100644 --- a/src/test/java/ca/gc/aafc/seqdb/api/SequenceModuleBaseIT.java +++ b/src/test/java/ca/gc/aafc/seqdb/api/SequenceModuleBaseIT.java @@ -4,12 +4,16 @@ import ca.gc.aafc.seqdb.api.service.GenericMolecularAnalysisService; import ca.gc.aafc.seqdb.api.service.GenericMolecularAnalysisItemService; +import ca.gc.aafc.seqdb.api.service.MetagenomicsBatchItemService; +import ca.gc.aafc.seqdb.api.service.MetagenomicsBatchService; import ca.gc.aafc.seqdb.api.service.MolecularAnalysisResultService; import ca.gc.aafc.seqdb.api.service.MolecularAnalysisRunItemService; import ca.gc.aafc.seqdb.api.service.MolecularAnalysisRunService; import ca.gc.aafc.seqdb.api.service.PcrBatchItemService; import ca.gc.aafc.seqdb.api.service.PcrBatchService; +import ca.gc.aafc.seqdb.api.service.QualityControlService; import ca.gc.aafc.seqdb.api.service.SeqSubmissionService; +import ca.gc.aafc.seqdb.api.service.SequenceManagedAttributeService; import ca.gc.aafc.seqdb.api.service.SequencingFacilityService; public class SequenceModuleBaseIT extends BaseIntegrationTest { @@ -40,4 +44,17 @@ public class SequenceModuleBaseIT extends BaseIntegrationTest { @Inject protected GenericMolecularAnalysisItemService genericMolecularAnalysisItemService; + + @Inject + protected MetagenomicsBatchService metagenomicsBatchService; + + @Inject + protected MetagenomicsBatchItemService metagenomicsBatchItemService; + + @Inject + protected SequenceManagedAttributeService managedAttributeService; + + @Inject + protected QualityControlService qualityControlService; + } diff --git a/src/test/java/ca/gc/aafc/seqdb/api/entities/MetagenomicsBatchIT.java b/src/test/java/ca/gc/aafc/seqdb/api/entities/MetagenomicsBatchIT.java new file mode 100644 index 00000000..de3194ae --- /dev/null +++ b/src/test/java/ca/gc/aafc/seqdb/api/entities/MetagenomicsBatchIT.java @@ -0,0 +1,28 @@ +package ca.gc.aafc.seqdb.api.entities; + +import org.junit.jupiter.api.Test; + +import ca.gc.aafc.seqdb.api.SequenceModuleBaseIT; +import ca.gc.aafc.seqdb.api.testsupport.factories.IndexSetFactory; +import ca.gc.aafc.seqdb.api.testsupport.factories.MetagenomicsBatchFactory; +import ca.gc.aafc.seqdb.api.testsupport.factories.MetagenomicsBatchItemFactory; +import ca.gc.aafc.seqdb.api.testsupport.factories.NgsIndexFactory; + +public class MetagenomicsBatchIT extends SequenceModuleBaseIT { + + @Test + public void onCreateMetagenomicsBatchEntities_entitiesSaved() { + MetagenomicsBatch mgb = MetagenomicsBatchFactory.newMetagenomicsBatch() + .indexSet(IndexSetFactory.newIndexSet().build()) + .build(); + metagenomicsBatchService.create(mgb); + + MetagenomicsBatchItem mgbi = MetagenomicsBatchItemFactory.newMetagenomicsBatchItem(mgb) + .indexI5(NgsIndexFactory.newNgsIndex().build()) + .indexI7(NgsIndexFactory.newNgsIndex().build()) + .build(); + + metagenomicsBatchItemService.create(mgbi); + } +} + diff --git a/src/test/java/ca/gc/aafc/seqdb/api/repository/MetagenomicsBatchIT.java b/src/test/java/ca/gc/aafc/seqdb/api/repository/MetagenomicsBatchIT.java new file mode 100644 index 00000000..26f6d6e5 --- /dev/null +++ b/src/test/java/ca/gc/aafc/seqdb/api/repository/MetagenomicsBatchIT.java @@ -0,0 +1,76 @@ +package ca.gc.aafc.seqdb.api.repository; + +import org.junit.jupiter.api.Test; + +import ca.gc.aafc.seqdb.api.dto.MetagenomicsBatchDto; +import ca.gc.aafc.seqdb.api.dto.MetagenomicsBatchItemDto; +import ca.gc.aafc.seqdb.api.dto.MolecularAnalysisResultDto; +import ca.gc.aafc.seqdb.api.dto.MolecularAnalysisRunDto; +import ca.gc.aafc.seqdb.api.dto.MolecularAnalysisRunItemDto; +import ca.gc.aafc.seqdb.api.dto.pcr.PcrBatchDto; +import ca.gc.aafc.seqdb.api.dto.pcr.PcrBatchItemDto; +import ca.gc.aafc.seqdb.api.testsupport.fixtures.MetagenomicsBatchItemTestFixture; +import ca.gc.aafc.seqdb.api.testsupport.fixtures.MetagenomicsBatchTestFixture; +import ca.gc.aafc.seqdb.api.testsupport.fixtures.MolecularAnalysisResultFixture; +import ca.gc.aafc.seqdb.api.testsupport.fixtures.MolecularAnalysisRunItemTestFixture; +import ca.gc.aafc.seqdb.api.testsupport.fixtures.MolecularAnalysisRunTestFixture; +import ca.gc.aafc.seqdb.api.testsupport.fixtures.PcrBatchItemTestFixture; +import ca.gc.aafc.seqdb.api.testsupport.fixtures.PcrBatchTestFixture; + +import javax.inject.Inject; + +public class MetagenomicsBatchIT extends BaseRepositoryTestV2 { + + @Inject + private MetagenomicsBatchRepository metagenomicsBatchRepository; + + @Inject + private MetagenomicsBatchItemRepository metagenomicsBatchItemRepository; + + @Inject + private PcrBatchRepository pcrBatchRepository; + + @Inject + private PcrBatchItemRepository pcrBatchItemRepository; + + @Inject + private MolecularAnalysisRunRepository molecularAnalysisRunRepository; + + @Inject + private MolecularAnalysisRunItemRepository molecularAnalysisRunItemRepository; + + @Inject + private MolecularAnalysisResultRepository molecularAnalysisResultRepository; + + @Test + public void onValidDto_dtoSavedWithoutExceptions() { + + MetagenomicsBatchDto metagenomicsBatchDto = metagenomicsBatchRepository + .create(MetagenomicsBatchTestFixture.newMetagenomicsBatch()); + + PcrBatchDto pcrBatchDto = PcrBatchTestFixture.newPcrBatch(); + pcrBatchDto = pcrBatchRepository.create(pcrBatchDto); + + PcrBatchItemDto pcrBatchItemDto = PcrBatchItemTestFixture.newPcrBatchItem(pcrBatchDto); + pcrBatchItemDto = pcrBatchItemRepository.create(pcrBatchItemDto); + + MolecularAnalysisRunDto runDto = molecularAnalysisRunRepository + .create(MolecularAnalysisRunTestFixture.newMolecularAnalysisRun()); + + MolecularAnalysisResultDto resultDto = molecularAnalysisResultRepository + .create(MolecularAnalysisResultFixture.newMolecularAnalysisResult()); + + MolecularAnalysisRunItemDto runItemDto = MolecularAnalysisRunItemTestFixture + .newMolecularAnalysisRunItem(); + runItemDto.setRun(runDto); + runItemDto.setResult(resultDto); + + runItemDto = molecularAnalysisRunItemRepository.create(runItemDto); + + MetagenomicsBatchItemDto itemDto = MetagenomicsBatchItemTestFixture.newMetagenomicsBatchItem(metagenomicsBatchDto); + itemDto.setPcrBatchItem(pcrBatchItemDto); + itemDto.setMolecularAnalysisRunItem(runItemDto); + + metagenomicsBatchItemRepository.create(itemDto); + } +} diff --git a/src/test/java/ca/gc/aafc/seqdb/api/repository/QualityControlRepositoryIT.java b/src/test/java/ca/gc/aafc/seqdb/api/repository/QualityControlRepositoryIT.java new file mode 100644 index 00000000..70f11362 --- /dev/null +++ b/src/test/java/ca/gc/aafc/seqdb/api/repository/QualityControlRepositoryIT.java @@ -0,0 +1,42 @@ +package ca.gc.aafc.seqdb.api.repository; + +import org.junit.jupiter.api.Test; + +import ca.gc.aafc.seqdb.api.dto.MolecularAnalysisRunDto; +import ca.gc.aafc.seqdb.api.dto.MolecularAnalysisRunItemDto; +import ca.gc.aafc.seqdb.api.dto.QualityControlDto; +import ca.gc.aafc.seqdb.api.testsupport.fixtures.MolecularAnalysisRunItemTestFixture; +import ca.gc.aafc.seqdb.api.testsupport.fixtures.MolecularAnalysisRunTestFixture; +import ca.gc.aafc.seqdb.api.testsupport.fixtures.QualityControlTestFixture; + +import javax.inject.Inject; + +public class QualityControlRepositoryIT extends BaseRepositoryTestV2 { + + @Inject + private QualityControlRepository qualityControlRepository; + + @Inject + private MolecularAnalysisRunRepository molecularAnalysisRunRepository; + + @Inject + private MolecularAnalysisRunItemRepository molecularAnalysisRunItemRepository; + + @Test + public void onValidDto_dtoSavedWithoutExceptions() { + + MolecularAnalysisRunDto runDto = molecularAnalysisRunRepository + .create(MolecularAnalysisRunTestFixture.newMolecularAnalysisRun()); + + MolecularAnalysisRunItemDto runItemDto = MolecularAnalysisRunItemTestFixture + .newMolecularAnalysisRunItem(); + runItemDto.setRun(runDto); + molecularAnalysisRunItemRepository.create(runItemDto); + + QualityControlDto qualityControlDto = QualityControlTestFixture.newQualityControl(); + qualityControlDto.setMolecularAnalysisRunItem(runItemDto); + + qualityControlRepository.create(qualityControlDto); + } + +} diff --git a/src/test/java/ca/gc/aafc/seqdb/api/repository/SequenceManagedAttributeRepositoryIT.java b/src/test/java/ca/gc/aafc/seqdb/api/repository/SequenceManagedAttributeRepositoryIT.java new file mode 100644 index 00000000..983c1a66 --- /dev/null +++ b/src/test/java/ca/gc/aafc/seqdb/api/repository/SequenceManagedAttributeRepositoryIT.java @@ -0,0 +1,70 @@ +package ca.gc.aafc.seqdb.api.repository; + +import io.crnk.core.queryspec.QuerySpec; +import java.util.UUID; +import javax.inject.Inject; + +import org.junit.jupiter.api.Test; + +import ca.gc.aafc.dina.testsupport.security.WithMockKeycloakUser; +import ca.gc.aafc.dina.vocabulary.TypedVocabularyElement; +import ca.gc.aafc.seqdb.api.dto.SequenceManagedAttributeDto; +import ca.gc.aafc.seqdb.api.entities.SequenceManagedAttribute; + +import ca.gc.aafc.seqdb.api.testsupport.fixtures.SequenceManagedAttributeTestFixture; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +public class SequenceManagedAttributeRepositoryIT extends BaseRepositoryTestV2 { + + @Inject + private SequenceManagedAttributeRepository repo; + + @Test + @WithMockKeycloakUser(groupRole = "dina-group:SUPER_USER") + void create_recordCreated() { + String expectedName = "dina attribute #12"; + String expectedValue = "dina value"; + String expectedCreatedBy = "dina"; + String expectedGroup = "dina-group"; + + SequenceManagedAttributeDto dto = SequenceManagedAttributeTestFixture.newManagedAttribute(); + dto.setName(expectedName); + dto.setVocabularyElementType(TypedVocabularyElement.VocabularyElementType.INTEGER); + dto.setAcceptedValues(new String[]{expectedValue}); + dto.setManagedAttributeComponent(SequenceManagedAttribute.ManagedAttributeComponent.GENERIC_MOLECULAR_ANALYSIS); + dto.setCreatedBy(expectedCreatedBy); + dto.setGroup(expectedGroup); + + UUID uuid = repo.create(dto).getUuid(); + SequenceManagedAttributeDto result = repo.findOne(uuid, new QuerySpec( + SequenceManagedAttributeDto.class)); + assertEquals(uuid, result.getUuid()); + assertEquals(expectedName, result.getName()); + assertEquals("dina_attribute_12", result.getKey()); + assertEquals(expectedValue, result.getAcceptedValues()[0]); + assertNotNull(result.getCreatedBy()); + assertEquals(expectedGroup, result.getGroup()); + assertEquals(TypedVocabularyElement.VocabularyElementType.INTEGER, result.getVocabularyElementType()); + assertEquals( + SequenceManagedAttribute.ManagedAttributeComponent.GENERIC_MOLECULAR_ANALYSIS, + result.getManagedAttributeComponent()); + } + + @Test + @WithMockKeycloakUser(groupRole = SequenceManagedAttributeTestFixture.GROUP + ":SUPER_USER") + void findOneByKey_whenKeyProvided_managedAttributeFetched() { + SequenceManagedAttributeDto newAttribute = SequenceManagedAttributeTestFixture.newManagedAttribute(); + newAttribute.setName("Attribute 1"); + newAttribute.setVocabularyElementType(TypedVocabularyElement.VocabularyElementType.INTEGER); + newAttribute.setManagedAttributeComponent(SequenceManagedAttribute.ManagedAttributeComponent.GENERIC_MOLECULAR_ANALYSIS); + + UUID newAttributeUuid = repo.create(newAttribute).getUuid(); + + QuerySpec querySpec = new QuerySpec(SequenceManagedAttributeDto.class); + SequenceManagedAttributeDto fetchedAttribute = repo.findOne("generic_molecular_analysis.attribute_1", querySpec); + + assertEquals(newAttributeUuid, fetchedAttribute.getUuid()); + } +} diff --git a/src/test/java/ca/gc/aafc/seqdb/api/repository/VocabularyRepositoryIT.java b/src/test/java/ca/gc/aafc/seqdb/api/repository/VocabularyRepositoryIT.java index 4081b55a..1f77884d 100644 --- a/src/test/java/ca/gc/aafc/seqdb/api/repository/VocabularyRepositoryIT.java +++ b/src/test/java/ca/gc/aafc/seqdb/api/repository/VocabularyRepositoryIT.java @@ -18,7 +18,7 @@ public class VocabularyRepositoryIT extends BaseRepositoryTest { @Test public void findAll_DefaultQuerySpec_AllDtosReturned() { List resultList = readOnlyRepo.findAll(""); - assertEquals(3, resultList.size()); + assertEquals(4, resultList.size()); } @Test diff --git a/src/test/java/ca/gc/aafc/seqdb/api/service/GenericMolecularAnalysisServiceIT.java b/src/test/java/ca/gc/aafc/seqdb/api/service/GenericMolecularAnalysisServiceIT.java new file mode 100644 index 00000000..d57dd5ee --- /dev/null +++ b/src/test/java/ca/gc/aafc/seqdb/api/service/GenericMolecularAnalysisServiceIT.java @@ -0,0 +1,61 @@ +package ca.gc.aafc.seqdb.api.service; + +import org.junit.jupiter.api.Test; + +import ca.gc.aafc.dina.vocabulary.TypedVocabularyElement; +import ca.gc.aafc.seqdb.api.SequenceModuleBaseIT; +import ca.gc.aafc.seqdb.api.entities.GenericMolecularAnalysis; +import ca.gc.aafc.seqdb.api.entities.SequenceManagedAttribute; +import ca.gc.aafc.seqdb.api.testsupport.factories.GenericMolecularAnalysisFactory; +import ca.gc.aafc.seqdb.api.testsupport.factories.SequenceManagedAttributeFactory; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.Map; +import javax.validation.ValidationException; + +public class GenericMolecularAnalysisServiceIT extends SequenceModuleBaseIT { + + private static final String GROUP = "grp"; + + @Test + void assignedValueContainedInAcceptedValues_validationPasses() { + SequenceManagedAttribute testManagedAttribute = SequenceManagedAttributeFactory.newManagedAttribute() + .acceptedValues(new String[]{"val1", "val2"}) + .managedAttributeComponent(SequenceManagedAttribute.ManagedAttributeComponent.GENERIC_MOLECULAR_ANALYSIS) + .build(); + + managedAttributeService.create(testManagedAttribute); + + GenericMolecularAnalysis genericMolecularAnalysis = GenericMolecularAnalysisFactory + .newGenericMolecularAnalysis() + .managedAttributes(Map.of(testManagedAttribute.getKey(), testManagedAttribute.getAcceptedValues()[0])) + .build(); + + assertDoesNotThrow(() -> genericMolecularAnalysisService.create(genericMolecularAnalysis)); + } + + @Test + void validate_WhenInvalidIntegerType_ExceptionThrown() { + SequenceManagedAttribute testManagedAttribute = + SequenceManagedAttributeFactory.newManagedAttribute() + .createdBy("GenericMolecularAnalysisServiceIT") + .managedAttributeComponent( + SequenceManagedAttribute.ManagedAttributeComponent.GENERIC_MOLECULAR_ANALYSIS) + .group(GROUP) + .vocabularyElementType(TypedVocabularyElement.VocabularyElementType.INTEGER) + .acceptedValues(null) + .build(); + + managedAttributeService.create(testManagedAttribute); + + GenericMolecularAnalysis genericMolecularAnalysis = GenericMolecularAnalysisFactory + .newGenericMolecularAnalysis() + .managedAttributes(Map.of(testManagedAttribute.getKey(), "1.2")) + .build(); + + assertThrows(ValidationException.class, + () -> genericMolecularAnalysisService.create(genericMolecularAnalysis)); + } +} \ No newline at end of file diff --git a/src/test/java/ca/gc/aafc/seqdb/api/service/QualityControlServiceIT.java b/src/test/java/ca/gc/aafc/seqdb/api/service/QualityControlServiceIT.java new file mode 100644 index 00000000..8cac4327 --- /dev/null +++ b/src/test/java/ca/gc/aafc/seqdb/api/service/QualityControlServiceIT.java @@ -0,0 +1,16 @@ +package ca.gc.aafc.seqdb.api.service; + +import org.junit.jupiter.api.Test; + +import ca.gc.aafc.seqdb.api.SequenceModuleBaseIT; +import ca.gc.aafc.seqdb.api.entities.QualityControl; +import ca.gc.aafc.seqdb.api.testsupport.factories.QualityControlFactory; + +public class QualityControlServiceIT extends SequenceModuleBaseIT { + + @Test + public void onValidEntity_save_noException() { + QualityControl qc = QualityControlFactory.newQualityControl().build(); + qualityControlService.create(qc); + } +} diff --git a/src/test/java/ca/gc/aafc/seqdb/api/service/SequenceManagedAttributeServiceIT.java b/src/test/java/ca/gc/aafc/seqdb/api/service/SequenceManagedAttributeServiceIT.java new file mode 100644 index 00000000..c4f9ce94 --- /dev/null +++ b/src/test/java/ca/gc/aafc/seqdb/api/service/SequenceManagedAttributeServiceIT.java @@ -0,0 +1,39 @@ +package ca.gc.aafc.seqdb.api.service; + +import org.junit.jupiter.api.Test; + +import ca.gc.aafc.seqdb.api.SequenceModuleBaseIT; +import ca.gc.aafc.seqdb.api.entities.SequenceManagedAttribute; +import ca.gc.aafc.seqdb.api.testsupport.factories.SequenceManagedAttributeFactory; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + +public class SequenceManagedAttributeServiceIT extends SequenceModuleBaseIT { + + private static final String GROUP = "grp"; + + @Test + void delete_WhenNotInUse_DeleteAccepted() { + SequenceManagedAttribute attribute = newAttribute(SequenceManagedAttribute.ManagedAttributeComponent.GENERIC_MOLECULAR_ANALYSIS); + managedAttributeService.create(attribute); + + assertNotNull( + managedAttributeService.findOne(attribute.getUuid(), SequenceManagedAttribute.class)); + + // To enable when usage is implemented + managedAttributeService.delete(attribute); + + assertNull( + managedAttributeService.findOne(attribute.getUuid(), SequenceManagedAttribute.class)); + } + + private static SequenceManagedAttribute newAttribute(SequenceManagedAttribute.ManagedAttributeComponent component) { + return SequenceManagedAttributeFactory.newManagedAttribute() + .createdBy("SequenceManagedAttributeServiceIT") + .managedAttributeComponent(component) + .group(GROUP) + .acceptedValues(null) + .build(); + } +} diff --git a/src/test/java/ca/gc/aafc/seqdb/api/testsupport/factories/MetagenomicsBatchFactory.java b/src/test/java/ca/gc/aafc/seqdb/api/testsupport/factories/MetagenomicsBatchFactory.java new file mode 100644 index 00000000..b1bfdf2b --- /dev/null +++ b/src/test/java/ca/gc/aafc/seqdb/api/testsupport/factories/MetagenomicsBatchFactory.java @@ -0,0 +1,21 @@ +package ca.gc.aafc.seqdb.api.testsupport.factories; + +import java.util.UUID; + +import ca.gc.aafc.seqdb.api.entities.MetagenomicsBatch; + +public final class MetagenomicsBatchFactory { + + private MetagenomicsBatchFactory() { + // static utility class + } + + public static MetagenomicsBatch.MetagenomicsBatchBuilder newMetagenomicsBatch() { + + return MetagenomicsBatch.builder() + .uuid(UUID.randomUUID()) + .createdBy("test user") + .name(TestableEntityFactory.generateRandomName(10)) + .group("dina"); + } +} diff --git a/src/test/java/ca/gc/aafc/seqdb/api/testsupport/factories/MetagenomicsBatchItemFactory.java b/src/test/java/ca/gc/aafc/seqdb/api/testsupport/factories/MetagenomicsBatchItemFactory.java new file mode 100644 index 00000000..ab176863 --- /dev/null +++ b/src/test/java/ca/gc/aafc/seqdb/api/testsupport/factories/MetagenomicsBatchItemFactory.java @@ -0,0 +1,24 @@ +package ca.gc.aafc.seqdb.api.testsupport.factories; + +import ca.gc.aafc.seqdb.api.entities.MetagenomicsBatch; +import ca.gc.aafc.seqdb.api.entities.MetagenomicsBatchItem; + +import java.util.UUID; + +public final class MetagenomicsBatchItemFactory { + + private MetagenomicsBatchItemFactory() { + // static utility class + } + + public static MetagenomicsBatchItem.MetagenomicsBatchItemBuilder newMetagenomicsBatchItem( + MetagenomicsBatch metagenomicsBatch) { + + return MetagenomicsBatchItem.builder() + .uuid(UUID.randomUUID()) + .createdBy("test user") + .metagenomicsBatch(metagenomicsBatch); + } +} + + diff --git a/src/test/java/ca/gc/aafc/seqdb/api/testsupport/factories/QualityControlFactory.java b/src/test/java/ca/gc/aafc/seqdb/api/testsupport/factories/QualityControlFactory.java new file mode 100644 index 00000000..733a3754 --- /dev/null +++ b/src/test/java/ca/gc/aafc/seqdb/api/testsupport/factories/QualityControlFactory.java @@ -0,0 +1,16 @@ +package ca.gc.aafc.seqdb.api.testsupport.factories; + +import ca.gc.aafc.dina.util.UUIDHelper; +import ca.gc.aafc.seqdb.api.entities.QualityControl; + +public class QualityControlFactory { + + public static QualityControl.QualityControlBuilder newQualityControl() { + return QualityControl.builder() + .name(TestableEntityFactory.generateRandomName(7)) + .qcType("reserpine_standard") + .uuid(UUIDHelper.generateUUIDv7()) + .group("dina") + .createdBy("test user"); + } +} diff --git a/src/test/java/ca/gc/aafc/seqdb/api/testsupport/factories/SequenceManagedAttributeFactory.java b/src/test/java/ca/gc/aafc/seqdb/api/testsupport/factories/SequenceManagedAttributeFactory.java new file mode 100644 index 00000000..fb40a1a9 --- /dev/null +++ b/src/test/java/ca/gc/aafc/seqdb/api/testsupport/factories/SequenceManagedAttributeFactory.java @@ -0,0 +1,26 @@ +package ca.gc.aafc.seqdb.api.testsupport.factories; + +import java.util.UUID; + +import org.apache.commons.lang3.RandomStringUtils; + +import ca.gc.aafc.dina.vocabulary.TypedVocabularyElement; +import ca.gc.aafc.seqdb.api.entities.SequenceManagedAttribute; +import ca.gc.aafc.seqdb.api.testsupport.fixtures.MultilingualTestFixture; + +public class SequenceManagedAttributeFactory { + + public static SequenceManagedAttribute.SequenceManagedAttributeBuilder newManagedAttribute() { + return SequenceManagedAttribute + .builder() + .uuid(UUID.randomUUID()) + .name(RandomStringUtils.randomAlphabetic(5)) + .group(RandomStringUtils.randomAlphabetic(5)) + .createdBy(RandomStringUtils.randomAlphabetic(5)) + .vocabularyElementType(TypedVocabularyElement.VocabularyElementType.STRING) + .acceptedValues(new String[]{"value"}) + .managedAttributeComponent(SequenceManagedAttribute.ManagedAttributeComponent.GENERIC_MOLECULAR_ANALYSIS) + .multilingualDescription(MultilingualTestFixture.newMultilingualDescription()); + } + +} diff --git a/src/test/java/ca/gc/aafc/seqdb/api/testsupport/fixtures/MetagenomicsBatchItemTestFixture.java b/src/test/java/ca/gc/aafc/seqdb/api/testsupport/fixtures/MetagenomicsBatchItemTestFixture.java new file mode 100644 index 00000000..e4f4910e --- /dev/null +++ b/src/test/java/ca/gc/aafc/seqdb/api/testsupport/fixtures/MetagenomicsBatchItemTestFixture.java @@ -0,0 +1,19 @@ +package ca.gc.aafc.seqdb.api.testsupport.fixtures; + +import ca.gc.aafc.seqdb.api.dto.MetagenomicsBatchDto; +import ca.gc.aafc.seqdb.api.dto.MetagenomicsBatchItemDto; + +public class MetagenomicsBatchItemTestFixture { + + public static MetagenomicsBatchItemDto newMetagenomicsBatchItem(MetagenomicsBatchDto batchDto) { + MetagenomicsBatchItemDto metagenomicsBatchItemDto = newMetagenomicsBatchItem(); + metagenomicsBatchItemDto.setMetagenomicsBatch(batchDto); + return metagenomicsBatchItemDto; + } + + public static MetagenomicsBatchItemDto newMetagenomicsBatchItem() { + return MetagenomicsBatchItemDto.builder() + .createdBy("test-user") + .build(); + } +} diff --git a/src/test/java/ca/gc/aafc/seqdb/api/testsupport/fixtures/MetagenomicsBatchTestFixture.java b/src/test/java/ca/gc/aafc/seqdb/api/testsupport/fixtures/MetagenomicsBatchTestFixture.java new file mode 100644 index 00000000..1888746c --- /dev/null +++ b/src/test/java/ca/gc/aafc/seqdb/api/testsupport/fixtures/MetagenomicsBatchTestFixture.java @@ -0,0 +1,17 @@ +package ca.gc.aafc.seqdb.api.testsupport.fixtures; + +import ca.gc.aafc.seqdb.api.dto.MetagenomicsBatchDto; +import ca.gc.aafc.seqdb.api.testsupport.factories.TestableEntityFactory; + +public class MetagenomicsBatchTestFixture { + + public static final String GROUP = "aafc"; + + public static MetagenomicsBatchDto newMetagenomicsBatch() { + return MetagenomicsBatchDto.builder() + .group(GROUP) + .name(TestableEntityFactory.generateRandomName(12)) + .createdBy("test-user") + .build(); + } +} diff --git a/src/test/java/ca/gc/aafc/seqdb/api/testsupport/fixtures/MultilingualTestFixture.java b/src/test/java/ca/gc/aafc/seqdb/api/testsupport/fixtures/MultilingualTestFixture.java new file mode 100644 index 00000000..542b08b1 --- /dev/null +++ b/src/test/java/ca/gc/aafc/seqdb/api/testsupport/fixtures/MultilingualTestFixture.java @@ -0,0 +1,26 @@ +package ca.gc.aafc.seqdb.api.testsupport.fixtures; + +import java.util.List; + +import org.apache.commons.lang3.RandomStringUtils; + +import ca.gc.aafc.dina.i18n.MultilingualDescription; +import ca.gc.aafc.dina.i18n.MultilingualTitle; + +public class MultilingualTestFixture { + + public static MultilingualDescription newMultilingualDescription() { + return MultilingualDescription.builder() + .descriptions(List.of(MultilingualDescription + .MultilingualPair.of("en", RandomStringUtils.randomAlphabetic(4)))) + .build(); + } + + public static MultilingualTitle newMultilingualTitle() { + return MultilingualTitle.builder() + .titles(List.of(MultilingualTitle + .MultilingualTitlePair.of("en", RandomStringUtils.randomAlphabetic(4)))) + .build(); + } + +} diff --git a/src/test/java/ca/gc/aafc/seqdb/api/testsupport/fixtures/PcrBatchItemTestFixture.java b/src/test/java/ca/gc/aafc/seqdb/api/testsupport/fixtures/PcrBatchItemTestFixture.java index eb6fcd99..f538f3a5 100644 --- a/src/test/java/ca/gc/aafc/seqdb/api/testsupport/fixtures/PcrBatchItemTestFixture.java +++ b/src/test/java/ca/gc/aafc/seqdb/api/testsupport/fixtures/PcrBatchItemTestFixture.java @@ -1,5 +1,6 @@ package ca.gc.aafc.seqdb.api.testsupport.fixtures; +import ca.gc.aafc.seqdb.api.dto.pcr.PcrBatchDto; import ca.gc.aafc.seqdb.api.dto.pcr.PcrBatchItemDto; public class PcrBatchItemTestFixture { @@ -8,6 +9,12 @@ public class PcrBatchItemTestFixture { public static final String CREATED_BY = "created_by"; public static final String RESULT = "Good Band"; + public static PcrBatchItemDto newPcrBatchItem(PcrBatchDto batch) { + PcrBatchItemDto pcrBatchItemDto = newPcrBatchItem(); + pcrBatchItemDto.setPcrBatch(batch); + return pcrBatchItemDto; + } + public static PcrBatchItemDto newPcrBatchItem() { PcrBatchItemDto pcrBatchItemDto = new PcrBatchItemDto(); pcrBatchItemDto.setGroup(GROUP); diff --git a/src/test/java/ca/gc/aafc/seqdb/api/testsupport/fixtures/QualityControlTestFixture.java b/src/test/java/ca/gc/aafc/seqdb/api/testsupport/fixtures/QualityControlTestFixture.java new file mode 100644 index 00000000..a1ec1ead --- /dev/null +++ b/src/test/java/ca/gc/aafc/seqdb/api/testsupport/fixtures/QualityControlTestFixture.java @@ -0,0 +1,18 @@ +package ca.gc.aafc.seqdb.api.testsupport.fixtures; + +import ca.gc.aafc.seqdb.api.dto.QualityControlDto; +import ca.gc.aafc.seqdb.api.testsupport.factories.TestableEntityFactory; + +public class QualityControlTestFixture { + + public static final String GROUP = "aafc"; + + public static QualityControlDto newQualityControl() { + return QualityControlDto.builder() + .group(GROUP) + .name(TestableEntityFactory.generateRandomName(11)) + .qcType("reserpine_standard") + .createdBy("test-user") + .build(); + } +} diff --git a/src/test/java/ca/gc/aafc/seqdb/api/testsupport/fixtures/SequenceManagedAttributeTestFixture.java b/src/test/java/ca/gc/aafc/seqdb/api/testsupport/fixtures/SequenceManagedAttributeTestFixture.java new file mode 100644 index 00000000..46e1a8f1 --- /dev/null +++ b/src/test/java/ca/gc/aafc/seqdb/api/testsupport/fixtures/SequenceManagedAttributeTestFixture.java @@ -0,0 +1,26 @@ +package ca.gc.aafc.seqdb.api.testsupport.fixtures; + +import org.apache.commons.lang3.RandomStringUtils; + +import ca.gc.aafc.dina.vocabulary.TypedVocabularyElement; +import ca.gc.aafc.seqdb.api.dto.SequenceManagedAttributeDto; +import ca.gc.aafc.seqdb.api.entities.SequenceManagedAttribute; + +public class SequenceManagedAttributeTestFixture { + + public static final String GROUP = "dina"; + + public static SequenceManagedAttributeDto newManagedAttribute() { + SequenceManagedAttributeDto collectionManagedAttributeDto = new SequenceManagedAttributeDto(); + collectionManagedAttributeDto.setName(RandomStringUtils.randomAlphabetic(5)); + collectionManagedAttributeDto.setGroup(GROUP); + collectionManagedAttributeDto.setVocabularyElementType( + TypedVocabularyElement.VocabularyElementType.INTEGER); + collectionManagedAttributeDto.setAcceptedValues(new String[]{"1", "2"}); + collectionManagedAttributeDto.setUnit("cm"); + collectionManagedAttributeDto.setManagedAttributeComponent(SequenceManagedAttribute.ManagedAttributeComponent.GENERIC_MOLECULAR_ANALYSIS); + collectionManagedAttributeDto.setCreatedBy("created by"); + collectionManagedAttributeDto.setMultilingualDescription(MultilingualTestFixture.newMultilingualDescription()); + return collectionManagedAttributeDto; + } +}