Skip to content

Commit

Permalink
Merge branch 'dev' into 35276_add_metagenomic_workflow_resource
Browse files Browse the repository at this point in the history
  • Loading branch information
cgendreau committed Dec 3, 2024
2 parents 380d87f + b8fc9e3 commit b43c16a
Show file tree
Hide file tree
Showing 12 changed files with 304 additions and 9 deletions.
16 changes: 16 additions & 0 deletions src/main/java/ca/gc/aafc/seqdb/api/ResourceRepositoryConfig.java
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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()));
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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<String, String> managedAttributes = Map.of();

}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;

Expand Down Expand Up @@ -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<String, String> managedAttributes = Map.of();

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,51 @@
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<GenericMolecularAnalysis> {

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
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);
}
}

}
Original file line number Diff line number Diff line change
@@ -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<Object> {
private final StringMapper<Object> 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);
}

}
Original file line number Diff line number Diff line change
@@ -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<SequenceManagedAttribute> {

private static final String INVALID_VALIDATION_CONTEXT_KEY = "managedAttribute.validation.context.invalid";
private static final String COMPONENT_FIELD_NAME = "managedAttributeComponent";

private final ManagedAttributeService<SequenceManagedAttribute> dinaService;
private final MessageSource messageSource;

public SequenceManagedAttributeValueValidator(
@Named("validationMessageSource") MessageSource baseMessageSource, // from dina-base
@NonNull MessageSource messageSource,
@NonNull ManagedAttributeService<SequenceManagedAttribute> dinaService) {
super(baseMessageSource, dinaService);
this.dinaService = dinaService;
this.messageSource = messageSource;
}

public <D extends DinaEntity> void validate(D entity, Map<String, String> 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<String, SequenceManagedAttribute> findAttributesForValidation(Set<String> 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<SequenceManagedAttribute.ManagedAttributeComponent, SequenceManagedAttributeValidationContext>
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());
}
}
1 change: 1 addition & 0 deletions src/main/resources/db/changelog/db.changelog-master.xml
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,5 @@
<include file="db/changelog/migrations/53-Add_attachments_to_MolecularAnalysisRun.xml"/>
<include file="db/changelog/migrations/54-Add_metagenomics_tables.xml"/>
<include file="db/changelog/migrations/55-Add_managed_attribute.xml"/>
<include file="db/changelog/migrations/56-Add_managed_attribute_to_molecular_analysis.xml"/>
</databaseChangeLog>
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog https://local.xsd/dbchangelog-4.4.xsd"
objectQuotingStrategy="QUOTE_ONLY_RESERVED_WORDS">

<changeSet id="56-Add_managed_attribute_to_molecular_analysis" context="schema-change" author="cgendreau">
<addColumn tableName="generic_molecular_analysis">
<column name="managed_attributes" type="jsonb" defaultValue="{}" />
</addColumn>
</changeSet>
</databaseChangeLog>
9 changes: 5 additions & 4 deletions src/main/resources/vocabulary/pcrBatchType.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
title: Illumina Metagenomics

Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,20 @@ void create_recordCreated() {
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());
}
}
Original file line number Diff line number Diff line change
@@ -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));
}
}
Loading

0 comments on commit b43c16a

Please sign in to comment.